From b8c664297bdfb7e953aedfff1e160fcd3062421d Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Fri, 8 Jul 2011 17:54:16 +1000 Subject: [PATCH] exp/template: parse variables and declarations R=golang-dev, adg CC=golang-dev https://golang.org/cl/4631099 --- src/pkg/exp/template/exec.go | 16 +-- src/pkg/exp/template/parse.go | 158 +++++++++++++++++++---------- src/pkg/exp/template/parse_test.go | 6 ++ 3 files changed, 120 insertions(+), 60 deletions(-) diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go index 42279c2e6b9..57012183cdf 100644 --- a/src/pkg/exp/template/exec.go +++ b/src/pkg/exp/template/exec.go @@ -82,14 +82,14 @@ func (s *state) walk(data reflect.Value, n node) { 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 @@ func (s *state) walk(data reflect.Value, n node) { 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 @@ func (s *state) walk(data reflect.Value, n node) { // 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 isTrue(val reflect.Value) (truth, ok bool) { } 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 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) { 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 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) { // 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 2b3cd17aba6..906de23c42c 100644 --- a/src/pkg/exp/template/parse.go +++ b/src/pkg/exp/template/parse.go @@ -21,35 +21,41 @@ type Template struct { 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 @@ const ( nodeIf nodeList nodeNumber + nodePipe nodeRange nodeString nodeTemplate + nodeVariable nodeWith ) @@ -122,23 +130,42 @@ func (t *textNode) String() string { 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 @@ func (i *identifierNode) String() string { 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 @@ func (e *elseNode) String() string { 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 @@ func (t *Template) action() (n node) { // 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 @@ func (t *Template) pipeline(context string) (pipe []*commandNode) { 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 @@ func (t *Template) templateControl() node { 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 @@ Loop: 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 71580f8b603..70267954e2f 100644 --- a/src/pkg/exp/template/parse_test.go +++ b/src/pkg/exp/template/parse_test.go @@ -146,10 +146,16 @@ var parseTests = []parseTest{ `[(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,