{"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},
lex *lexer
token [2]item // two-token lookahead for parser
peekCount int
+ vars []string // variables defined at the moment
}
// Name returns the name of the template.
return t.name
}
+// popVars trims the variable list to the specified length
+func (t *Template) popVars(n int) {
+ t.vars = t.vars[:n]
+}
+
// next returns the next token.
func (t *Template) next() item {
if t.peekCount > 0 {
t.root = nil
t.set = set
t.lex = lex
+ t.vars = []string{"$"}
}
// stopParse terminates parsing.
func (t *Template) stopParse() {
t.set, t.lex = nil, nil
+ t.vars = nil
}
// atEOF returns true if, possibly after spaces, we're at EOF.
t.startParse(set, lex(t.name, s))
defer t.recover(&err)
t.parse(true)
+ if len(t.vars) != 1 { // $ should still be defined
+ t.errorf("internal error: vars not popped")
+ }
t.stopParse()
return
}
return t.withControl()
}
t.backup()
+ defer t.popVars(len(t.vars))
return newAction(t.lex.lineNumber(), t.pipeline("command"))
}
if ce := t.peek(); ce.typ == itemColonEquals {
t.next()
decl = newVariable(v.val)
+ t.vars = append(t.vars, v.val)
} else {
t.backup2(v)
}
func (t *Template) parseControl(context string) (lineNum int, pipe *pipeNode, list, elseList *listNode) {
lineNum = t.lex.lineNumber()
+ defer t.popVars(len(t.vars))
pipe = t.pipeline(context)
var next node
list, next = t.itemList(false)
var pipe *pipeNode
if t.next().typ != itemRightDelim {
t.backup()
+ defer t.popVars(len(t.vars))
pipe = t.pipeline("template")
}
return newTemplate(t.lex.lineNumber(), name, pipe)
case itemDot:
cmd.append(newDot())
case itemVariable:
+ found := false
+ for _, varName := range t.vars {
+ if varName == token.val {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.errorf("undefined variable %q", token.val)
+ }
cmd.append(newVariable(token.val))
case itemField:
cmd.append(newField(token.val))
`[(action: [(command: [F=[X]])])]`},
{"simple command", "{{printf}}", noError,
`[(action: [(command: [I=printf])])]`},
- {"variable invocation", "{{$x 23}}", noError,
- "[(action: [(command: [V=$x N=23])])]"},
+ {"$ invocation", "{{$}}", noError,
+ "[(action: [(command: [V=$])])]"},
+ {"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
+ "[({{with $x := [(command: [N=3])]}} [(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,
`[({{with [(command: [F=[X]])]}} [(text: "hello")] {{else}} [(text: "goodbye")])]`},
// Errors.
{"unclosed action", "hello{{range", hasError, ""},
+ {"unmatched end", "{{end}}", hasError, ""},
{"missing end", "hello{{range .x}}", hasError, ""},
{"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
{"undefined function", "hello{{undefined}}", hasError, ""},
+ {"undefined variable", "{{$x}}", hasError, ""},
+ {"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""},
}
func TestParse(t *testing.T) {