blob: 6918074664e9e9e0f146ef90034e45d5ab836159 [file] [log] [blame]
// 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"
"os"
"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.
func (t *Tree) error(err os.Error) {
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.
func (t *Tree) recover(errp *os.Error) {
e := recover()
if e != nil {
if _, ok := e.(runtime.Error); ok {
panic(e)
}
if t != nil {
t.stopParse()
}
*errp = e.(os.Error)
}
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
// representation of the template for execution.
func (t *Tree) Parse(s string, funcs ...map[string]interface{}) (tree *Tree, err os.Error) {
defer t.recover(&err)
t.startParse(funcs, lex(t.Name, s))
t.parse(true)
t.stopParse()
return t, nil
}
// parse is the helper for Parse.
// It triggers an error if we expect EOF but don't reach it.
func (t *Tree) parse(toEOF bool) (next Node) {
t.Root, next = t.itemList(true)
if toEOF && next != nil {
t.errorf("unexpected %s", next)
}
return next
}
// itemList:
// textOrAction*
// Terminates at EOF and at {{end}} or {{else}}, which is returned separately.
// The toEOF flag tells whether we expect to reach EOF.
func (t *Tree) itemList(toEOF bool) (list *ListNode, next Node) {
list = newList()
for t.peek().typ != itemEOF {
n := t.textOrAction()
switch n.Type() {
case nodeEnd, nodeElse:
return list, n
}
list.append(n)
}
if !toEOF {
t.unexpected(t.next(), "input")
}
return list, nil
}
// 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
list, next = t.itemList(false)
switch next.Type() {
case nodeEnd: //done
case nodeElse:
elseList, next = t.itemList(false)
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
}