diff options
Diffstat (limited to 'src/pkg/text/template/parse/parse.go')
-rw-r--r-- | src/pkg/text/template/parse/parse.go | 289 |
1 files changed, 209 insertions, 80 deletions
diff --git a/src/pkg/text/template/parse/parse.go b/src/pkg/text/template/parse/parse.go index c0087b278..c0fb9274a 100644 --- a/src/pkg/text/template/parse/parse.go +++ b/src/pkg/text/template/parse/parse.go @@ -13,17 +13,20 @@ import ( "fmt" "runtime" "strconv" + "strings" "unicode" ) // Tree is the representation of a single parsed template. type Tree struct { - Name string // name of the template represented by the tree. - Root *ListNode // top-level root of the tree. + Name string // name of the template represented by the tree. + ParseName string // name of the top-level template during parsing, for error messages. + Root *ListNode // top-level root of the tree. + text string // text parsed to create the template (or its parent) // Parsing only; cleared after parse. funcs []map[string]interface{} lex *lexer - token [2]item // two-token lookahead for parser. + token [3]item // three-token lookahead for parser. peekCount int vars []string // variables defined at the moment. } @@ -34,7 +37,9 @@ type Tree struct { // empty map is returned with the error. func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) { treeSet = make(map[string]*Tree) - _, err = New(name).Parse(text, leftDelim, rightDelim, treeSet, funcs...) + t := New(name) + t.text = text + _, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...) return } @@ -53,12 +58,21 @@ func (t *Tree) backup() { t.peekCount++ } -// backup2 backs the input stream up two tokens +// backup2 backs the input stream up two tokens. +// The zeroth token is already there. func (t *Tree) backup2(t1 item) { t.token[1] = t1 t.peekCount = 2 } +// backup3 backs the input stream up three tokens +// The zeroth token is already there. +func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back. + t.token[1] = t1 + t.token[2] = t2 + t.peekCount = 3 +} + // peek returns but does not consume the next token. func (t *Tree) peek() item { if t.peekCount > 0 { @@ -69,6 +83,29 @@ func (t *Tree) peek() item { return t.token[0] } +// nextNonSpace returns the next non-space token. +func (t *Tree) nextNonSpace() (token item) { + for { + token = t.next() + if token.typ != itemSpace { + break + } + } + return token +} + +// peekNonSpace returns but does not consume the next non-space token. +func (t *Tree) peekNonSpace() (token item) { + for { + token = t.next() + if token.typ != itemSpace { + break + } + } + t.backup() + return token +} + // Parsing. // New allocates a new parse tree with the given name. @@ -79,10 +116,29 @@ func New(name string, funcs ...map[string]interface{}) *Tree { } } +// ErrorContext returns a textual representation of the location of the node in the input text. +func (t *Tree) ErrorContext(n Node) (location, context string) { + pos := int(n.Position()) + text := t.text[:pos] + byteNum := strings.LastIndex(text, "\n") + if byteNum == -1 { + byteNum = pos // On first line. + } else { + byteNum++ // After the newline. + byteNum = pos - byteNum + } + lineNum := 1 + strings.Count(text, "\n") + context = n.String() + if len(context) > 20 { + context = fmt.Sprintf("%.20s...", context) + } + return fmt.Sprintf("%s:%d:%d", t.ParseName, lineNum, byteNum), context +} + // 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) + format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format) panic(fmt.Errorf(format, args...)) } @@ -93,18 +149,18 @@ func (t *Tree) error(err error) { // expect consumes the next token and guarantees it has the required type. func (t *Tree) expect(expected itemType, context string) item { - token := t.next() + token := t.nextNonSpace() if token.typ != expected { - t.errorf("expected %s in %s; got %s", expected, context, token) + t.unexpected(token, context) } return token } -// expectEither consumes the next token and guarantees it has one of the required types. +// expectOneOf consumes the next token and guarantees it has one of the required types. func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { - token := t.next() + token := t.nextNonSpace() if token.typ != expected1 && token.typ != expected2 { - t.errorf("expected %s or %s in %s; got %s", expected1, expected2, context, token) + t.unexpected(token, context) } return token } @@ -169,9 +225,11 @@ func (t *Tree) atEOF() bool { // the template for execution. If either action delimiter string is empty, the // default ("{{" or "}}") is used. Embedded template definitions are added to // the treeSet map. -func (t *Tree) Parse(s, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) { +func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) { defer t.recover(&err) - t.startParse(funcs, lex(t.Name, s, leftDelim, rightDelim)) + t.ParseName = t.Name + t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim)) + t.text = text t.parse(treeSet) t.add(treeSet) t.stopParse() @@ -219,12 +277,14 @@ func IsEmptyTree(n Node) bool { // 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() + t.Root = newList(t.peek().pos) for t.peek().typ != itemEOF { if t.peek().typ == itemLeftDelim { delim := t.next() - if t.next().typ == itemDefine { + if t.nextNonSpace().typ == itemDefine { newT := New("definition") // name will be updated once we know it. + newT.text = t.text + newT.ParseName = t.ParseName newT.startParse(t.funcs, t.lex) newT.parseDefinition(treeSet) continue @@ -257,16 +317,16 @@ func (t *Tree) parseDefinition(treeSet map[string]*Tree) { if end.Type() != nodeEnd { t.errorf("unexpected %s in %s", end, context) } - t.stopParse() t.add(treeSet) + t.stopParse() } // itemList: // textOrAction* // Terminates at {{end}} or {{else}}, returned separately. func (t *Tree) itemList() (list *ListNode, next Node) { - list = newList() - for t.peek().typ != itemEOF { + list = newList(t.peekNonSpace().pos) + for t.peekNonSpace().typ != itemEOF { n := t.textOrAction() switch n.Type() { case nodeEnd, nodeElse: @@ -281,9 +341,9 @@ func (t *Tree) itemList() (list *ListNode, next Node) { // textOrAction: // text | action func (t *Tree) textOrAction() Node { - switch token := t.next(); token.typ { + switch token := t.nextNonSpace(); token.typ { case itemText: - return newText(token.val) + return newText(token.pos, token.val) case itemLeftDelim: return t.action() default: @@ -298,7 +358,7 @@ func (t *Tree) textOrAction() Node { // 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 { + switch token := t.nextNonSpace(); token.typ { case itemElse: return t.elseControl() case itemEnd: @@ -314,24 +374,26 @@ func (t *Tree) action() (n Node) { } t.backup() // Do not pop variables; they persist until "end". - return newAction(t.lex.lineNumber(), t.pipeline("command")) + return newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command")) } // Pipeline: -// field or command -// pipeline "|" pipeline +// declarations? command ('|' command)* func (t *Tree) pipeline(context string) (pipe *PipeNode) { var decl []*VariableNode + pos := t.peekNonSpace().pos // Are there declarations? for { - if v := t.peek(); v.typ == itemVariable { + if v := t.peekNonSpace(); v.typ == itemVariable { t.next() - if next := t.peek(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") { - t.next() - variable := newVariable(v.val) - if len(variable.Ident) != 1 { - t.errorf("illegal variable in declaration: %s", v.val) - } + // Since space is a token, we need 3-token look-ahead here in the worst case: + // in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an + // argument variable rather than a declaration. So remember the token + // adjacent to the variable so we can push it back if necessary. + tokenAfterVariable := t.peek() + if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") { + t.nextNonSpace() + variable := newVariable(v.pos, v.val) decl = append(decl, variable) t.vars = append(t.vars, v.val) if next.typ == itemChar && next.val == "," { @@ -340,22 +402,27 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) { } t.errorf("too many declarations in %s", context) } + } else if tokenAfterVariable.typ == itemSpace { + t.backup3(v, tokenAfterVariable) } else { t.backup2(v) } } break } - pipe = newPipeline(t.lex.lineNumber(), decl) + pipe = newPipeline(pos, t.lex.lineNumber(), decl) for { - switch token := t.next(); token.typ { - case itemRightDelim: + switch token := t.nextNonSpace(); token.typ { + case itemRightDelim, itemRightParen: if len(pipe.Cmds) == 0 { t.errorf("missing value for %s", context) } + if token.typ == itemRightParen { + t.backup() + } return case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, - itemVariable, itemNumber, itemRawString, itemString: + itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen: t.backup() pipe.append(t.command()) default: @@ -365,9 +432,9 @@ func (t *Tree) pipeline(context string) (pipe *PipeNode) { return } -func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list, elseList *ListNode) { - lineNum = t.lex.lineNumber() +func (t *Tree) parseControl(context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) { defer t.popVars(len(t.vars)) + line = t.lex.lineNumber() pipe = t.pipeline(context) var next Node list, next = t.itemList() @@ -380,7 +447,7 @@ func (t *Tree) parseControl(context string) (lineNum int, pipe *PipeNode, list, } elseList = elseList } - return lineNum, pipe, list, elseList + return pipe.Position(), line, pipe, list, elseList } // If: @@ -411,16 +478,14 @@ func (t *Tree) withControl() Node { // {{end}} // End keyword is past. func (t *Tree) endControl() Node { - t.expect(itemRightDelim, "end") - return newEnd() + return newEnd(t.expect(itemRightDelim, "end").pos) } // Else: // {{else}} // Else keyword is past. func (t *Tree) elseControl() Node { - t.expect(itemRightDelim, "else") - return newElse(t.lex.lineNumber()) + return newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber()) } // Template: @@ -429,7 +494,8 @@ func (t *Tree) elseControl() Node { // to a string. func (t *Tree) templateControl() Node { var name string - switch token := t.next(); token.typ { + token := t.nextNonSpace() + switch token.typ { case itemString, itemRawString: s, err := strconv.Unquote(token.val) if err != nil { @@ -440,57 +506,38 @@ func (t *Tree) templateControl() Node { t.unexpected(token, "template invocation") } var pipe *PipeNode - if t.next().typ != itemRightDelim { + if t.nextNonSpace().typ != itemRightDelim { t.backup() // Do not pop variables; they persist until "end". pipe = t.pipeline("template") } - return newTemplate(t.lex.lineNumber(), name, pipe) + return newTemplate(token.pos, t.lex.lineNumber(), name, pipe) } // command: +// operand (space operand)* // 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: + cmd := newCommand(t.peekNonSpace().pos) for { + t.peekNonSpace() // skip leading spaces. + operand := t.operand() + if operand != nil { + cmd.append(operand) + } switch token := t.next(); token.typ { - case itemRightDelim: - t.backup() - break Loop - case itemPipe: - break Loop + case itemSpace: + continue 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)) + case itemRightDelim, itemRightParen: + t.backup() + case itemPipe: default: - t.unexpected(token, "command") + t.errorf("unexpected %s in operand; missing space?", token) } + break } if len(cmd.Args) == 0 { t.errorf("empty command") @@ -498,6 +545,88 @@ Loop: return cmd } +// operand: +// term .Field* +// An operand is a space-separated component of a command, +// a term possibly followed by field accesses. +// A nil return means the next item is not an operand. +func (t *Tree) operand() Node { + node := t.term() + if node == nil { + return nil + } + if t.peek().typ == itemField { + chain := newChain(t.peek().pos, node) + for t.peek().typ == itemField { + chain.Add(t.next().val) + } + // Compatibility with original API: If the term is of type NodeField + // or NodeVariable, just put more fields on the original. + // Otherwise, keep the Chain node. + // TODO: Switch to Chains always when we can. + switch node.Type() { + case NodeField: + node = newField(chain.Position(), chain.String()) + case NodeVariable: + node = newVariable(chain.Position(), chain.String()) + default: + node = chain + } + } + return node +} + +// term: +// literal (number, string, nil, boolean) +// function (identifier) +// . +// .Field +// $ +// '(' pipeline ')' +// A term is a simple "expression". +// A nil return means the next item is not a term. +func (t *Tree) term() Node { + switch token := t.nextNonSpace(); token.typ { + case itemError: + t.errorf("%s", token.val) + case itemIdentifier: + if !t.hasFunction(token.val) { + t.errorf("function %q not defined", token.val) + } + return NewIdentifier(token.val).SetPos(token.pos) + case itemDot: + return newDot(token.pos) + case itemNil: + return newNil(token.pos) + case itemVariable: + return t.useVar(token.pos, token.val) + case itemField: + return newField(token.pos, token.val) + case itemBool: + return newBool(token.pos, token.val == "true") + case itemCharConstant, itemComplex, itemNumber: + number, err := newNumber(token.pos, token.val, token.typ) + if err != nil { + t.error(err) + } + return number + case itemLeftParen: + pipe := t.pipeline("parenthesized pipeline") + if token := t.next(); token.typ != itemRightParen { + t.errorf("unclosed right paren: unexpected %s", token) + } + return pipe + case itemString, itemRawString: + s, err := strconv.Unquote(token.val) + if err != nil { + t.error(err) + } + return newString(token.pos, token.val, s) + } + t.backup() + return nil +} + // hasFunction reports if a function name exists in the Tree's maps. func (t *Tree) hasFunction(name string) bool { for _, funcMap := range t.funcs { @@ -518,8 +647,8 @@ func (t *Tree) popVars(n int) { // 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) +func (t *Tree) useVar(pos Pos, name string) Node { + v := newVariable(pos, name) for _, varName := range t.vars { if varName == v.Ident[0] { return v |