exp/template: parse variables and declarations
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/4631099
diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go
index 42279c2..5701218 100644
--- a/src/pkg/exp/template/exec.go
+++ b/src/pkg/exp/template/exec.go
@@ -82,14 +82,14 @@
switch n := n.(type) {
case *actionNode:
s.line = n.line
- s.printValue(n, s.evalPipeline(data, n.pipeline))
+ s.printValue(n, s.evalPipeline(data, n.pipe))
case *listNode:
for _, node := range n.nodes {
s.walk(data, node)
}
case *ifNode:
s.line = n.line
- s.walkIfOrWith(nodeIf, data, n.pipeline, n.list, n.elseList)
+ s.walkIfOrWith(nodeIf, data, n.pipe, n.list, n.elseList)
case *rangeNode:
s.line = n.line
s.walkRange(data, n)
@@ -102,7 +102,7 @@
s.walkTemplate(data, n)
case *withNode:
s.line = n.line
- s.walkIfOrWith(nodeWith, data, n.pipeline, n.list, n.elseList)
+ s.walkIfOrWith(nodeWith, data, n.pipe, n.list, n.elseList)
default:
s.errorf("unknown node: %s", n)
}
@@ -110,7 +110,7 @@
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
// are identical in behavior except that 'with' sets dot.
-func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe []*commandNode, list, elseList *listNode) {
+func (s *state) walkIfOrWith(typ nodeType, data reflect.Value, pipe *pipeNode, list, elseList *listNode) {
val := s.evalPipeline(data, pipe)
truth, ok := isTrue(val)
if !ok {
@@ -152,7 +152,7 @@
}
func (s *state) walkRange(data reflect.Value, r *rangeNode) {
- val := s.evalPipeline(data, r.pipeline)
+ val := s.evalPipeline(data, r.pipe)
down := s.down(data)
switch val.Kind() {
case reflect.Array, reflect.Slice:
@@ -188,7 +188,7 @@
if tmpl == nil {
s.errorf("template %q not in set", name)
}
- data = s.evalPipeline(data, t.pipeline)
+ data = s.evalPipeline(data, t.pipe)
newState := *s
newState.tmpl = tmpl
newState.walk(data, tmpl.root)
@@ -198,9 +198,9 @@
// values from the data structure by examining fields, calling methods, and so on.
// The printing of those values happens only through walk functions.
-func (s *state) evalPipeline(data reflect.Value, pipe []*commandNode) reflect.Value {
+func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) reflect.Value {
value := zero
- for _, cmd := range pipe {
+ for _, cmd := range pipe.cmds {
value = s.evalCommand(data, cmd, value) // previous value is this one's final arg.
// If the object has type interface{}, dig down one level to the thing inside.
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
diff --git a/src/pkg/exp/template/parse.go b/src/pkg/exp/template/parse.go
index 2b3cd17..906de23 100644
--- a/src/pkg/exp/template/parse.go
+++ b/src/pkg/exp/template/parse.go
@@ -21,35 +21,41 @@
root *listNode
funcs map[string]reflect.Value
// Parsing only; cleared after parse.
- set *Set
- lex *lexer
- token item // token lookahead for parser
- havePeek bool
+ set *Set
+ lex *lexer
+ token [2]item // two-token lookahead for parser
+ peekCount int
}
// next returns the next token.
func (t *Template) next() item {
- if t.havePeek {
- t.havePeek = false
+ if t.peekCount > 0 {
+ t.peekCount--
} else {
- t.token = t.lex.nextItem()
+ t.token[0] = t.lex.nextItem()
}
- return t.token
+ return t.token[t.peekCount]
}
// backup backs the input stream up one token.
func (t *Template) backup() {
- t.havePeek = true
+ t.peekCount++
+}
+
+// backup2 backs the input stream up two tokens
+func (t *Template) backup2(t1 item) {
+ t.token[1] = t1
+ t.peekCount = 2
}
// peek returns but does not consume the next token.
func (t *Template) peek() item {
- if t.havePeek {
- return t.token
+ if t.peekCount > 0 {
+ return t.token[t.peekCount-1]
}
- t.token = t.lex.nextItem()
- t.havePeek = true
- return t.token
+ t.peekCount = 1
+ t.token[0] = t.lex.nextItem()
+ return t.token[0]
}
// A node is an element in the parse tree. The interface is trivial.
@@ -76,9 +82,11 @@
nodeIf
nodeList
nodeNumber
+ nodePipe
nodeRange
nodeString
nodeTemplate
+ nodeVariable
nodeWith
)
@@ -122,23 +130,42 @@
return fmt.Sprintf("(text: %q)", t.text)
}
+// pipeNode holds a pipeline with optional declaration
+type pipeNode struct {
+ nodeType
+ line int
+ decl *variableNode
+ cmds []*commandNode
+}
+
+func newPipeline(line int, decl *variableNode) *pipeNode {
+ return &pipeNode{nodeType: nodePipe, line: line, decl: decl}
+}
+
+func (p *pipeNode) append(command *commandNode) {
+ p.cmds = append(p.cmds, command)
+}
+
+func (p *pipeNode) String() string {
+ if p.decl != nil {
+ return fmt.Sprintf("%s := %v", p.decl.ident, p.cmds)
+ }
+ return fmt.Sprintf("%v", p.cmds)
+}
+
// actionNode holds an action (something bounded by delimiters).
type actionNode struct {
nodeType
- line int
- pipeline []*commandNode
+ line int
+ pipe *pipeNode
}
-func newAction(line int, pipeline []*commandNode) *actionNode {
- return &actionNode{nodeType: nodeAction, line: line, pipeline: pipeline}
-}
-
-func (a *actionNode) append(command *commandNode) {
- a.pipeline = append(a.pipeline, command)
+func newAction(line int, pipe *pipeNode) *actionNode {
+ return &actionNode{nodeType: nodeAction, line: line, pipe: pipe}
}
func (a *actionNode) String() string {
- return fmt.Sprintf("(action: %v)", a.pipeline)
+ return fmt.Sprintf("(action: %v)", a.pipe)
}
// commandNode holds a command (a pipeline inside an evaluating action).
@@ -173,6 +200,20 @@
return fmt.Sprintf("I=%s", i.ident)
}
+// variableNode holds a variable.
+type variableNode struct {
+ nodeType
+ ident string
+}
+
+func newVariable(ident string) *variableNode {
+ return &variableNode{nodeType: nodeVariable, ident: ident}
+}
+
+func (v *variableNode) String() string {
+ return fmt.Sprintf("V=%s", v.ident)
+}
+
// dotNode holds the special identifier '.'. It is represented by a nil pointer.
type dotNode bool
@@ -370,80 +411,80 @@
return "{{else}}"
}
// ifNode represents an {{if}} action and its commands.
-// TODO: what should evaluation look like? is a pipeline enough?
+// TODO: what should evaluation look like? is a pipe enough?
type ifNode struct {
nodeType
line int
- pipeline []*commandNode
+ pipe *pipeNode
list *listNode
elseList *listNode
}
-func newIf(line int, pipeline []*commandNode, list, elseList *listNode) *ifNode {
- return &ifNode{nodeType: nodeIf, line: line, pipeline: pipeline, list: list, elseList: elseList}
+func newIf(line int, pipe *pipeNode, list, elseList *listNode) *ifNode {
+ return &ifNode{nodeType: nodeIf, line: line, pipe: pipe, list: list, elseList: elseList}
}
func (i *ifNode) String() string {
if i.elseList != nil {
- return fmt.Sprintf("({{if %s}} %s {{else}} %s)", i.pipeline, i.list, i.elseList)
+ return fmt.Sprintf("({{if %s}} %s {{else}} %s)", i.pipe, i.list, i.elseList)
}
- return fmt.Sprintf("({{if %s}} %s)", i.pipeline, i.list)
+ return fmt.Sprintf("({{if %s}} %s)", i.pipe, i.list)
}
// rangeNode represents a {{range}} action and its commands.
type rangeNode struct {
nodeType
line int
- pipeline []*commandNode
+ pipe *pipeNode
list *listNode
elseList *listNode
}
-func newRange(line int, pipeline []*commandNode, list, elseList *listNode) *rangeNode {
- return &rangeNode{nodeType: nodeRange, line: line, pipeline: pipeline, list: list, elseList: elseList}
+func newRange(line int, pipe *pipeNode, list, elseList *listNode) *rangeNode {
+ return &rangeNode{nodeType: nodeRange, line: line, pipe: pipe, list: list, elseList: elseList}
}
func (r *rangeNode) String() string {
if r.elseList != nil {
- return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.pipeline, r.list, r.elseList)
+ return fmt.Sprintf("({{range %s}} %s {{else}} %s)", r.pipe, r.list, r.elseList)
}
- return fmt.Sprintf("({{range %s}} %s)", r.pipeline, r.list)
+ return fmt.Sprintf("({{range %s}} %s)", r.pipe, r.list)
}
// templateNode represents a {{template}} action.
type templateNode struct {
nodeType
- line int
- name node
- pipeline []*commandNode
+ line int
+ name node
+ pipe *pipeNode
}
-func newTemplate(line int, name node, pipeline []*commandNode) *templateNode {
- return &templateNode{nodeType: nodeTemplate, line: line, name: name, pipeline: pipeline}
+func newTemplate(line int, name node, pipe *pipeNode) *templateNode {
+ return &templateNode{nodeType: nodeTemplate, line: line, name: name, pipe: pipe}
}
func (t *templateNode) String() string {
- return fmt.Sprintf("{{template %s %s}}", t.name, t.pipeline)
+ return fmt.Sprintf("{{template %s %s}}", t.name, t.pipe)
}
// withNode represents a {{with}} action and its commands.
type withNode struct {
nodeType
line int
- pipeline []*commandNode
+ pipe *pipeNode
list *listNode
elseList *listNode
}
-func newWith(line int, pipeline []*commandNode, list, elseList *listNode) *withNode {
- return &withNode{nodeType: nodeWith, line: line, pipeline: pipeline, list: list, elseList: elseList}
+func newWith(line int, pipe *pipeNode, list, elseList *listNode) *withNode {
+ return &withNode{nodeType: nodeWith, line: line, pipe: pipe, list: list, elseList: elseList}
}
func (w *withNode) String() string {
if w.elseList != nil {
- return fmt.Sprintf("({{with %s}} %s {{else}} %s)", w.pipeline, w.list, w.elseList)
+ return fmt.Sprintf("({{with %s}} %s {{else}} %s)", w.pipe, w.list, w.elseList)
}
- return fmt.Sprintf("({{with %s}} %s)", w.pipeline, w.list)
+ return fmt.Sprintf("({{with %s}} %s)", w.pipe, w.list)
}
@@ -631,17 +672,29 @@
// Pipeline:
// field or command
// pipeline "|" pipeline
-func (t *Template) pipeline(context string) (pipe []*commandNode) {
+func (t *Template) pipeline(context string) (pipe *pipeNode) {
+ var decl *variableNode
+ // Is there a declaration?
+ if v := t.peek(); v.typ == itemVariable {
+ t.next()
+ if ce := t.peek(); ce.typ == itemColonEquals {
+ t.next()
+ decl = newVariable(v.val)
+ } else {
+ t.backup2(v)
+ }
+ }
+ pipe = newPipeline(t.lex.lineNumber(), decl)
for {
switch token := t.next(); token.typ {
case itemRightDelim:
- if len(pipe) == 0 {
+ if len(pipe.cmds) == 0 {
t.errorf("missing value for %s", context)
}
return
- case itemBool, itemComplex, itemDot, itemField, itemIdentifier, itemNumber, itemRawString, itemString:
+ case itemBool, itemComplex, itemDot, itemField, itemIdentifier, itemVariable, itemNumber, itemRawString, itemString:
t.backup()
- pipe = append(pipe, t.command())
+ pipe.append(t.command())
default:
t.unexpected(token, context)
}
@@ -649,7 +702,7 @@
return
}
-func (t *Template) parseControl(context string) (lineNum int, pipe []*commandNode, list, elseList *listNode) {
+func (t *Template) parseControl(context string) (lineNum int, pipe *pipeNode, list, elseList *listNode) {
lineNum = t.lex.lineNumber()
pipe = t.pipeline(context)
var next node
@@ -732,8 +785,7 @@
default:
t.unexpected(token, "template invocation")
}
- pipeline := t.pipeline("template")
- return newTemplate(t.lex.lineNumber(), name, pipeline)
+ return newTemplate(t.lex.lineNumber(), name, t.pipeline("template"))
}
// command:
@@ -758,6 +810,8 @@
cmd.append(newIdentifier(token.val))
case itemDot:
cmd.append(newDot())
+ case itemVariable:
+ cmd.append(newVariable(token.val))
case itemField:
cmd.append(newField(token.val))
case itemBool:
diff --git a/src/pkg/exp/template/parse_test.go b/src/pkg/exp/template/parse_test.go
index 71580f8..7026795 100644
--- a/src/pkg/exp/template/parse_test.go
+++ b/src/pkg/exp/template/parse_test.go
@@ -146,10 +146,16 @@
`[(action: [(command: [F=[X]])])]`},
{"simple command", "{{printf}}", noError,
`[(action: [(command: [I=printf])])]`},
+ {"variable invocation", "{{$x 23}}", noError,
+ "[(action: [(command: [V=$x N=23])])]"},
{"multi-word command", "{{printf `%d` 23}}", noError,
"[(action: [(command: [I=printf S=`%d` N=23])])]"},
{"pipeline", "{{.X|.Y}}", noError,
`[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
+ {"pipeline with decl", "{{$x := .X|.Y}}", noError,
+ `[(action: $x := [(command: [F=[X]]) (command: [F=[Y]])])]`},
+ {"declaration", "{{.X|.Y}}", noError,
+ `[(action: [(command: [F=[X]]) (command: [F=[Y]])])]`},
{"simple if", "{{if .X}}hello{{end}}", noError,
`[({{if [(command: [F=[X]])]}} [(text: "hello")])]`},
{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,