2011-09-16 15:47:21 +00:00
|
|
|
// Copyright 2011 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
// Package parse builds parse trees for templates. The grammar is defined
|
|
|
|
// in the documents for the template package.
|
|
|
|
package parse
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"runtime"
|
|
|
|
"strconv"
|
|
|
|
"unicode"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Tree is the representation of a parsed template.
|
|
|
|
type Tree struct {
|
|
|
|
Name string // Name is the name of the template.
|
|
|
|
Root *ListNode // Root is the top-level root of the parse tree.
|
|
|
|
// Parsing only; cleared after parse.
|
|
|
|
funcs []map[string]interface{}
|
|
|
|
lex *lexer
|
|
|
|
token [2]item // two-token lookahead for parser.
|
|
|
|
peekCount int
|
|
|
|
vars []string // variables defined at the moment.
|
|
|
|
}
|
|
|
|
|
|
|
|
// next returns the next token.
|
|
|
|
func (t *Tree) next() item {
|
|
|
|
if t.peekCount > 0 {
|
|
|
|
t.peekCount--
|
|
|
|
} else {
|
|
|
|
t.token[0] = t.lex.nextItem()
|
|
|
|
}
|
|
|
|
return t.token[t.peekCount]
|
|
|
|
}
|
|
|
|
|
|
|
|
// backup backs the input stream up one token.
|
|
|
|
func (t *Tree) backup() {
|
|
|
|
t.peekCount++
|
|
|
|
}
|
|
|
|
|
|
|
|
// backup2 backs the input stream up two tokens
|
|
|
|
func (t *Tree) backup2(t1 item) {
|
|
|
|
t.token[1] = t1
|
|
|
|
t.peekCount = 2
|
|
|
|
}
|
|
|
|
|
|
|
|
// peek returns but does not consume the next token.
|
|
|
|
func (t *Tree) peek() item {
|
|
|
|
if t.peekCount > 0 {
|
|
|
|
return t.token[t.peekCount-1]
|
|
|
|
}
|
|
|
|
t.peekCount = 1
|
|
|
|
t.token[0] = t.lex.nextItem()
|
|
|
|
return t.token[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parsing.
|
|
|
|
|
|
|
|
// New allocates a new template with the given name.
|
|
|
|
func New(name string, funcs ...map[string]interface{}) *Tree {
|
|
|
|
return &Tree{
|
|
|
|
Name: name,
|
|
|
|
funcs: funcs,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// errorf formats the error and terminates processing.
|
|
|
|
func (t *Tree) errorf(format string, args ...interface{}) {
|
|
|
|
t.Root = nil
|
|
|
|
format = fmt.Sprintf("template: %s:%d: %s", t.Name, t.lex.lineNumber(), format)
|
|
|
|
panic(fmt.Errorf(format, args...))
|
|
|
|
}
|
|
|
|
|
|
|
|
// error terminates processing.
|
2011-12-03 02:17:34 +00:00
|
|
|
func (t *Tree) error(err error) {
|
2011-09-16 15:47:21 +00:00
|
|
|
t.errorf("%s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// expect consumes the next token and guarantees it has the required type.
|
|
|
|
func (t *Tree) expect(expected itemType, context string) item {
|
|
|
|
token := t.next()
|
|
|
|
if token.typ != expected {
|
|
|
|
t.errorf("expected %s in %s; got %s", expected, context, token)
|
|
|
|
}
|
|
|
|
return token
|
|
|
|
}
|
|
|
|
|
|
|
|
// unexpected complains about the token and terminates processing.
|
|
|
|
func (t *Tree) unexpected(token item, context string) {
|
|
|
|
t.errorf("unexpected %s in %s", token, context)
|
|
|
|
}
|
|
|
|
|
|
|
|
// recover is the handler that turns panics into returns from the top level of Parse.
|
2011-12-03 02:17:34 +00:00
|
|
|
func (t *Tree) recover(errp *error) {
|
2011-09-16 15:47:21 +00:00
|
|
|
e := recover()
|
|
|
|
if e != nil {
|
|
|
|
if _, ok := e.(runtime.Error); ok {
|
|
|
|
panic(e)
|
|
|
|
}
|
|
|
|
if t != nil {
|
|
|
|
t.stopParse()
|
|
|
|
}
|
2011-12-03 02:17:34 +00:00
|
|
|
*errp = e.(error)
|
2011-09-16 15:47:21 +00:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// startParse starts the template parsing from the lexer.
|
|
|
|
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
|
|
|
|
t.Root = nil
|
|
|
|
t.lex = lex
|
|
|
|
t.vars = []string{"$"}
|
|
|
|
t.funcs = funcs
|
|
|
|
}
|
|
|
|
|
|
|
|
// stopParse terminates parsing.
|
|
|
|
func (t *Tree) stopParse() {
|
|
|
|
t.lex = nil
|
|
|
|
t.vars = nil
|
|
|
|
t.funcs = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// atEOF returns true if, possibly after spaces, we're at EOF.
|
|
|
|
func (t *Tree) atEOF() bool {
|
|
|
|
for {
|
|
|
|
token := t.peek()
|
|
|
|
switch token.typ {
|
|
|
|
case itemEOF:
|
|
|
|
return true
|
|
|
|
case itemText:
|
|
|
|
for _, r := range token.val {
|
|
|
|
if !unicode.IsSpace(r) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t.next() // skip spaces.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse parses the template definition string to construct an internal
|
2011-10-26 23:57:58 +00:00
|
|
|
// representation of the template for execution. If either action delimiter
|
|
|
|
// string is empty, the default ("{{" or "}}") is used.
|
2011-12-12 23:40:51 +00:00
|
|
|
func (t *Tree) Parse(s, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
2011-09-16 15:47:21 +00:00
|
|
|
defer t.recover(&err)
|
2011-10-26 23:57:58 +00:00
|
|
|
t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim))
|
2011-12-12 23:40:51 +00:00
|
|
|
t.parse(treeSet)
|
2011-09-16 15:47:21 +00:00
|
|
|
t.stopParse()
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
2011-12-12 23:40:51 +00:00
|
|
|
// parse is the top-level parser for a template, essentially the same
|
|
|
|
// as itemList except it also parses {{define}} actions.
|
|
|
|
// It runs to EOF.
|
|
|
|
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
|
|
|
t.Root = newList()
|
|
|
|
for t.peek().typ != itemEOF {
|
|
|
|
if t.peek().typ == itemLeftDelim {
|
|
|
|
delim := t.next()
|
|
|
|
if t.next().typ == itemDefine {
|
|
|
|
newT := New("new definition") // name will be updated once we know it.
|
|
|
|
newT.startParse(t.funcs, t.lex)
|
|
|
|
newT.parseDefinition(treeSet)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
t.backup2(delim)
|
|
|
|
}
|
|
|
|
n := t.textOrAction()
|
|
|
|
if n.Type() == nodeEnd {
|
|
|
|
t.errorf("unexpected %s", n)
|
|
|
|
}
|
|
|
|
t.Root.append(n)
|
2011-09-16 15:47:21 +00:00
|
|
|
}
|
2011-12-12 23:40:51 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
|
|
|
// installs the definition in the treeSet map. The "define" keyword has already
|
|
|
|
// been scanned.
|
|
|
|
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
|
|
|
if treeSet == nil {
|
|
|
|
t.errorf("no set specified for template definition")
|
|
|
|
}
|
|
|
|
const context = "define clause"
|
|
|
|
name := t.expect(itemString, context)
|
|
|
|
var err error
|
|
|
|
t.Name, err = strconv.Unquote(name.val)
|
|
|
|
if err != nil {
|
|
|
|
t.error(err)
|
|
|
|
}
|
|
|
|
t.expect(itemRightDelim, context)
|
|
|
|
var end Node
|
|
|
|
t.Root, end = t.itemList()
|
|
|
|
if end.Type() != nodeEnd {
|
|
|
|
t.errorf("unexpected %s in %s", end, context)
|
|
|
|
}
|
|
|
|
t.stopParse()
|
|
|
|
if _, present := treeSet[t.Name]; present {
|
|
|
|
t.errorf("template: %q multiply defined", name)
|
|
|
|
}
|
|
|
|
treeSet[t.Name] = t
|
2011-09-16 15:47:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// itemList:
|
|
|
|
// textOrAction*
|
2011-12-12 23:40:51 +00:00
|
|
|
// Terminates at {{end}} or {{else}}, returned separately.
|
|
|
|
func (t *Tree) itemList() (list *ListNode, next Node) {
|
2011-09-16 15:47:21 +00:00
|
|
|
list = newList()
|
|
|
|
for t.peek().typ != itemEOF {
|
|
|
|
n := t.textOrAction()
|
|
|
|
switch n.Type() {
|
|
|
|
case nodeEnd, nodeElse:
|
|
|
|
return list, n
|
|
|
|
}
|
|
|
|
list.append(n)
|
|
|
|
}
|
2011-12-12 23:40:51 +00:00
|
|
|
t.errorf("unexpected EOF")
|
|
|
|
return
|
2011-09-16 15:47:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// textOrAction:
|
|
|
|
// text | action
|
|
|
|
func (t *Tree) textOrAction() Node {
|
|
|
|
switch token := t.next(); token.typ {
|
|
|
|
case itemText:
|
|
|
|
return newText(token.val)
|
|
|
|
case itemLeftDelim:
|
|
|
|
return t.action()
|
|
|
|
default:
|
|
|
|
t.unexpected(token, "input")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Action:
|
|
|
|
// control
|
|
|
|
// command ("|" command)*
|
|
|
|
// Left delim is past. Now get actions.
|
|
|
|
// First word could be a keyword such as range.
|
|
|
|
func (t *Tree) action() (n Node) {
|
|
|
|
switch token := t.next(); token.typ {
|
|
|
|
case itemElse:
|
|
|
|
return t.elseControl()
|
|
|
|
case itemEnd:
|
|
|
|
return t.endControl()
|
|
|
|
case itemIf:
|
|
|
|
return t.ifControl()
|
|
|
|
case itemRange:
|
|
|
|
return t.rangeControl()
|
|
|
|
case itemTemplate:
|
|
|
|
return t.templateControl()
|
|
|
|
case itemWith:
|
|
|
|
return t.withControl()
|
|
|
|
}
|
|
|
|
t.backup()
|
|
|
|
// Do not pop variables; they persist until "end".
|
|
|
|
return newAction(t.lex.lineNumber(), t.pipeline("command"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pipeline:
|
|
|
|
// field or command
|
|
|
|
// pipeline "|" pipeline
|
|
|
|
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
|
|
|
var decl []*VariableNode
|
|
|
|
// Are there declarations?
|
|
|
|
for {
|
|
|
|
if v := t.peek(); v.typ == itemVariable {
|
|
|
|
t.next()
|
|
|
|
if next := t.peek(); next.typ == itemColonEquals || next.typ == itemChar {
|
|
|
|
t.next()
|
|
|
|
variable := newVariable(v.val)
|
|
|
|
if len(variable.Ident) != 1 {
|
|
|
|
t.errorf("illegal variable in declaration: %s", v.val)
|
|
|
|
}
|
|
|
|
decl = append(decl, variable)
|
|
|
|
t.vars = append(t.vars, v.val)
|
|
|
|
if next.typ == itemChar && next.val == "," {
|
|
|
|
if context == "range" && len(decl) < 2 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
t.errorf("too many declarations in %s", context)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
t.backup2(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
pipe = newPipeline(t.lex.lineNumber(), decl)
|
|
|
|
for {
|
|
|
|
switch token := t.next(); token.typ {
|
|
|
|
case itemRightDelim:
|
|
|
|
if len(pipe.Cmds) == 0 {
|
|
|
|
t.errorf("missing value for %s", context)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
|
|
|
itemVariable, itemNumber, itemRawString, itemString:
|
|
|
|
t.backup()
|
|
|
|
pipe.append(t.command())
|
|
|
|
default:
|
|
|
|
t.unexpected(token, context)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Tree) 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
|
2011-12-12 23:40:51 +00:00
|
|
|
list, next = t.itemList()
|
2011-09-16 15:47:21 +00:00
|
|
|
switch next.Type() {
|
|
|
|
case nodeEnd: //done
|
|
|
|
case nodeElse:
|
2011-12-12 23:40:51 +00:00
|
|
|
elseList, next = t.itemList()
|
2011-09-16 15:47:21 +00:00
|
|
|
if next.Type() != nodeEnd {
|
|
|
|
t.errorf("expected end; found %s", next)
|
|
|
|
}
|
|
|
|
elseList = elseList
|
|
|
|
}
|
|
|
|
return lineNum, pipe, list, elseList
|
|
|
|
}
|
|
|
|
|
|
|
|
// If:
|
|
|
|
// {{if pipeline}} itemList {{end}}
|
|
|
|
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
|
|
|
// If keyword is past.
|
|
|
|
func (t *Tree) ifControl() Node {
|
|
|
|
return newIf(t.parseControl("if"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Range:
|
|
|
|
// {{range pipeline}} itemList {{end}}
|
|
|
|
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
|
|
|
// Range keyword is past.
|
|
|
|
func (t *Tree) rangeControl() Node {
|
|
|
|
return newRange(t.parseControl("range"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// With:
|
|
|
|
// {{with pipeline}} itemList {{end}}
|
|
|
|
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
|
|
|
// If keyword is past.
|
|
|
|
func (t *Tree) withControl() Node {
|
|
|
|
return newWith(t.parseControl("with"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// End:
|
|
|
|
// {{end}}
|
|
|
|
// End keyword is past.
|
|
|
|
func (t *Tree) endControl() Node {
|
|
|
|
t.expect(itemRightDelim, "end")
|
|
|
|
return newEnd()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Else:
|
|
|
|
// {{else}}
|
|
|
|
// Else keyword is past.
|
|
|
|
func (t *Tree) elseControl() Node {
|
|
|
|
t.expect(itemRightDelim, "else")
|
|
|
|
return newElse(t.lex.lineNumber())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Template:
|
|
|
|
// {{template stringValue pipeline}}
|
|
|
|
// Template keyword is past. The name must be something that can evaluate
|
|
|
|
// to a string.
|
|
|
|
func (t *Tree) templateControl() Node {
|
|
|
|
var name string
|
|
|
|
switch token := t.next(); token.typ {
|
|
|
|
case itemString, itemRawString:
|
|
|
|
s, err := strconv.Unquote(token.val)
|
|
|
|
if err != nil {
|
|
|
|
t.error(err)
|
|
|
|
}
|
|
|
|
name = s
|
|
|
|
default:
|
|
|
|
t.unexpected(token, "template invocation")
|
|
|
|
}
|
|
|
|
var pipe *PipeNode
|
|
|
|
if t.next().typ != itemRightDelim {
|
|
|
|
t.backup()
|
|
|
|
// Do not pop variables; they persist until "end".
|
|
|
|
pipe = t.pipeline("template")
|
|
|
|
}
|
|
|
|
return newTemplate(t.lex.lineNumber(), name, pipe)
|
|
|
|
}
|
|
|
|
|
|
|
|
// command:
|
|
|
|
// space-separated arguments up to a pipeline character or right delimiter.
|
|
|
|
// we consume the pipe character but leave the right delim to terminate the action.
|
|
|
|
func (t *Tree) command() *CommandNode {
|
|
|
|
cmd := newCommand()
|
|
|
|
Loop:
|
|
|
|
for {
|
|
|
|
switch token := t.next(); token.typ {
|
|
|
|
case itemRightDelim:
|
|
|
|
t.backup()
|
|
|
|
break Loop
|
|
|
|
case itemPipe:
|
|
|
|
break Loop
|
|
|
|
case itemError:
|
|
|
|
t.errorf("%s", token.val)
|
|
|
|
case itemIdentifier:
|
|
|
|
if !t.hasFunction(token.val) {
|
|
|
|
t.errorf("function %q not defined", token.val)
|
|
|
|
}
|
|
|
|
cmd.append(NewIdentifier(token.val))
|
|
|
|
case itemDot:
|
|
|
|
cmd.append(newDot())
|
|
|
|
case itemVariable:
|
|
|
|
cmd.append(t.useVar(token.val))
|
|
|
|
case itemField:
|
|
|
|
cmd.append(newField(token.val))
|
|
|
|
case itemBool:
|
|
|
|
cmd.append(newBool(token.val == "true"))
|
|
|
|
case itemCharConstant, itemComplex, itemNumber:
|
|
|
|
number, err := newNumber(token.val, token.typ)
|
|
|
|
if err != nil {
|
|
|
|
t.error(err)
|
|
|
|
}
|
|
|
|
cmd.append(number)
|
|
|
|
case itemString, itemRawString:
|
|
|
|
s, err := strconv.Unquote(token.val)
|
|
|
|
if err != nil {
|
|
|
|
t.error(err)
|
|
|
|
}
|
|
|
|
cmd.append(newString(token.val, s))
|
|
|
|
default:
|
|
|
|
t.unexpected(token, "command")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(cmd.Args) == 0 {
|
|
|
|
t.errorf("empty command")
|
|
|
|
}
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
// hasFunction reports if a function name exists in the Tree's maps.
|
|
|
|
func (t *Tree) hasFunction(name string) bool {
|
|
|
|
for _, funcMap := range t.funcs {
|
|
|
|
if funcMap == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if funcMap[name] != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// popVars trims the variable list to the specified length
|
|
|
|
func (t *Tree) popVars(n int) {
|
|
|
|
t.vars = t.vars[:n]
|
|
|
|
}
|
|
|
|
|
|
|
|
// useVar returns a node for a variable reference. It errors if the
|
|
|
|
// variable is not defined.
|
|
|
|
func (t *Tree) useVar(name string) Node {
|
|
|
|
v := newVariable(name)
|
|
|
|
for _, varName := range t.vars {
|
|
|
|
if varName == v.Ident[0] {
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t.errorf("undefined variable %q", v.Ident[0])
|
|
|
|
return nil
|
|
|
|
}
|