next.txt is the only file intended to be mutated. It's a list of
features that may be added to the next version. It only affects
warning output from the go api tool.
-
pkg syscall (openbsd-amd64), const SYS_KILL = 37
pkg syscall (openbsd-amd64-cgo), const SYS_KILL = 37
pkg unicode, const Version = "9.0.0"
+pkg text/template/parse, method (*AssignNode) Copy() Node
+pkg text/template/parse, method (*AssignNode) String() string
+pkg text/template/parse, method (*VariableNode) Copy() Node
+pkg text/template/parse, method (*VariableNode) String() string
+pkg text/template/parse, method (AssignNode) Position() Pos
+pkg text/template/parse, method (AssignNode) Type() NodeType
+pkg text/template/parse, method (VariableNode) Position() Pos
+pkg text/template/parse, method (VariableNode) Type() NodeType
+pkg text/template/parse, type AssignNode struct
+pkg text/template/parse, type AssignNode struct, Ident []string
+pkg text/template/parse, type AssignNode struct, embedded NodeType
+pkg text/template/parse, type AssignNode struct, embedded Pos
+pkg text/template/parse, type PipeNode struct, Decl []*VariableNode
+pkg text/template/parse, type PipeNode struct, Decl bool
+pkg text/template/parse, type PipeNode struct, Vars []*AssignNode
+pkg text/template/parse, type VariableNode struct
+pkg text/template/parse, type VariableNode struct, Ident []string
+pkg text/template/parse, type VariableNode struct, embedded NodeType
+pkg text/template/parse, type VariableNode struct, embedded Pos
// escapeAction escapes an action template node.
func (e *escaper) escapeAction(c context, n *parse.ActionNode) context {
- if len(n.Pipe.Decl) != 0 {
+ if len(n.Pipe.Vars) != 0 {
// A local variable assignment, not an interpolation.
return c
}
where $variable is the name of the variable. An action that declares a
variable produces no output.
+Variables previously declared can also be assigned, using the syntax
+
+ $variable = pipeline
+
If a "range" action initializes a variable, the variable is set to the
successive elements of the iteration. Also, a "range" may declare two
variables, separated by a comma:
s.vars = s.vars[0:mark]
}
-// setVar overwrites the top-nth variable on the stack. Used by range iterations.
-func (s *state) setVar(n int, value reflect.Value) {
+// setVar overwrites the last declared variable with the given name.
+// Used by variable assignments.
+func (s *state) setVar(name string, value reflect.Value) {
+ for i := s.mark() - 1; i >= 0; i-- {
+ if s.vars[i].name == name {
+ s.vars[i].value = value
+ return
+ }
+ }
+ s.errorf("undefined variable: %s", name)
+}
+
+// setTopVar overwrites the top-nth variable on the stack. Used by range iterations.
+func (s *state) setTopVar(n int, value reflect.Value) {
s.vars[len(s.vars)-n].value = value
}
// Do not pop variables so they persist until next end.
// Also, if the action declares variables, don't print the result.
val := s.evalPipeline(dot, node.Pipe)
- if len(node.Pipe.Decl) == 0 {
+ if len(node.Pipe.Vars) == 0 {
s.printValue(node, val)
}
case *parse.IfNode:
mark := s.mark()
oneIteration := func(index, elem reflect.Value) {
// Set top var (lexically the second if there are two) to the element.
- if len(r.Pipe.Decl) > 0 {
- s.setVar(1, elem)
+ if len(r.Pipe.Vars) > 0 {
+ s.setTopVar(1, elem)
}
// Set next var (lexically the first if there are two) to the index.
- if len(r.Pipe.Decl) > 1 {
- s.setVar(2, index)
+ if len(r.Pipe.Vars) > 1 {
+ s.setTopVar(2, index)
}
s.walk(elem, r.List)
s.pop(mark)
value = reflect.ValueOf(value.Interface()) // lovely!
}
}
- for _, variable := range pipe.Decl {
- s.push(variable.Ident[0], value)
+ for _, variable := range pipe.Vars {
+ if pipe.Decl {
+ s.push(variable.Ident[0], value)
+ } else {
+ s.setVar(variable.Ident[0], value)
+ }
}
return value
}
case *parse.PipeNode:
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
return s.evalPipeline(dot, n)
- case *parse.VariableNode:
+ case *parse.AssignNode:
return s.evalVariableNode(dot, n, cmd.Args, final)
}
s.at(firstWord)
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
}
-func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
+func (s *state) evalVariableNode(dot reflect.Value, variable *parse.AssignNode, args []parse.Node, final reflect.Value) reflect.Value {
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
s.at(variable)
value := s.varValue(variable.Ident[0])
s.errorf("cannot assign nil to %s", typ)
case *parse.FieldNode:
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, missingVal), typ)
- case *parse.VariableNode:
+ case *parse.AssignNode:
return s.validateType(s.evalVariableNode(dot, arg, nil, missingVal), typ)
case *parse.PipeNode:
return s.validateType(s.evalPipeline(dot, arg), typ)
return s.idealConstant(n)
case *parse.StringNode:
return reflect.ValueOf(n.Text)
- case *parse.VariableNode:
+ case *parse.AssignNode:
return s.evalVariableNode(dot, n, nil, missingVal)
case *parse.PipeNode:
return s.evalPipeline(dot, n)
{"$.I", "{{$.I}}", "17", tVal, true},
{"$.U.V", "{{$.U.V}}", "v", tVal, true},
{"declare in action", "{{$x := $.U.V}}{{$x}}", "v", tVal, true},
+ {"simple assignment", "{{$x := 2}}{{$x = 3}}{{$x}}", "3", tVal, true},
+ {"nested assignment",
+ "{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{$x}}",
+ "3", tVal, true},
+ {"nested assignment changes the last declaration",
+ "{{$x := 1}}{{if true}}{{$x := 2}}{{if true}}{{$x = 3}}{{end}}{{end}}{{$x}}",
+ "1", tVal, true},
// Type with String method.
{"V{6666}.String()", "-{{.V0}}-", "-<6666>-", tVal, true},
itemChar // printable ASCII character; grab bag for comma etc.
itemCharConstant // character constant
itemComplex // complex constant (1+2i); imaginary is just a number
- itemColonEquals // colon-equals (':=') introducing a declaration
+ itemAssign // colon-equals ('=') introducing an assignment
+ itemDeclare // colon-equals (':=') introducing a declaration
itemEOF
itemField // alphanumeric identifier starting with '.'
itemIdentifier // alphanumeric identifier not starting with '.'
return l.errorf("unclosed action")
case isSpace(r):
return lexSpace
+ case r == '=':
+ l.emit(itemAssign)
case r == ':':
if l.next() != '=' {
return l.errorf("expected :=")
}
- l.emit(itemColonEquals)
+ l.emit(itemDeclare)
case r == '|':
l.emit(itemPipe)
case r == '"':
itemChar: "char",
itemCharConstant: "charconst",
itemComplex: "complex",
- itemColonEquals: ":=",
+ itemDeclare: ":=",
itemEOF: "EOF",
itemField: "field",
itemIdentifier: "identifier",
tLeft,
mkItem(itemVariable, "$c"),
tSpace,
- mkItem(itemColonEquals, ":="),
+ mkItem(itemDeclare, ":="),
tSpace,
mkItem(itemIdentifier, "printf"),
tSpace,
tLeft,
mkItem(itemVariable, "$v"),
tSpace,
- mkItem(itemColonEquals, ":="),
+ mkItem(itemDeclare, ":="),
tSpace,
mkItem(itemNumber, "3"),
tRight,
tSpace,
mkItem(itemVariable, "$w"),
tSpace,
- mkItem(itemColonEquals, ":="),
+ mkItem(itemDeclare, ":="),
tSpace,
mkItem(itemNumber, "3"),
tRight,
NodeType
Pos
tr *Tree
- Line int // The line number in the input. Deprecated: Kept for compatibility.
- Decl []*VariableNode // Variable declarations in lexical order.
- Cmds []*CommandNode // The commands in lexical order.
+ Line int // The line number in the input. Deprecated: Kept for compatibility.
+ Decl bool // The variables are being declared, not assigned
+ Vars []*AssignNode // Variables in lexical order.
+ Cmds []*CommandNode // The commands in lexical order.
}
-func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
- return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
+func (t *Tree) newPipeline(pos Pos, line int, vars []*AssignNode) *PipeNode {
+ return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Vars: vars}
}
func (p *PipeNode) append(command *CommandNode) {
func (p *PipeNode) String() string {
s := ""
- if len(p.Decl) > 0 {
- for i, v := range p.Decl {
+ if len(p.Vars) > 0 {
+ for i, v := range p.Vars {
if i > 0 {
s += ", "
}
if p == nil {
return p
}
- var decl []*VariableNode
- for _, d := range p.Decl {
- decl = append(decl, d.Copy().(*VariableNode))
+ var vars []*AssignNode
+ for _, d := range p.Vars {
+ vars = append(vars, d.Copy().(*AssignNode))
}
- n := p.tr.newPipeline(p.Pos, p.Line, decl)
+ n := p.tr.newPipeline(p.Pos, p.Line, vars)
for _, c := range p.Cmds {
n.append(c.Copy().(*CommandNode))
}
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
}
-// VariableNode holds a list of variable names, possibly with chained field
+// AssignNode holds a list of variable names, possibly with chained field
// accesses. The dollar sign is part of the (first) name.
-type VariableNode struct {
+type AssignNode struct {
NodeType
Pos
tr *Tree
Ident []string // Variable name and fields in lexical order.
}
-func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
- return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
+func (t *Tree) newVariable(pos Pos, ident string) *AssignNode {
+ return &AssignNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
}
-func (v *VariableNode) String() string {
+func (v *AssignNode) String() string {
s := ""
for i, id := range v.Ident {
if i > 0 {
return s
}
-func (v *VariableNode) tree() *Tree {
+func (v *AssignNode) tree() *Tree {
return v.tr
}
-func (v *VariableNode) Copy() Node {
- return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
+func (v *AssignNode) Copy() Node {
+ return &AssignNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
}
// DotNode holds the special identifier '.'.
// Pipeline:
// declarations? command ('|' command)*
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
- var decl []*VariableNode
+ decl := false
+ var vars []*AssignNode
token := t.peekNonSpace()
pos := token.pos
- // Are there declarations?
+ // Are there declarations or assignments?
for {
if v := t.peekNonSpace(); v.typ == itemVariable {
t.next()
// argument variable rather than a declaration. So remember the token
// adjacent to the variable so we can push it back if necessary.
tokenAfterVariable := t.peek()
- if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
+ next := t.peekNonSpace()
+ switch {
+ case next.typ == itemAssign, next.typ == itemDeclare,
+ next.typ == itemChar && next.val == ",":
t.nextNonSpace()
variable := t.newVariable(v.pos, v.val)
- decl = append(decl, variable)
+ vars = append(vars, variable)
t.vars = append(t.vars, v.val)
+ if next.typ == itemDeclare {
+ decl = true
+ }
if next.typ == itemChar && next.val == "," {
- if context == "range" && len(decl) < 2 {
+ if context == "range" && len(vars) < 2 {
continue
}
t.errorf("too many declarations in %s", context)
}
- } else if tokenAfterVariable.typ == itemSpace {
+ case tokenAfterVariable.typ == itemSpace:
t.backup3(v, tokenAfterVariable)
- } else {
+ default:
t.backup2(v)
}
}
break
}
- pipe = t.newPipeline(pos, token.line, decl)
+ pipe = t.newPipeline(pos, token.line, vars)
+ pipe.Decl = decl
for {
switch token := t.nextNonSpace(); token.typ {
case itemRightDelim, itemRightParen:
{"adjacent args", "{{printf 3`x`}}", hasError, ""},
{"adjacent args with .", "{{printf `x`.}}", hasError, ""},
{"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
- // Equals (and other chars) do not assignments make (yet).
+ // Other kinds of assignments and operators aren't available yet.
{"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
- {"bug0b", "{{$x = 1}}{{$x}}", hasError, ""},
+ {"bug0b", "{{$x += 1}}{{$x}}", hasError, ""},
{"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""},
{"bug0d", "{{$x % 3}}{{$x}}", hasError, ""},
// Check the parse fails for := rather than comma.