diff options
Diffstat (limited to 'tpl/internal/go_templates/texttemplate/parse/parse.go')
-rw-r--r-- | tpl/internal/go_templates/texttemplate/parse/parse.go | 79 |
1 files changed, 51 insertions, 28 deletions
diff --git a/tpl/internal/go_templates/texttemplate/parse/parse.go b/tpl/internal/go_templates/texttemplate/parse/parse.go index c9b80f4a2..5e6e512eb 100644 --- a/tpl/internal/go_templates/texttemplate/parse/parse.go +++ b/tpl/internal/go_templates/texttemplate/parse/parse.go @@ -21,16 +21,26 @@ 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]interface{} - lex *lexer - token [3]item // three-token lookahead for parser. - peekCount int - vars []string // variables defined at the moment. - treeSet map[string]*Tree + funcs []map[string]interface{} + 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 + mode Mode } +// 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 +) + // Copy returns a copy of the Tree. Any parsing state is discarded. func (t *Tree) Copy() *Tree { if t == nil { @@ -178,6 +188,16 @@ func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { // 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) } @@ -220,7 +240,8 @@ func (t *Tree) stopParse() { 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.ParseName = t.Name - t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim), treeSet) + emitComment := t.Mode&ParseComments != 0 + t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim, emitComment), treeSet) t.text = text t.parse() t.add() @@ -240,12 +261,14 @@ func (t *Tree) add() { } } -// IsEmptyTree reports whether this tree (node) is empty of everything but space. +// 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 { @@ -276,6 +299,7 @@ func (t *Tree) parse() { 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() @@ -331,19 +355,27 @@ func (t *Tree) itemList() (list *ListNode, next Node) { } // textOrAction: -// text | action +// 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)* @@ -369,12 +401,12 @@ func (t *Tree) action() (n Node) { t.backup() token := t.peek() // Do not pop variables; they persist until "end". - return t.newAction(token.pos, token.line, t.pipeline("command")) + return t.newAction(token.pos, token.line, t.pipeline("command", itemRightDelim)) } // Pipeline: // declarations? command ('|' command)* -func (t *Tree) pipeline(context string) (pipe *PipeNode) { +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? @@ -415,12 +447,9 @@ decls: } for { switch token := t.nextNonSpace(); token.typ { - case itemRightDelim, itemRightParen: + case end: // At this point, the pipeline is complete t.checkPipeline(pipe, context) - if token.typ == itemRightParen { - t.backup() - } return case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen: @@ -449,7 +478,7 @@ func (t *Tree) checkPipeline(pipe *PipeNode, context string) { 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) + pipe = t.pipeline(context, itemRightDelim) var next Node list, next = t.itemList() switch next.Type() { @@ -535,10 +564,11 @@ func (t *Tree) blockControl() Node { token := t.nextNonSpace() name := t.parseTemplateName(token, context) - pipe := t.pipeline(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 @@ -564,7 +594,7 @@ func (t *Tree) templateControl() Node { if t.nextNonSpace().typ != itemRightDelim { t.backup() // Do not pop variables; they persist until "end". - pipe = t.pipeline(context) + pipe = t.pipeline(context, itemRightDelim) } return t.newTemplate(token.pos, token.line, name, pipe) } @@ -598,13 +628,12 @@ func (t *Tree) command() *CommandNode { switch token := t.next(); token.typ { case itemSpace: continue - case itemError: - t.errorf("%s", token.val) case itemRightDelim, itemRightParen: t.backup() case itemPipe: + // nothing here; break loop below default: - t.errorf("unexpected %s in operand", token) + t.unexpected(token, "operand") } break } @@ -659,8 +688,6 @@ func (t *Tree) operand() Node { // 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) @@ -683,11 +710,7 @@ func (t *Tree) term() Node { } 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 + return t.pipeline("parenthesized pipeline", itemRightParen) case itemString, itemRawString: s, err := strconv.Unquote(token.val) if err != nil { |