| // 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 as defined by text/template |
| // and html/template. Clients should use those packages to construct templates |
| // rather than this one, which provides shared internal data structures not |
| // intended for general use. |
| package parse |
| |
| import ( |
| "bytes" |
| "fmt" |
| "runtime" |
| "strconv" |
| "strings" |
| ) |
| |
| // Tree is the representation of a single parsed template. |
| type Tree struct { |
| 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. |
| Mode Mode // parsing mode. |
| text string // text parsed to create the template (or its parent) |
| // Parsing only; cleared after parse. |
| funcs []map[string]any |
| lex *lexer |
| token [3]item // three-token lookahead for parser. |
| peekCount int |
| vars []string // variables defined at the moment. |
| treeSet map[string]*Tree |
| actionLine int // line of left delim starting action |
| rangeDepth int |
| } |
| |
| // A mode value is a set of flags (or 0). Modes control parser behavior. |
| type Mode uint |
| |
| const ( |
| ParseComments Mode = 1 << iota // parse comments and add them to AST |
| SkipFuncCheck // do not check that functions are defined |
| ) |
| |
| // Copy returns a copy of the Tree. Any parsing state is discarded. |
| func (t *Tree) Copy() *Tree { |
| if t == nil { |
| return nil |
| } |
| return &Tree{ |
| Name: t.Name, |
| ParseName: t.ParseName, |
| Root: t.Root.CopyList(), |
| text: t.text, |
| } |
| } |
| |
| // Parse returns a map from template name to parse.Tree, created by parsing the |
| // templates described in the argument string. The top-level template will be |
| // given the specified name. If an error is encountered, parsing stops and an |
| // empty map is returned with the error. |
| func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]any) (map[string]*Tree, error) { |
| treeSet := make(map[string]*Tree) |
| t := New(name) |
| t.text = text |
| _, err := t.Parse(text, leftDelim, rightDelim, treeSet, funcs...) |
| return treeSet, err |
| } |
| |
| // 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. |
| // 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 { |
| return t.token[t.peekCount-1] |
| } |
| t.peekCount = 1 |
| t.token[0] = t.lex.nextItem() |
| 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() item { |
| token := t.nextNonSpace() |
| t.backup() |
| return token |
| } |
| |
| // Parsing. |
| |
| // New allocates a new parse tree with the given name. |
| func New(name string, funcs ...map[string]any) *Tree { |
| return &Tree{ |
| Name: name, |
| funcs: funcs, |
| } |
| } |
| |
| // ErrorContext returns a textual representation of the location of the node in the input text. |
| // The receiver is only used when the node does not have a pointer to the tree inside, |
| // which can occur in old code. |
| func (t *Tree) ErrorContext(n Node) (location, context string) { |
| pos := int(n.Position()) |
| tree := n.tree() |
| if tree == nil { |
| tree = t |
| } |
| text := tree.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() |
| return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context |
| } |
| |
| // errorf formats the error and terminates processing. |
| func (t *Tree) errorf(format string, args ...any) { |
| t.Root = nil |
| format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format) |
| panic(fmt.Errorf(format, args...)) |
| } |
| |
| // error terminates processing. |
| func (t *Tree) error(err 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.nextNonSpace() |
| if token.typ != expected { |
| t.unexpected(token, context) |
| } |
| return token |
| } |
| |
| // 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.nextNonSpace() |
| if token.typ != expected1 && token.typ != expected2 { |
| t.unexpected(token, context) |
| } |
| return token |
| } |
| |
| // unexpected complains about the token and terminates processing. |
| func (t *Tree) unexpected(token item, context string) { |
| if token.typ == itemError { |
| extra := "" |
| if t.actionLine != 0 && t.actionLine != token.line { |
| extra = fmt.Sprintf(" in action started at %s:%d", t.ParseName, t.actionLine) |
| if strings.HasSuffix(token.val, " action") { |
| extra = extra[len(" in action"):] // avoid "action in action" |
| } |
| } |
| t.errorf("%s%s", token, extra) |
| } |
| 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 *error) { |
| e := recover() |
| if e != nil { |
| if _, ok := e.(runtime.Error); ok { |
| panic(e) |
| } |
| if t != nil { |
| t.lex.drain() |
| t.stopParse() |
| } |
| *errp = e.(error) |
| } |
| } |
| |
| // startParse initializes the parser, using the lexer. |
| func (t *Tree) startParse(funcs []map[string]any, lex *lexer, treeSet map[string]*Tree) { |
| t.Root = nil |
| t.lex = lex |
| t.vars = []string{"$"} |
| t.funcs = funcs |
| t.treeSet = treeSet |
| lex.breakOK = !t.hasFunction("break") |
| lex.continueOK = !t.hasFunction("continue") |
| } |
| |
| // stopParse terminates parsing. |
| func (t *Tree) stopParse() { |
| t.lex = nil |
| t.vars = nil |
| t.funcs = nil |
| t.treeSet = nil |
| } |
| |
| // Parse parses the template definition string to construct a representation of |
| // 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(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]any) (tree *Tree, err error) { |
| defer t.recover(&err) |
| t.ParseName = t.Name |
| emitComment := t.Mode&ParseComments != 0 |
| t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim, emitComment), treeSet) |
| t.text = text |
| t.parse() |
| t.add() |
| t.stopParse() |
| return t, nil |
| } |
| |
| // add adds tree to t.treeSet. |
| func (t *Tree) add() { |
| tree := t.treeSet[t.Name] |
| if tree == nil || IsEmptyTree(tree.Root) { |
| t.treeSet[t.Name] = t |
| return |
| } |
| if !IsEmptyTree(t.Root) { |
| t.errorf("template: multiple definition of template %q", t.Name) |
| } |
| } |
| |
| // IsEmptyTree reports whether this tree (node) is empty of everything but space or comments. |
| func IsEmptyTree(n Node) bool { |
| switch n := n.(type) { |
| case nil: |
| return true |
| case *ActionNode: |
| case *CommentNode: |
| return true |
| case *IfNode: |
| case *ListNode: |
| for _, node := range n.Nodes { |
| if !IsEmptyTree(node) { |
| return false |
| } |
| } |
| return true |
| case *RangeNode: |
| case *TemplateNode: |
| case *TextNode: |
| return len(bytes.TrimSpace(n.Text)) == 0 |
| case *WithNode: |
| default: |
| panic("unknown node: " + n.String()) |
| } |
| return false |
| } |
| |
| // 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() { |
| t.Root = t.newList(t.peek().pos) |
| for t.peek().typ != itemEOF { |
| if t.peek().typ == itemLeftDelim { |
| delim := t.next() |
| if t.nextNonSpace().typ == itemDefine { |
| newT := New("definition") // name will be updated once we know it. |
| newT.text = t.text |
| newT.Mode = t.Mode |
| newT.ParseName = t.ParseName |
| newT.startParse(t.funcs, t.lex, t.treeSet) |
| newT.parseDefinition() |
| continue |
| } |
| t.backup2(delim) |
| } |
| switch n := t.textOrAction(); n.Type() { |
| case nodeEnd, nodeElse: |
| t.errorf("unexpected %s", n) |
| default: |
| t.Root.append(n) |
| } |
| } |
| } |
| |
| // parseDefinition parses a {{define}} ... {{end}} template definition and |
| // installs the definition in t.treeSet. The "define" keyword has already |
| // been scanned. |
| func (t *Tree) parseDefinition() { |
| const context = "define clause" |
| name := t.expectOneOf(itemString, itemRawString, 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.add() |
| t.stopParse() |
| } |
| |
| // itemList: |
| // textOrAction* |
| // Terminates at {{end}} or {{else}}, returned separately. |
| func (t *Tree) itemList() (list *ListNode, next Node) { |
| list = t.newList(t.peekNonSpace().pos) |
| for t.peekNonSpace().typ != itemEOF { |
| n := t.textOrAction() |
| switch n.Type() { |
| case nodeEnd, nodeElse: |
| return list, n |
| } |
| list.append(n) |
| } |
| t.errorf("unexpected EOF") |
| return |
| } |
| |
| // textOrAction: |
| // text | comment | action |
| func (t *Tree) textOrAction() Node { |
| switch token := t.nextNonSpace(); token.typ { |
| case itemText: |
| return t.newText(token.pos, token.val) |
| case itemLeftDelim: |
| t.actionLine = token.line |
| defer t.clearActionLine() |
| return t.action() |
| case itemComment: |
| return t.newComment(token.pos, token.val) |
| default: |
| t.unexpected(token, "input") |
| } |
| return nil |
| } |
| |
| func (t *Tree) clearActionLine() { |
| t.actionLine = 0 |
| } |
| |
| // 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.nextNonSpace(); token.typ { |
| case itemBlock: |
| return t.blockControl() |
| case itemBreak: |
| return t.breakControl(token.pos, token.line) |
| case itemContinue: |
| return t.continueControl(token.pos, token.line) |
| 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() |
| token := t.peek() |
| // Do not pop variables; they persist until "end". |
| return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim)) |
| } |
| |
| // Break: |
| // {{break}} |
| // Break keyword is past. |
| func (t *Tree) breakControl(pos Pos, line int) Node { |
| if token := t.next(); token.typ != itemRightDelim { |
| t.unexpected(token, "in {{break}}") |
| } |
| if t.rangeDepth == 0 { |
| t.errorf("{{break}} outside {{range}}") |
| } |
| return t.newBreak(pos, line) |
| } |
| |
| // Continue: |
| // {{continue}} |
| // Continue keyword is past. |
| func (t *Tree) continueControl(pos Pos, line int) Node { |
| if token := t.next(); token.typ != itemRightDelim { |
| t.unexpected(token, "in {{continue}}") |
| } |
| if t.rangeDepth == 0 { |
| t.errorf("{{continue}} outside {{range}}") |
| } |
| return t.newContinue(pos, line) |
| } |
| |
| // Pipeline: |
| // declarations? command ('|' command)* |
| func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) { |
| token := t.peekNonSpace() |
| pipe = t.newPipeline(token.pos, token.line, nil) |
| // Are there declarations or assignments? |
| decls: |
| if v := t.peekNonSpace(); v.typ == itemVariable { |
| t.next() |
| // 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() |
| next := t.peekNonSpace() |
| switch { |
| case next.typ == itemAssign, next.typ == itemDeclare: |
| pipe.IsAssign = next.typ == itemAssign |
| t.nextNonSpace() |
| pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) |
| t.vars = append(t.vars, v.val) |
| case next.typ == itemChar && next.val == ",": |
| t.nextNonSpace() |
| pipe.Decl = append(pipe.Decl, t.newVariable(v.pos, v.val)) |
| t.vars = append(t.vars, v.val) |
| if context == "range" && len(pipe.Decl) < 2 { |
| switch t.peekNonSpace().typ { |
| case itemVariable, itemRightDelim, itemRightParen: |
| // second initialized variable in a range pipeline |
| goto decls |
| default: |
| t.errorf("range can only initialize variables") |
| } |
| } |
| t.errorf("too many declarations in %s", context) |
| case tokenAfterVariable.typ == itemSpace: |
| t.backup3(v, tokenAfterVariable) |
| default: |
| t.backup2(v) |
| } |
| } |
| for { |
| switch token := t.nextNonSpace(); token.typ { |
| case end: |
| // At this point, the pipeline is complete |
| t.checkPipeline(pipe, context) |
| return |
| case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, |
| itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen: |
| t.backup() |
| pipe.append(t.command()) |
| default: |
| t.unexpected(token, context) |
| } |
| } |
| } |
| |
| func (t *Tree) checkPipeline(pipe *PipeNode, context string) { |
| // Reject empty pipelines |
| if len(pipe.Cmds) == 0 { |
| t.errorf("missing value for %s", context) |
| } |
| // Only the first command of a pipeline can start with a non executable operand |
| for i, c := range pipe.Cmds[1:] { |
| switch c.Args[0].Type() { |
| case NodeBool, NodeDot, NodeNil, NodeNumber, NodeString: |
| // With A|B|C, pipeline stage 2 is B |
| t.errorf("non executable command in pipeline stage %d", i+2) |
| } |
| } |
| } |
| |
| func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) { |
| defer t.popVars(len(t.vars)) |
| pipe = t.pipeline(context, itemRightDelim) |
| if context == "range" { |
| t.rangeDepth++ |
| } |
| var next Node |
| list, next = t.itemList() |
| if context == "range" { |
| t.rangeDepth-- |
| } |
| switch next.Type() { |
| case nodeEnd: //done |
| case nodeElse: |
| if allowElseIf { |
| // Special case for "else if". If the "else" is followed immediately by an "if", |
| // the elseControl will have left the "if" token pending. Treat |
| // {{if a}}_{{else if b}}_{{end}} |
| // as |
| // {{if a}}_{{else}}{{if b}}_{{end}}{{end}}. |
| // To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}} |
| // is assumed. This technique works even for long if-else-if chains. |
| // TODO: Should we allow else-if in with and range? |
| if t.peek().typ == itemIf { |
| t.next() // Consume the "if" token. |
| elseList = t.newList(next.Position()) |
| elseList.append(t.ifControl()) |
| // Do not consume the next item - only one {{end}} required. |
| break |
| } |
| } |
| elseList, next = t.itemList() |
| if next.Type() != nodeEnd { |
| t.errorf("expected end; found %s", next) |
| } |
| } |
| return pipe.Position(), pipe.Line, pipe, list, elseList |
| } |
| |
| // If: |
| // {{if pipeline}} itemList {{end}} |
| // {{if pipeline}} itemList {{else}} itemList {{end}} |
| // If keyword is past. |
| func (t *Tree) ifControl() Node { |
| return t.newIf(t.parseControl(true, "if")) |
| } |
| |
| // Range: |
| // {{range pipeline}} itemList {{end}} |
| // {{range pipeline}} itemList {{else}} itemList {{end}} |
| // Range keyword is past. |
| func (t *Tree) rangeControl() Node { |
| r := t.newRange(t.parseControl(false, "range")) |
| return r |
| } |
| |
| // With: |
| // {{with pipeline}} itemList {{end}} |
| // {{with pipeline}} itemList {{else}} itemList {{end}} |
| // If keyword is past. |
| func (t *Tree) withControl() Node { |
| return t.newWith(t.parseControl(false, "with")) |
| } |
| |
| // End: |
| // {{end}} |
| // End keyword is past. |
| func (t *Tree) endControl() Node { |
| return t.newEnd(t.expect(itemRightDelim, "end").pos) |
| } |
| |
| // Else: |
| // {{else}} |
| // Else keyword is past. |
| func (t *Tree) elseControl() Node { |
| // Special case for "else if". |
| peek := t.peekNonSpace() |
| if peek.typ == itemIf { |
| // We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ". |
| return t.newElse(peek.pos, peek.line) |
| } |
| token := t.expect(itemRightDelim, "else") |
| return t.newElse(token.pos, token.line) |
| } |
| |
| // Block: |
| // {{block stringValue pipeline}} |
| // Block keyword is past. |
| // The name must be something that can evaluate to a string. |
| // The pipeline is mandatory. |
| func (t *Tree) blockControl() Node { |
| const context = "block clause" |
| |
| token := t.nextNonSpace() |
| name := t.parseTemplateName(token, context) |
| pipe := t.pipeline(context, itemRightDelim) |
| |
| block := New(name) // name will be updated once we know it. |
| block.text = t.text |
| block.Mode = t.Mode |
| block.ParseName = t.ParseName |
| block.startParse(t.funcs, t.lex, t.treeSet) |
| var end Node |
| block.Root, end = block.itemList() |
| if end.Type() != nodeEnd { |
| t.errorf("unexpected %s in %s", end, context) |
| } |
| block.add() |
| block.stopParse() |
| |
| return t.newTemplate(token.pos, token.line, name, pipe) |
| } |
| |
| // Template: |
| // {{template stringValue pipeline}} |
| // Template keyword is past. The name must be something that can evaluate |
| // to a string. |
| func (t *Tree) templateControl() Node { |
| const context = "template clause" |
| token := t.nextNonSpace() |
| name := t.parseTemplateName(token, context) |
| var pipe *PipeNode |
| if t.nextNonSpace().typ != itemRightDelim { |
| t.backup() |
| // Do not pop variables; they persist until "end". |
| pipe = t.pipeline(context, itemRightDelim) |
| } |
| return t.newTemplate(token.pos, token.line, name, pipe) |
| } |
| |
| func (t *Tree) parseTemplateName(token item, context string) (name string) { |
| switch token.typ { |
| case itemString, itemRawString: |
| s, err := strconv.Unquote(token.val) |
| if err != nil { |
| t.error(err) |
| } |
| name = s |
| default: |
| t.unexpected(token, context) |
| } |
| return |
| } |
| |
| // 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 := t.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 itemSpace: |
| continue |
| case itemRightDelim, itemRightParen: |
| t.backup() |
| case itemPipe: |
| // nothing here; break loop below |
| default: |
| t.unexpected(token, "operand") |
| } |
| break |
| } |
| if len(cmd.Args) == 0 { |
| t.errorf("empty command") |
| } |
| 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 := t.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. |
| // Obvious parsing errors involving literal values are detected here. |
| // More complex error cases will have to be handled at execution time. |
| switch node.Type() { |
| case NodeField: |
| node = t.newField(chain.Position(), chain.String()) |
| case NodeVariable: |
| node = t.newVariable(chain.Position(), chain.String()) |
| case NodeBool, NodeString, NodeNumber, NodeNil, NodeDot: |
| t.errorf("unexpected . after term %q", node.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 itemIdentifier: |
| checkFunc := t.Mode&SkipFuncCheck == 0 |
| if checkFunc && !t.hasFunction(token.val) { |
| t.errorf("function %q not defined", token.val) |
| } |
| return NewIdentifier(token.val).SetTree(t).SetPos(token.pos) |
| case itemDot: |
| return t.newDot(token.pos) |
| case itemNil: |
| return t.newNil(token.pos) |
| case itemVariable: |
| return t.useVar(token.pos, token.val) |
| case itemField: |
| return t.newField(token.pos, token.val) |
| case itemBool: |
| return t.newBool(token.pos, token.val == "true") |
| case itemCharConstant, itemComplex, itemNumber: |
| number, err := t.newNumber(token.pos, token.val, token.typ) |
| if err != nil { |
| t.error(err) |
| } |
| return number |
| case itemLeftParen: |
| return t.pipeline("parenthesized pipeline", itemRightParen) |
| case itemString, itemRawString: |
| s, err := strconv.Unquote(token.val) |
| if err != nil { |
| t.error(err) |
| } |
| return t.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 { |
| 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(pos Pos, name string) Node { |
| v := t.newVariable(pos, name) |
| for _, varName := range t.vars { |
| if varName == v.Ident[0] { |
| return v |
| } |
| } |
| t.errorf("undefined variable %q", v.Ident[0]) |
| return nil |
| } |