]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: parse variables and declarations
authorRob Pike <r@golang.org>
Fri, 8 Jul 2011 07:54:16 +0000 (17:54 +1000)
committerRob Pike <r@golang.org>
Fri, 8 Jul 2011 07:54:16 +0000 (17:54 +1000)
R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/4631099

src/pkg/exp/template/exec.go
src/pkg/exp/template/parse.go
src/pkg/exp/template/parse_test.go

index 42279c2e6b9861265f6f4da6975417a5b6b8cb78..57012183cdf7b6247aba86a1ee5f6dfbabd075e1 100644 (file)
@@ -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 {
index 2b3cd17aba6b5739d555a0b8f72fb63c5d151143..906de23c42cefb1f81a3b6ab39a926e32f1888d9 100644 (file)
@@ -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)
 }
 
-// actionNode holds an action (something bounded by delimiters).
-type actionNode struct {
+// pipeNode holds a pipeline with optional declaration
+type pipeNode struct {
        nodeType
-       line     int
-       pipeline []*commandNode
+       line int
+       decl *variableNode
+       cmds []*commandNode
+}
+
+func newPipeline(line int, decl *variableNode) *pipeNode {
+       return &pipeNode{nodeType: nodePipe, line: line, decl: decl}
 }
 
-func newAction(line int, pipeline []*commandNode) *actionNode {
-       return &actionNode{nodeType: nodeAction, line: line, pipeline: pipeline}
+func (p *pipeNode) append(command *commandNode) {
+       p.cmds = append(p.cmds, command)
 }
 
-func (a *actionNode) append(command *commandNode) {
-       a.pipeline = append(a.pipeline, 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
+       pipe *pipeNode
+}
+
+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:
index 71580f8b60344321d6883a568776a01b45deb137..70267954e2f85925d731cf438f82e9c4bf88a2a1 100644 (file)
@@ -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,