aboutsummaryrefslogtreecommitdiff
path: root/src/text/template/parse
diff options
context:
space:
mode:
authorDaniel Martí <mvdan@mvdan.cc>2017-12-17 13:23:40 +0000
committerDaniel Martí <mvdan@mvdan.cc>2018-04-04 15:51:56 +0000
commit28c1ad9d35f27b3b57afff4ee78faac746a8ed0a (patch)
tree53786ed3cf374d17122a1d1b3511ba2be11482cd /src/text/template/parse
parent804d03281c04096fca7f73dc33d1d62e09a86892 (diff)
downloadgo-28c1ad9d35f27b3b57afff4ee78faac746a8ed0a.tar.xz
text/template: add variable assignments
Variables can be declared and shadowing is supported, but modifying existing variables via assignments was not available. This meant that modifying a variable from a nested block was not possible: {{ $v := "init" }} {{ if true }} {{ $v := "changed" }} {{ end }} v: {{ $v }} {{/* "init" */}} Introduce the "=" assignment token, such that one can now do: {{ $v := "init" }} {{ if true }} {{ $v = "changed" }} {{ end }} v: {{ $v }} {{/* "changed" */}} To avoid confusion, rename PipeNode.Decl to PipeNode.Vars, as the variables may not always be declared after this change. Also change a few other names to better reflect the added ambiguity of variables in pipelines. Modifying the text/template/parse package in a backwards incompatible manner is acceptable, given that the package godoc clearly states that it isn't intended for general use. It's the equivalent of an internal package, back when internal packages didn't exist yet. To make the changes to the parse package sit well with the cmd/api test, update except.txt with the changes that we aren't worried about. Fixes #10608. Change-Id: I1f83a4297ee093fd45f9993cebb78fc9a9e81295 Reviewed-on: https://go-review.googlesource.com/84480 Run-TryBot: Daniel Martí <mvdan@mvdan.cc> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
Diffstat (limited to 'src/text/template/parse')
-rw-r--r--src/text/template/parse/lex.go7
-rw-r--r--src/text/template/parse/lex_test.go8
-rw-r--r--src/text/template/parse/node.go39
-rw-r--r--src/text/template/parse/parse.go24
-rw-r--r--src/text/template/parse/parse_test.go4
5 files changed, 47 insertions, 35 deletions
diff --git a/src/text/template/parse/lex.go b/src/text/template/parse/lex.go
index e112cb7714..fae8841fb1 100644
--- a/src/text/template/parse/lex.go
+++ b/src/text/template/parse/lex.go
@@ -42,7 +42,8 @@ const (
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 '.'
@@ -366,11 +367,13 @@ func lexInsideAction(l *lexer) stateFn {
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 == '"':
diff --git a/src/text/template/parse/lex_test.go b/src/text/template/parse/lex_test.go
index cb01cd98b6..6e7ece9db3 100644
--- a/src/text/template/parse/lex_test.go
+++ b/src/text/template/parse/lex_test.go
@@ -16,7 +16,7 @@ var itemName = map[itemType]string{
itemChar: "char",
itemCharConstant: "charconst",
itemComplex: "complex",
- itemColonEquals: ":=",
+ itemDeclare: ":=",
itemEOF: "EOF",
itemField: "field",
itemIdentifier: "identifier",
@@ -210,7 +210,7 @@ var lexTests = []lexTest{
tLeft,
mkItem(itemVariable, "$c"),
tSpace,
- mkItem(itemColonEquals, ":="),
+ mkItem(itemDeclare, ":="),
tSpace,
mkItem(itemIdentifier, "printf"),
tSpace,
@@ -262,7 +262,7 @@ var lexTests = []lexTest{
tLeft,
mkItem(itemVariable, "$v"),
tSpace,
- mkItem(itemColonEquals, ":="),
+ mkItem(itemDeclare, ":="),
tSpace,
mkItem(itemNumber, "3"),
tRight,
@@ -276,7 +276,7 @@ var lexTests = []lexTest{
tSpace,
mkItem(itemVariable, "$w"),
tSpace,
- mkItem(itemColonEquals, ":="),
+ mkItem(itemDeclare, ":="),
tSpace,
mkItem(itemNumber, "3"),
tRight,
diff --git a/src/text/template/parse/node.go b/src/text/template/parse/node.go
index 55ff46c17a..737172dfdd 100644
--- a/src/text/template/parse/node.go
+++ b/src/text/template/parse/node.go
@@ -145,13 +145,14 @@ type PipeNode struct {
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) {
@@ -160,8 +161,8 @@ 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 += ", "
}
@@ -186,11 +187,11 @@ func (p *PipeNode) CopyPipe() *PipeNode {
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))
}
@@ -317,20 +318,20 @@ func (i *IdentifierNode) Copy() Node {
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 {
@@ -341,12 +342,12 @@ func (v *VariableNode) String() string {
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 '.'.
diff --git a/src/text/template/parse/parse.go b/src/text/template/parse/parse.go
index a91a544ce0..34dc41c620 100644
--- a/src/text/template/parse/parse.go
+++ b/src/text/template/parse/parse.go
@@ -383,10 +383,11 @@ func (t *Tree) action() (n Node) {
// 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()
@@ -395,26 +396,33 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
// 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:
diff --git a/src/text/template/parse/parse_test.go b/src/text/template/parse/parse_test.go
index 81f14aca98..c1f80c1326 100644
--- a/src/text/template/parse/parse_test.go
+++ b/src/text/template/parse/parse_test.go
@@ -259,9 +259,9 @@ var parseTests = []parseTest{
{"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.