aboutsummaryrefslogtreecommitdiff
path: root/src/pkg/text/template/parse/parse.go
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2012-09-24 13:23:15 +1000
committerRob Pike <r@golang.org>2012-09-24 13:23:15 +1000
commit9050550c12e2d09cf8f0c22a270cfa90120cdf6d (patch)
tree014cb848c742805de43d7d3f5293e830af2baef6 /src/pkg/text/template/parse/parse.go
parentedce6349639e321c3b1a34036a8fbc08ad363cd3 (diff)
downloadgo-9050550c12e2d09cf8f0c22a270cfa90120cdf6d.tar.xz
text/template: allow .Field access to parenthesized expressions
Change the grammar so that field access is a proper operator. This introduces a new node, ChainNode, into the public (but actually internal) API of text/template/parse. For compatibility, we only use the new node type for the specific construct, which was not parseable before. Therefore this should be backward-compatible. Before, .X.Y was a token in the lexer; this CL breaks it out into .Y applied to .X. But for compatibility we mush them back together before delivering. One day we might remove that hack; it's the simple TODO in parse.go/operand. This change also provides grammatical distinction between f and (f) which might permit function values later, but not now. Fixes #3999. R=golang-dev, dsymonds, gri, rsc, mikesamuel CC=golang-dev https://golang.org/cl/6494119
Diffstat (limited to 'src/pkg/text/template/parse/parse.go')
-rw-r--r--src/pkg/text/template/parse/parse.go149
1 files changed, 94 insertions, 55 deletions
diff --git a/src/pkg/text/template/parse/parse.go b/src/pkg/text/template/parse/parse.go
index c52e41d166..9e2af12ad7 100644
--- a/src/pkg/text/template/parse/parse.go
+++ b/src/pkg/text/template/parse/parse.go
@@ -353,8 +353,7 @@ func (t *Tree) action() (n Node) {
}
// Pipeline:
-// field or command
-// pipeline "|" pipeline
+// declarations? command ('|' command)*
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
var decl []*VariableNode
// Are there declarations?
@@ -369,9 +368,6 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
t.nextNonSpace()
variable := newVariable(v.val)
- if len(variable.Ident) != 1 {
- t.errorf("illegal variable in declaration: %s", v.val)
- }
decl = append(decl, variable)
t.vars = append(t.vars, v.val)
if next.typ == itemChar && next.val == "," {
@@ -400,7 +396,7 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) {
}
return
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
- itemNumber, itemNil, itemRawString, itemString, itemVariable:
+ itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
t.backup()
pipe.append(t.command())
default:
@@ -494,57 +490,29 @@ func (t *Tree) templateControl() Node {
}
// command:
+// operand (space operand)*
// space-separated arguments up to a pipeline character or right delimiter.
// we consume the pipe character but leave the right delim to terminate the action.
func (t *Tree) command() *CommandNode {
cmd := newCommand()
-Loop:
for {
- switch token := t.nextNonSpace(); token.typ {
+ t.peekNonSpace() // skip leading spaces.
+ operand := t.operand()
+ if operand != nil {
+ cmd.append(operand)
+ }
+ switch token := t.next(); token.typ {
+ case itemSpace:
+ continue
+ case itemError:
+ t.errorf("%s", token.val)
case itemRightDelim, itemRightParen:
t.backup()
- break Loop
case itemPipe:
- break Loop
- case itemLeftParen:
- p := t.pipeline("parenthesized expression")
- if t.nextNonSpace().typ != itemRightParen {
- t.errorf("missing right paren in parenthesized expression")
- }
- cmd.append(p)
- case itemError:
- t.errorf("%s", token.val)
- case itemIdentifier:
- if !t.hasFunction(token.val) {
- t.errorf("function %q not defined", token.val)
- }
- cmd.append(NewIdentifier(token.val))
- case itemDot:
- cmd.append(newDot())
- case itemNil:
- cmd.append(newNil())
- case itemVariable:
- cmd.append(t.useVar(token.val))
- case itemField:
- cmd.append(newField(token.val))
- case itemBool:
- cmd.append(newBool(token.val == "true"))
- case itemCharConstant, itemComplex, itemNumber:
- number, err := newNumber(token.val, token.typ)
- if err != nil {
- t.error(err)
- }
- cmd.append(number)
- case itemString, itemRawString:
- s, err := strconv.Unquote(token.val)
- if err != nil {
- t.error(err)
- }
- cmd.append(newString(token.val, s))
default:
- t.unexpected(token, "command")
+ t.errorf("unexpected %s in operand; missing space?", token)
}
- t.terminate()
+ break
}
if len(cmd.Args) == 0 {
t.errorf("empty command")
@@ -552,15 +520,86 @@ Loop:
return cmd
}
-// terminate checks that the next token terminates an argument. This guarantees
-// that arguments are space-separated, for example that (2)3 does not parse.
-func (t *Tree) terminate() {
- token := t.peek()
- switch token.typ {
- case itemChar, itemPipe, itemRightDelim, itemRightParen, itemSpace:
- return
+// operand:
+// term .Field*
+// An operand is a space-separated component of a command,
+// a term possibly followed by field accesses.
+// A nil return means the next item is not an operand.
+func (t *Tree) operand() Node {
+ node := t.term()
+ if node == nil {
+ return nil
+ }
+ if t.peek().typ == itemField {
+ chain := newChain(node)
+ for t.peek().typ == itemField {
+ chain.Add(t.next().val)
+ }
+ // Compatibility with original API: If the term is of type NodeField
+ // or NodeVariable, just put more fields on the original.
+ // Otherwise, keep the Chain node.
+ // TODO: Switch to Chains always when we can.
+ switch node.Type() {
+ case NodeField:
+ node = newField(chain.String())
+ case NodeVariable:
+ node = newVariable(chain.String())
+ default:
+ node = chain
+ }
}
- t.unexpected(token, "argument list (missing space?)")
+ return node
+}
+
+// term:
+// literal (number, string, nil, boolean)
+// function (identifier)
+// .
+// .Field
+// $
+// '(' pipeline ')'
+// A term is a simple "expression".
+// A nil return means the next item is not a term.
+func (t *Tree) term() Node {
+ switch token := t.nextNonSpace(); token.typ {
+ case itemError:
+ t.errorf("%s", token.val)
+ case itemIdentifier:
+ if !t.hasFunction(token.val) {
+ t.errorf("function %q not defined", token.val)
+ }
+ return NewIdentifier(token.val)
+ case itemDot:
+ return newDot()
+ case itemNil:
+ return newNil()
+ case itemVariable:
+ return t.useVar(token.val)
+ case itemField:
+ return newField(token.val)
+ case itemBool:
+ return newBool(token.val == "true")
+ case itemCharConstant, itemComplex, itemNumber:
+ number, err := newNumber(token.val, token.typ)
+ if err != nil {
+ t.error(err)
+ }
+ return number
+ case itemLeftParen:
+ pipe := t.pipeline("parenthesized pipeline")
+ if token := t.next(); token.typ != itemRightParen {
+ t.errorf("unclosed right paren: unexpected %s", token)
+ }
+ return pipe
+ case itemString, itemRawString:
+ s, err := strconv.Unquote(token.val)
+ if err != nil {
+ t.error(err)
+ }
+ return newString(token.val, s)
+ }
+ t.backup()
+ return nil
}
// hasFunction reports if a function name exists in the Tree's maps.