From 58baf648277730f57debc3be521652d30770ab34 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Sat, 9 Jul 2011 12:05:39 +1000 Subject: [PATCH] exp/template: variable evaluation. 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 | 93 ++++++++++++++++++++++++++++--- src/pkg/exp/template/exec_test.go | 9 +++ 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/src/pkg/exp/template/exec.go b/src/pkg/exp/template/exec.go index 26ec7f438c..15c382147a 100644 --- a/src/pkg/exp/template/exec.go +++ b/src/pkg/exp/template/exec.go @@ -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) diff --git a/src/pkg/exp/template/exec_test.go b/src/pkg/exp/template/exec_test.go index fbffa12e65..5b9b469dd7 100644 --- a/src/pkg/exp/template/exec_test.go +++ b/src/pkg/exp/template/exec_test.go @@ -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}, -- 2.50.0