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
// 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
}
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 {
// 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 {
}
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:
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:
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)
}
// 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 {
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) {
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
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)
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},