parse.go (hugo-0.80.0) | : | parse.go (hugo-0.81.0) | ||
---|---|---|---|---|
skipping to change at line 24 | skipping to change at line 24 | |||
"runtime" | "runtime" | |||
"strconv" | "strconv" | |||
"strings" | "strings" | |||
) | ) | |||
// Tree is the representation of a single parsed template. | // Tree is the representation of a single parsed template. | |||
type Tree struct { | type Tree struct { | |||
Name string // name of the template represented by 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. | ParseName string // name of the top-level template during parsing, for error messages. | |||
Root *ListNode // top-level root of the tree. | Root *ListNode // top-level root of the tree. | |||
Mode Mode // parsing mode. | ||||
text string // text parsed to create the template (or its parent) | text string // text parsed to create the template (or its parent) | |||
// Parsing only; cleared after parse. | // Parsing only; cleared after parse. | |||
funcs []map[string]interface{} | funcs []map[string]interface{} | |||
lex *lexer | lex *lexer | |||
token [3]item // three-token lookahead for parser. | token [3]item // three-token lookahead for parser. | |||
peekCount int | peekCount int | |||
vars []string // variables defined at the moment. | vars []string // variables defined at the moment. | |||
treeSet map[string]*Tree | 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. | // Copy returns a copy of the Tree. Any parsing state is discarded. | |||
func (t *Tree) Copy() *Tree { | func (t *Tree) Copy() *Tree { | |||
if t == nil { | if t == nil { | |||
return nil | return nil | |||
} | } | |||
return &Tree{ | return &Tree{ | |||
Name: t.Name, | Name: t.Name, | |||
ParseName: t.ParseName, | ParseName: t.ParseName, | |||
Root: t.Root.CopyList(), | Root: t.Root.CopyList(), | |||
text: t.text, | text: t.text, | |||
skipping to change at line 181 | skipping to change at line 191 | |||
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { | func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item { | |||
token := t.nextNonSpace() | token := t.nextNonSpace() | |||
if token.typ != expected1 && token.typ != expected2 { | if token.typ != expected1 && token.typ != expected2 { | |||
t.unexpected(token, context) | t.unexpected(token, context) | |||
} | } | |||
return token | return token | |||
} | } | |||
// unexpected complains about the token and terminates processing. | // unexpected complains about the token and terminates processing. | |||
func (t *Tree) unexpected(token item, context string) { | 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.Pars | ||||
eName, t.actionLine) | ||||
if strings.HasSuffix(token.val, " action") { | ||||
extra = extra[len(" in action"):] // avoid "actio | ||||
n in action" | ||||
} | ||||
} | ||||
t.errorf("%s%s", token, extra) | ||||
} | ||||
t.errorf("unexpected %s in %s", token, context) | t.errorf("unexpected %s in %s", token, context) | |||
} | } | |||
// recover is the handler that turns panics into returns from the top level of P arse. | // recover is the handler that turns panics into returns from the top level of P arse. | |||
func (t *Tree) recover(errp *error) { | func (t *Tree) recover(errp *error) { | |||
e := recover() | e := recover() | |||
if e != nil { | if e != nil { | |||
if _, ok := e.(runtime.Error); ok { | if _, ok := e.(runtime.Error); ok { | |||
panic(e) | panic(e) | |||
} | } | |||
skipping to change at line 223 | skipping to change at line 243 | |||
t.treeSet = nil | t.treeSet = nil | |||
} | } | |||
// Parse parses the template definition string to construct a representation of | // Parse parses the template definition string to construct a representation of | |||
// the template for execution. If either action delimiter string is empty, the | // the template for execution. If either action delimiter string is empty, the | |||
// default ("{{" or "}}") is used. Embedded template definitions are added to | // default ("{{" or "}}") is used. Embedded template definitions are added to | |||
// the treeSet map. | // the treeSet map. | |||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tre e, funcs ...map[string]interface{}) (tree *Tree, err error) { | func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tre e, funcs ...map[string]interface{}) (tree *Tree, err error) { | |||
defer t.recover(&err) | defer t.recover(&err) | |||
t.ParseName = t.Name | 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.text = text | |||
t.parse() | t.parse() | |||
t.add() | t.add() | |||
t.stopParse() | t.stopParse() | |||
return t, nil | return t, nil | |||
} | } | |||
// add adds tree to t.treeSet. | // add adds tree to t.treeSet. | |||
func (t *Tree) add() { | func (t *Tree) add() { | |||
tree := t.treeSet[t.Name] | tree := t.treeSet[t.Name] | |||
if tree == nil || IsEmptyTree(tree.Root) { | if tree == nil || IsEmptyTree(tree.Root) { | |||
t.treeSet[t.Name] = t | t.treeSet[t.Name] = t | |||
return | return | |||
} | } | |||
if !IsEmptyTree(t.Root) { | if !IsEmptyTree(t.Root) { | |||
t.errorf("template: multiple definition of template %q", t.Name) | t.errorf("template: multiple definition of template %q", t.Name) | |||
} | } | |||
} | } | |||
// 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 { | func IsEmptyTree(n Node) bool { | |||
switch n := n.(type) { | switch n := n.(type) { | |||
case nil: | case nil: | |||
return true | return true | |||
case *ActionNode: | case *ActionNode: | |||
case *CommentNode: | ||||
return true | ||||
case *IfNode: | case *IfNode: | |||
case *ListNode: | case *ListNode: | |||
for _, node := range n.Nodes { | for _, node := range n.Nodes { | |||
if !IsEmptyTree(node) { | if !IsEmptyTree(node) { | |||
return false | return false | |||
} | } | |||
} | } | |||
return true | return true | |||
case *RangeNode: | case *RangeNode: | |||
case *TemplateNode: | case *TemplateNode: | |||
skipping to change at line 279 | skipping to change at line 302 | |||
// as itemList except it also parses {{define}} actions. | // as itemList except it also parses {{define}} actions. | |||
// It runs to EOF. | // It runs to EOF. | |||
func (t *Tree) parse() { | func (t *Tree) parse() { | |||
t.Root = t.newList(t.peek().pos) | t.Root = t.newList(t.peek().pos) | |||
for t.peek().typ != itemEOF { | for t.peek().typ != itemEOF { | |||
if t.peek().typ == itemLeftDelim { | if t.peek().typ == itemLeftDelim { | |||
delim := t.next() | delim := t.next() | |||
if t.nextNonSpace().typ == itemDefine { | if t.nextNonSpace().typ == itemDefine { | |||
newT := New("definition") // name will be updated once we know it. | newT := New("definition") // name will be updated once we know it. | |||
newT.text = t.text | newT.text = t.text | |||
newT.Mode = t.Mode | ||||
newT.ParseName = t.ParseName | newT.ParseName = t.ParseName | |||
newT.startParse(t.funcs, t.lex, t.treeSet) | newT.startParse(t.funcs, t.lex, t.treeSet) | |||
newT.parseDefinition() | newT.parseDefinition() | |||
continue | continue | |||
} | } | |||
t.backup2(delim) | t.backup2(delim) | |||
} | } | |||
switch n := t.textOrAction(); n.Type() { | switch n := t.textOrAction(); n.Type() { | |||
case nodeEnd, nodeElse: | case nodeEnd, nodeElse: | |||
t.errorf("unexpected %s", n) | t.errorf("unexpected %s", n) | |||
skipping to change at line 334 | skipping to change at line 358 | |||
case nodeEnd, nodeElse: | case nodeEnd, nodeElse: | |||
return list, n | return list, n | |||
} | } | |||
list.append(n) | list.append(n) | |||
} | } | |||
t.errorf("unexpected EOF") | t.errorf("unexpected EOF") | |||
return | return | |||
} | } | |||
// textOrAction: | // textOrAction: | |||
// text | action | // text | comment | action | |||
func (t *Tree) textOrAction() Node { | func (t *Tree) textOrAction() Node { | |||
switch token := t.nextNonSpace(); token.typ { | switch token := t.nextNonSpace(); token.typ { | |||
case itemText: | case itemText: | |||
return t.newText(token.pos, token.val) | return t.newText(token.pos, token.val) | |||
case itemLeftDelim: | case itemLeftDelim: | |||
t.actionLine = token.line | ||||
defer t.clearActionLine() | ||||
return t.action() | return t.action() | |||
case itemComment: | ||||
return t.newComment(token.pos, token.val) | ||||
default: | default: | |||
t.unexpected(token, "input") | t.unexpected(token, "input") | |||
} | } | |||
return nil | return nil | |||
} | } | |||
func (t *Tree) clearActionLine() { | ||||
t.actionLine = 0 | ||||
} | ||||
// Action: | // Action: | |||
// control | // control | |||
// command ("|" command)* | // command ("|" command)* | |||
// Left delim is past. Now get actions. | // Left delim is past. Now get actions. | |||
// First word could be a keyword such as range. | // First word could be a keyword such as range. | |||
func (t *Tree) action() (n Node) { | func (t *Tree) action() (n Node) { | |||
switch token := t.nextNonSpace(); token.typ { | switch token := t.nextNonSpace(); token.typ { | |||
case itemBlock: | case itemBlock: | |||
return t.blockControl() | return t.blockControl() | |||
case itemElse: | case itemElse: | |||
skipping to change at line 372 | skipping to change at line 404 | |||
case itemRange: | case itemRange: | |||
return t.rangeControl() | return t.rangeControl() | |||
case itemTemplate: | case itemTemplate: | |||
return t.templateControl() | return t.templateControl() | |||
case itemWith: | case itemWith: | |||
return t.withControl() | return t.withControl() | |||
} | } | |||
t.backup() | t.backup() | |||
token := t.peek() | token := t.peek() | |||
// Do not pop variables; they persist until "end". | // 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", itemRight Delim)) | |||
} | } | |||
// Pipeline: | // Pipeline: | |||
// declarations? command ('|' command)* | // declarations? command ('|' command)* | |||
func (t *Tree) pipeline(context string) (pipe *PipeNode) { | func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) { | |||
token := t.peekNonSpace() | token := t.peekNonSpace() | |||
pipe = t.newPipeline(token.pos, token.line, nil) | pipe = t.newPipeline(token.pos, token.line, nil) | |||
// Are there declarations or assignments? | // Are there declarations or assignments? | |||
decls: | decls: | |||
if v := t.peekNonSpace(); v.typ == itemVariable { | if v := t.peekNonSpace(); v.typ == itemVariable { | |||
t.next() | t.next() | |||
// Since space is a token, we need 3-token look-ahead here in the worst case: | // 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 | // 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 t oken | // argument variable rather than a declaration. So remember the t oken | |||
// adjacent to the variable so we can push it back if necessary. | // adjacent to the variable so we can push it back if necessary. | |||
skipping to change at line 418 | skipping to change at line 450 | |||
} | } | |||
t.errorf("too many declarations in %s", context) | t.errorf("too many declarations in %s", context) | |||
case tokenAfterVariable.typ == itemSpace: | case tokenAfterVariable.typ == itemSpace: | |||
t.backup3(v, tokenAfterVariable) | t.backup3(v, tokenAfterVariable) | |||
default: | default: | |||
t.backup2(v) | t.backup2(v) | |||
} | } | |||
} | } | |||
for { | for { | |||
switch token := t.nextNonSpace(); token.typ { | switch token := t.nextNonSpace(); token.typ { | |||
case itemRightDelim, itemRightParen: | case end: | |||
// At this point, the pipeline is complete | // At this point, the pipeline is complete | |||
t.checkPipeline(pipe, context) | t.checkPipeline(pipe, context) | |||
if token.typ == itemRightParen { | ||||
t.backup() | ||||
} | ||||
return | return | |||
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, | case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier, | |||
itemNumber, itemNil, itemRawString, itemString, itemVaria ble, itemLeftParen: | itemNumber, itemNil, itemRawString, itemString, itemVaria ble, itemLeftParen: | |||
t.backup() | t.backup() | |||
pipe.append(t.command()) | pipe.append(t.command()) | |||
default: | default: | |||
t.unexpected(token, context) | t.unexpected(token, context) | |||
} | } | |||
} | } | |||
} | } | |||
skipping to change at line 452 | skipping to change at line 481 | |||
switch c.Args[0].Type() { | switch c.Args[0].Type() { | |||
case NodeBool, NodeDot, NodeNil, NodeNumber, NodeString: | case NodeBool, NodeDot, NodeNil, NodeNumber, NodeString: | |||
// With A|B|C, pipeline stage 2 is B | // With A|B|C, pipeline stage 2 is B | |||
t.errorf("non executable command in pipeline stage %d", i +2) | 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) { | func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int , pipe *PipeNode, list, elseList *ListNode) { | |||
defer t.popVars(len(t.vars)) | defer t.popVars(len(t.vars)) | |||
pipe = t.pipeline(context) | pipe = t.pipeline(context, itemRightDelim) | |||
var next Node | var next Node | |||
list, next = t.itemList() | list, next = t.itemList() | |||
switch next.Type() { | switch next.Type() { | |||
case nodeEnd: //done | case nodeEnd: //done | |||
case nodeElse: | case nodeElse: | |||
if allowElseIf { | if allowElseIf { | |||
// Special case for "else if". If the "else" is followed immediately by an "if", | // Special case for "else if". If the "else" is followed immediately by an "if", | |||
// the elseControl will have left the "if" token pending. Treat | // the elseControl will have left the "if" token pending. Treat | |||
// {{if a}}_{{else if b}}_{{end}} | // {{if a}}_{{else if b}}_{{end}} | |||
// as | // as | |||
skipping to change at line 538 | skipping to change at line 567 | |||
// Block: | // Block: | |||
// {{block stringValue pipeline}} | // {{block stringValue pipeline}} | |||
// Block keyword is past. | // Block keyword is past. | |||
// The name must be something that can evaluate to a string. | // The name must be something that can evaluate to a string. | |||
// The pipeline is mandatory. | // The pipeline is mandatory. | |||
func (t *Tree) blockControl() Node { | func (t *Tree) blockControl() Node { | |||
const context = "block clause" | const context = "block clause" | |||
token := t.nextNonSpace() | token := t.nextNonSpace() | |||
name := t.parseTemplateName(token, context) | 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 := New(name) // name will be updated once we know it. | |||
block.text = t.text | block.text = t.text | |||
block.Mode = t.Mode | ||||
block.ParseName = t.ParseName | block.ParseName = t.ParseName | |||
block.startParse(t.funcs, t.lex, t.treeSet) | block.startParse(t.funcs, t.lex, t.treeSet) | |||
var end Node | var end Node | |||
block.Root, end = block.itemList() | block.Root, end = block.itemList() | |||
if end.Type() != nodeEnd { | if end.Type() != nodeEnd { | |||
t.errorf("unexpected %s in %s", end, context) | t.errorf("unexpected %s in %s", end, context) | |||
} | } | |||
block.add() | block.add() | |||
block.stopParse() | block.stopParse() | |||
skipping to change at line 567 | skipping to change at line 597 | |||
// Template keyword is past. The name must be something that can evaluate | // Template keyword is past. The name must be something that can evaluate | |||
// to a string. | // to a string. | |||
func (t *Tree) templateControl() Node { | func (t *Tree) templateControl() Node { | |||
const context = "template clause" | const context = "template clause" | |||
token := t.nextNonSpace() | token := t.nextNonSpace() | |||
name := t.parseTemplateName(token, context) | name := t.parseTemplateName(token, context) | |||
var pipe *PipeNode | var pipe *PipeNode | |||
if t.nextNonSpace().typ != itemRightDelim { | if t.nextNonSpace().typ != itemRightDelim { | |||
t.backup() | t.backup() | |||
// Do not pop variables; they persist until "end". | // 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) | return t.newTemplate(token.pos, token.line, name, pipe) | |||
} | } | |||
func (t *Tree) parseTemplateName(token item, context string) (name string) { | func (t *Tree) parseTemplateName(token item, context string) (name string) { | |||
switch token.typ { | switch token.typ { | |||
case itemString, itemRawString: | case itemString, itemRawString: | |||
s, err := strconv.Unquote(token.val) | s, err := strconv.Unquote(token.val) | |||
if err != nil { | if err != nil { | |||
t.error(err) | t.error(err) | |||
skipping to change at line 601 | skipping to change at line 631 | |||
cmd := t.newCommand(t.peekNonSpace().pos) | cmd := t.newCommand(t.peekNonSpace().pos) | |||
for { | for { | |||
t.peekNonSpace() // skip leading spaces. | t.peekNonSpace() // skip leading spaces. | |||
operand := t.operand() | operand := t.operand() | |||
if operand != nil { | if operand != nil { | |||
cmd.append(operand) | cmd.append(operand) | |||
} | } | |||
switch token := t.next(); token.typ { | switch token := t.next(); token.typ { | |||
case itemSpace: | case itemSpace: | |||
continue | continue | |||
case itemError: | ||||
t.errorf("%s", token.val) | ||||
case itemRightDelim, itemRightParen: | case itemRightDelim, itemRightParen: | |||
t.backup() | t.backup() | |||
case itemPipe: | case itemPipe: | |||
// nothing here; break loop below | ||||
default: | default: | |||
t.errorf("unexpected %s in operand", token) | t.unexpected(token, "operand") | |||
} | } | |||
break | break | |||
} | } | |||
if len(cmd.Args) == 0 { | if len(cmd.Args) == 0 { | |||
t.errorf("empty command") | t.errorf("empty command") | |||
} | } | |||
return cmd | return cmd | |||
} | } | |||
// operand: | // operand: | |||
skipping to change at line 662 | skipping to change at line 691 | |||
// literal (number, string, nil, boolean) | // literal (number, string, nil, boolean) | |||
// function (identifier) | // function (identifier) | |||
// . | // . | |||
// .Field | // .Field | |||
// $ | // $ | |||
// '(' pipeline ')' | // '(' pipeline ')' | |||
// A term is a simple "expression". | // A term is a simple "expression". | |||
// A nil return means the next item is not a term. | // A nil return means the next item is not a term. | |||
func (t *Tree) term() Node { | func (t *Tree) term() Node { | |||
switch token := t.nextNonSpace(); token.typ { | switch token := t.nextNonSpace(); token.typ { | |||
case itemError: | ||||
t.errorf("%s", token.val) | ||||
case itemIdentifier: | case itemIdentifier: | |||
if !t.hasFunction(token.val) { | if !t.hasFunction(token.val) { | |||
t.errorf("function %q not defined", token.val) | t.errorf("function %q not defined", token.val) | |||
} | } | |||
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos) | return NewIdentifier(token.val).SetTree(t).SetPos(token.pos) | |||
case itemDot: | case itemDot: | |||
return t.newDot(token.pos) | return t.newDot(token.pos) | |||
case itemNil: | case itemNil: | |||
return t.newNil(token.pos) | return t.newNil(token.pos) | |||
case itemVariable: | case itemVariable: | |||
skipping to change at line 686 | skipping to change at line 713 | |||
return t.newField(token.pos, token.val) | return t.newField(token.pos, token.val) | |||
case itemBool: | case itemBool: | |||
return t.newBool(token.pos, token.val == "true") | return t.newBool(token.pos, token.val == "true") | |||
case itemCharConstant, itemComplex, itemNumber: | case itemCharConstant, itemComplex, itemNumber: | |||
number, err := t.newNumber(token.pos, token.val, token.typ) | number, err := t.newNumber(token.pos, token.val, token.typ) | |||
if err != nil { | if err != nil { | |||
t.error(err) | t.error(err) | |||
} | } | |||
return number | return number | |||
case itemLeftParen: | case itemLeftParen: | |||
pipe := t.pipeline("parenthesized pipeline") | return t.pipeline("parenthesized pipeline", itemRightParen) | |||
if token := t.next(); token.typ != itemRightParen { | ||||
t.errorf("unclosed right paren: unexpected %s", token) | ||||
} | ||||
return pipe | ||||
case itemString, itemRawString: | case itemString, itemRawString: | |||
s, err := strconv.Unquote(token.val) | s, err := strconv.Unquote(token.val) | |||
if err != nil { | if err != nil { | |||
t.error(err) | t.error(err) | |||
} | } | |||
return t.newString(token.pos, token.val, s) | return t.newString(token.pos, token.val, s) | |||
} | } | |||
t.backup() | t.backup() | |||
return nil | return nil | |||
} | } | |||
End of changes. 25 change blocks. | ||||
28 lines changed or deleted | 54 lines changed or added |