]> Cypherpunks repositories - gostls13.git/commitdiff
exp/template: variable evaluation.
authorRob Pike <r@golang.org>
Sat, 9 Jul 2011 02:05:39 +0000 (12:05 +1000)
committerRob Pike <r@golang.org>
Sat, 9 Jul 2011 02:05:39 +0000 (12:05 +1000)
Still need to do static checking of declarations during parse.

R=golang-dev, dsymonds, r
CC=golang-dev
https://golang.org/cl/4667070

src/pkg/exp/template/exec.go
src/pkg/exp/template/exec_test.go

index 26ec7f438c1ae60ff2e859e7eaf86f0eac224e83..15c382147a04d95e1b36e34c1c4b3b6814896357 100644 (file)
@@ -21,7 +21,45 @@ type state struct {
        tmpl *Template
        wr   io.Writer
        set  *Set
-       line int // line number for errors
+       line int        // line number for errors
+       vars []variable // push-down stack of variable values.
+}
+
+// variable holds the dynamic value of a variable such as $, $x etc.
+type variable struct {
+       name  string
+       value reflect.Value
+}
+
+// push pushes a new variable on the stack.
+func (s *state) push(name string, value reflect.Value) {
+       s.vars = append(s.vars, variable{name, value})
+}
+
+// mark returns the length of the variable stack.
+func (s *state) mark() int {
+       return len(s.vars)
+}
+
+// pop pops the variable stack up to the mark.
+func (s *state) pop(mark int) {
+       s.vars = s.vars[0:mark]
+}
+
+// setTop overwrites the top variable on the stack. Used by range iterations.
+func (s *state) setTop(value reflect.Value) {
+       s.vars[len(s.vars)-1].value = value
+}
+
+// value returns the value of the named variable.
+func (s *state) value(name string) reflect.Value {
+       for i := s.mark() - 1; i >= 0; i-- {
+               if s.vars[i].name == name {
+                       return s.vars[i].value
+               }
+       }
+       s.errorf("undefined variable: %s", name)
+       return zero
 }
 
 var zero reflect.Value
