1
0
mirror of https://github.com/golang/go synced 2024-11-21 22:24:40 -07:00

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
This commit is contained in:
Rob Pike 2011-07-09 12:05:39 +10:00
parent bbf5eb5ba2
commit 58baf64827
2 changed files with 94 additions and 8 deletions

View 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)

View 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},