diff options
| author | Rob Pike <r@golang.org> | 2012-09-24 13:23:15 +1000 |
|---|---|---|
| committer | Rob Pike <r@golang.org> | 2012-09-24 13:23:15 +1000 |
| commit | 9050550c12e2d09cf8f0c22a270cfa90120cdf6d (patch) | |
| tree | 014cb848c742805de43d7d3f5293e830af2baef6 /src/pkg/text/template/parse/parse.go | |
| parent | edce6349639e321c3b1a34036a8fbc08ad363cd3 (diff) | |
| download | go-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.go | 149 |
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. |