@@ -48,16 +86,21 @@ func (t *Template) Execute(wr io.Writer, data interface{}) os.Error {
 // from the specified set.
 func (t *Template) ExecuteInSet(wr io.Writer, data interface{}, set *Set) (err os.Error) {
        defer t.recover(&err)
+       value := reflect.ValueOf(data)
        state := &state{
                tmpl: t,
                wr:   wr,
                set:  set,
                line: 1,
+               vars: []variable{{"$", value}},
        }
        if t.root == nil {
                state.errorf("must be parsed before execution")
        }
-       state.walk(reflect.ValueOf(data), t.root)
+       state.walk(value, t.root)
+       if state.mark() != 1 {
+               t.errorf("internal error: variable stack at %d", state.mark())
+       }
        return
 }
 
@@ -67,6 +110,7 @@ func (s *state) walk(data reflect.Value, n node) {
        switch n := n.(type) {
        case *actionNode:
                s.line = n.line
+               defer s.pop(s.mark())
                s.printValue(n, s.evalPipeline(data, n.pipe))
        case *listNode:
                for _, node := range n.nodes {
@@ -96,6 +140,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 *pipeNode, list, elseList *listNode) {
+       defer s.pop(s.mark())
        val := s.evalPipeline(data, pipe)
        truth, ok := isTrue(val)
        if !ok {
@@ -137,14 +182,20 @@ func isTrue(val reflect.Value) (truth, ok bool) {
 }
 
 func (s *state) walkRange(data reflect.Value, r *rangeNode) {
-       val := s.evalPipeline(data, r.pipe)
+       defer s.pop(s.mark())
+       val, _ := indirect(s.evalPipeline(data, r.pipe))
        switch val.Kind() {
        case reflect.Array, reflect.Slice:
                if val.Len() == 0 {
                        break
                }
                for i := 0; i < val.Len(); i++ {
-                       s.walk(val.Index(i), r.list)
+                       elem := val.Index(i)
+                       // Set $x to the element rather than the slice.
+                       if r.pipe.decl != nil {
+                               s.setTop(elem)
+                       }
+                       s.walk(elem, r.list)
                }
                return
        case reflect.Map:
@@ -152,7 +203,12 @@ func (s *state) walkRange(data reflect.Value, r *rangeNode) {
                        break
                }
                for _, key := range val.MapKeys() {
-                       s.walk(val.MapIndex(key), r.list)
+                       elem := val.MapIndex(key)
+                       // Set $x to the key rather than the map.
+                       if r.pipe.decl != nil {
+                               s.setTop(elem)
+                       }
+                       s.walk(elem, r.list)
                }
                return
        default:
@@ -172,9 +228,12 @@ func (s *state) walkTemplate(data reflect.Value, t *templateNode) {
        if tmpl == nil {
                s.errorf("template %q not in set", name)
        }
+       defer s.pop(s.mark())
        data = s.evalPipeline(data, t.pipe)
        newState := *s
        newState.tmpl = tmpl
+       // No dynamic scoping: template invocations inherit no variables.
+       newState.vars = []variable{{"$", data}}
        newState.walk(data, tmpl.root)
 }
 
@@ -182,6 +241,10 @@ 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.
 
+// evalPipeline returns the value acquired by evaluating a pipeline. If the
+// pipeline has a variable declaration, the variable will be pushed on the
+// stack. Callers should therefore pop the stack after they are finished
+// executing commands depending on the pipeline value.
 func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) reflect.Value {
        value := zero
        for _, cmd := range pipe.cmds {
@@ -191,9 +254,18 @@ func (s *state) evalPipeline(data reflect.Value, pipe *pipeNode) reflect.Value {
                        value = reflect.ValueOf(value.Interface()) // lovely!
                }
        }
+       if pipe.decl != nil {
+               s.push(pipe.decl.ident, value)
+       }
        return value
 }
 
+func (s *state) notAFunction(args []node, final reflect.Value) {
+       if len(args) > 1 || final.IsValid() {
+               s.errorf("can't give argument to non-function %s", args[0])
+       }
+}
+
 func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.Value) reflect.Value {
        firstWord := cmd.args[0]
        switch n := firstWord.(type) {
@@ -202,10 +274,10 @@ func (s *state) evalCommand(data reflect.Value, cmd *commandNode, final reflect.
        case *identifierNode:
                // Must be a function.
                return s.evalFunction(data, n.ident, cmd.args, final)
+       case *variableNode:
+               return s.evalVariable(data, n.ident, cmd.args, final)
        }
-       if len(cmd.args) > 1 || final.IsValid() {
-               s.errorf("can't give argument to non-function %s", cmd.args[0])
-       }
+       s.notAFunction(cmd.args, final)
        switch word := cmd.args[0].(type) {
        case *dotNode:
                return data
@@ -250,6 +322,11 @@ func (s *state) evalFunction(data reflect.Value, name string, args []node, final
        return s.evalCall(data, function, name, false, args, final)
 }
 
+func (s *state) evalVariable(data reflect.Value, name string, args []node, final reflect.Value) reflect.Value {
+       s.notAFunction(args, final) // Can't invoke function-valued variables - too confusing.
+       return s.value(name)
+}
+
 // Is this an exported - upper case - name?
 func isExported(name string) bool {
        rune, _ := utf8.DecodeRuneInString(name)
index fbffa12e65fd81564e5083faafce9c6849dc8e83..5b9b469dd7a5b658f21056ec703411b7d696e68a 100644 (file)
@@ -155,6 +155,15 @@ var execTests = []execTest{
                b string
        }{7, "seven"}, true},
 
+       // Variables.
+       {"$ int", "{{$}}", "123", 123, true},
+       {"with $x int", "{{with $x := .I}}{{$x}}{{end}}", "17", tVal, true},
+       {"range $x SI", "{{range $x := .SI}}<{{$x}}>{{end}}", "<3><4><5>", tVal, true},
+       {"range $x PSI", "{{range $x := .PSI}}<{{$x}}>{{end}}", "<21><22><23>", tVal, true},
+       {"after range $x", "{{range $x := .SI}}{{end}}{{$x}}", "", tVal, false},
+       {"if $x with $y int", "{{if $x := true}}{{with $y := .I}}{{$x}},{{$y}}{{end}}{{end}}", "true,17", tVal, true},
+       {"if $x with $x int", "{{if $x := true}}{{with $x := .I}}{{$x}},{{end}}{{$x}}{{end}}", "17,true", tVal, true},
+
        // Pointers.
        {"*int", "{{.PI}}", "23", tVal, true},
        {"*[]int", "{{.PSI}}", "[21 22 23]", tVal, true},