123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987 |
- // Copyright 2016 José Santos <henrique_1609@me.com>
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package jet
- import (
- "bytes"
- "fmt"
- "runtime"
- "strconv"
- "strings"
- )
- func unquote(text string) (string, error) {
- return strconv.Unquote(text)
- }
- // Template is the representation of a single parsed template.
- type Template struct {
- Name string // name of the template represented by the tree.
- ParseName string // name of the top-level template during parsing, for error messages.
- set *Set
- extends *Template
- imports []*Template
- processedBlocks map[string]*BlockNode
- passedBlocks map[string]*BlockNode
- Root *ListNode // top-level root of the tree.
- text string // text parsed to create the template (or its parent)
- // Parsing only; cleared after parse.
- lex *lexer
- token [3]item // three-token lookahead for parser.
- peekCount int
- }
- // next returns the next token.
- func (t *Template) 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 *Template) backup() {
- t.peekCount++
- }
- // backup2 backs the input stream up two tokens.
- // The zeroth token is already there.
- func (t *Template) 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 *Template) 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 *Template) 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 *Template) 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 *Template) peekNonSpace() (token item) {
- for {
- token = t.next()
- if token.typ != itemSpace {
- break
- }
- }
- t.backup()
- return token
- }
- // errorf formats the error and terminates processing.
- func (t *Template) errorf(format string, args ...interface{}) {
- t.Root = nil
- format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
- panic(fmt.Errorf(format, args...))
- }
- // error terminates processing.
- func (t *Template) error(err error) {
- t.errorf("%s", err)
- }
- // expect consumes the next token and guarantees it has the required type.
- func (t *Template) 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 *Template) 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 *Template) 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 *Template) 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)
- }
- return
- }
- func (s *Set) parse(name, text string) (t *Template, err error) {
- t = &Template{Name: name, text: text, set: s, passedBlocks: make(map[string]*BlockNode)}
- defer t.recover(&err)
- t.ParseName = t.Name
- t.startParse(lex(t.Name, text))
- t.parseTemplate()
- t.stopParse()
- if t.extends != nil {
- t.addBlocks(t.extends.processedBlocks)
- }
- for _, _import := range t.imports {
- t.addBlocks(_import.processedBlocks)
- }
- t.addBlocks(t.passedBlocks)
- return t, err
- }
- func (t *Template) expectString(context string) string {
- token := t.expectOneOf(itemString, itemRawString, context)
- s, err := unquote(token.val)
- if err != nil {
- t.error(err)
- }
- return s
- }
- // parse is the top-level parser for a template, essentially the same
- // It runs to EOF.
- func (t *Template) parseTemplate() (next Node) {
- t.Root = t.newList(t.peek().pos)
- // {{ extends|import stringLiteral }}
- for t.peek().typ != itemEOF {
- delim := t.next()
- if delim.typ == itemText && strings.TrimSpace(delim.val) == "" {
- continue //skips empty text nodes
- }
- if delim.typ == itemLeftDelim {
- token := t.nextNonSpace()
- if token.typ == itemExtends || token.typ == itemImport {
- s := t.expectString("extends|import")
- if token.typ == itemExtends {
- if t.extends != nil {
- t.errorf("Unexpected extends clause, only one extends clause is valid per template")
- } else if len(t.imports) > 0 {
- t.errorf("Unexpected extends clause, all import clause should come after extends clause")
- }
- var err error
- t.extends, err = t.set.getTemplateWhileParsing(t.Name, s)
- if err != nil {
- t.error(err)
- }
- } else {
- tt, err := t.set.getTemplateWhileParsing(t.Name, s)
- if err != nil {
- t.error(err)
- }
- t.imports = append(t.imports, tt)
- }
- t.expect(itemRightDelim, "extends|import")
- } else {
- t.backup2(delim)
- break
- }
- } else {
- t.backup()
- break
- }
- }
- for t.peek().typ != itemEOF {
- switch n := t.textOrAction(); n.Type() {
- case nodeEnd, nodeElse, nodeContent:
- t.errorf("unexpected %s", n)
- default:
- t.Root.append(n)
- }
- }
- return nil
- }
- // startParse initializes the parser, using the lexer.
- func (t *Template) startParse(lex *lexer) {
- t.Root = nil
- t.lex = lex
- }
- // stopParse terminates parsing.
- func (t *Template) stopParse() {
- t.lex = nil
- }
- // IsEmptyTree reports whether this tree (node) is empty of everything but space.
- func IsEmptyTree(n Node) bool {
- switch n := n.(type) {
- case nil:
- return true
- case *ActionNode:
- case *IfNode:
- case *ListNode:
- for _, node := range n.Nodes {
- if !IsEmptyTree(node) {
- return false
- }
- }
- return true
- case *RangeNode:
- case *IncludeNode:
- case *TextNode:
- return len(bytes.TrimSpace(n.Text)) == 0
- case *BlockNode:
- case *YieldNode:
- default:
- panic("unknown node: " + n.String())
- }
- return false
- }
- func (t *Template) blockParametersList(isDeclaring bool, context string) *BlockParameterList {
- block := &BlockParameterList{}
- t.expect(itemLeftParen, context)
- for {
- var expression Expression
- next := t.nextNonSpace()
- if next.typ == itemIdentifier {
- identifier := next.val
- next2 := t.nextNonSpace()
- switch next2.typ {
- case itemComma, itemRightParen:
- block.List = append(block.List, BlockParameter{Identifier: identifier})
- next = next2
- case itemAssign:
- expression, next = t.parseExpression(context)
- block.List = append(block.List, BlockParameter{Identifier: identifier, Expression: expression})
- default:
- if !isDeclaring {
- switch next2.typ {
- case itemComma, itemRightParen:
- default:
- t.backup2(next)
- expression, next = t.parseExpression(context)
- block.List = append(block.List, BlockParameter{Expression: expression})
- }
- } else {
- t.unexpected(next2, context)
- }
- }
- } else if !isDeclaring {
- switch next.typ {
- case itemComma, itemRightParen:
- default:
- t.backup()
- expression, next = t.parseExpression(context)
- block.List = append(block.List, BlockParameter{Expression: expression})
- }
- }
- if next.typ != itemComma {
- t.backup()
- break
- }
- }
- t.expect(itemRightParen, context)
- return block
- }
- func (t *Template) parseBlock() Node {
- const context = "block clause"
- var pipe Expression
- name := t.expect(itemIdentifier, context)
- bplist := t.blockParametersList(true, context)
- if t.peekNonSpace().typ != itemRightDelim {
- pipe = t.expression(context)
- }
- t.expect(itemRightDelim, context)
- list, end := t.itemList()
- var contentList *ListNode
- if end.Type() == nodeContent {
- contentList, end = t.itemList()
- if end.Type() != nodeEnd {
- t.errorf("unexpected %s in %s", end, context)
- }
- } else if end.Type() != nodeEnd {
- t.errorf("unexpected %s in %s", end, context)
- }
- block := t.newBlock(name.pos, t.lex.lineNumber(), name.val, bplist, pipe, list, contentList)
- t.passedBlocks[block.Name] = block
- return block
- }
- func (t *Template) parseYield() Node {
- const context = "yield clause"
- var (
- pipe Expression
- name item
- bplist *BlockParameterList
- content *ListNode
- end Node
- )
- // content yield {{yield content}}
- name = t.nextNonSpace()
- if name.typ == itemContent {
- if t.peekNonSpace().typ != itemRightDelim {
- pipe = t.expression(context)
- }
- t.expect(itemRightDelim, context)
- return t.newYield(name.pos, t.lex.lineNumber(), "", nil, pipe, nil, true)
- } else if name.typ != itemIdentifier {
- t.unexpected(name, context)
- }
- bplist = t.blockParametersList(false, context)
- typ := t.peekNonSpace().typ
- if typ != itemRightDelim {
- if typ == itemContent {
- t.nextNonSpace()
- t.expect(itemRightDelim, context)
- content, end = t.itemList()
- if end.Type() != nodeEnd {
- t.errorf("unexpected %s in %s", end, context)
- }
- } else {
- pipe = t.expression("yield")
- if t.peekNonSpace().typ == itemContent {
- t.nextNonSpace()
- t.expect(itemRightDelim, context)
- content, end = t.itemList()
- if end.Type() != nodeEnd {
- t.errorf("unexpected %s in %s", end, context)
- }
- } else {
- t.expect(itemRightDelim, context)
- }
- }
- } else {
- t.expect(itemRightDelim, context)
- }
- return t.newYield(name.pos, t.lex.lineNumber(), name.val, bplist, pipe, content, false)
- }
- func (t *Template) parseInclude() Node {
- var pipe Expression
- name := t.expression("include")
- if t.nextNonSpace().typ != itemRightDelim {
- t.backup()
- pipe = t.expression("include")
- t.expect(itemRightDelim, "include invocation")
- }
- return t.newInclude(name.Position(), t.lex.lineNumber(), name, pipe)
- }
- // itemListBlock:
- // textOrAction*
- // Terminates at {{end}} or {{else}}, returned separately.
- func (t *Template) itemListBlock() (list *ListNode, next Node) {
- list = t.newList(t.peekNonSpace().pos)
- for t.peekNonSpace().typ != itemEOF {
- n := t.textOrAction()
- switch n.Type() {
- case nodeEnd, nodeContent:
- return list, n
- }
- list.append(n)
- }
- t.errorf("unexpected EOF")
- return
- }
- // itemListControl:
- // textOrAction*
- // Terminates at {{end}}, returned separately.
- func (t *Template) 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, nodeContent:
- return list, n
- }
- list.append(n)
- }
- t.errorf("unexpected EOF")
- return
- }
- // textOrAction:
- // text | action
- func (t *Template) textOrAction() Node {
- switch token := t.nextNonSpace(); token.typ {
- case itemText:
- return t.newText(token.pos, token.val)
- case itemLeftDelim:
- return t.action()
- default:
- t.unexpected(token, "input")
- }
- return nil
- }
- func (t *Template) action() (n Node) {
- switch token := t.nextNonSpace(); token.typ {
- case itemElse:
- return t.elseControl()
- case itemEnd:
- return t.endControl()
- case itemContent:
- return t.contentControl()
- case itemIf:
- return t.ifControl()
- case itemRange:
- return t.rangeControl()
- case itemBlock:
- return t.parseBlock()
- case itemInclude:
- return t.parseInclude()
- case itemYield:
- return t.parseYield()
- }
- t.backup()
- action := t.newAction(t.peek().pos, t.lex.lineNumber())
- expr := t.assignmentOrExpression("command")
- if expr.Type() == NodeSet {
- action.Set = expr.(*SetNode)
- expr = nil
- }
- if action.Set == nil || t.expectOneOf(itemColonComma, itemRightDelim, "command").typ == itemColonComma {
- action.Pipe = t.pipeline("command", expr)
- }
- return action
- }
- func (t *Template) logicalExpression(context string) (Expression, item) {
- left, endtoken := t.comparativeExpression(context)
- for endtoken.typ == itemAnd || endtoken.typ == itemOr {
- right, rightendtoken := t.comparativeExpression(context)
- left, endtoken = t.newLogicalExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
- }
- return left, endtoken
- }
- func (t *Template) parseExpression(context string) (Expression, item) {
- expression, endtoken := t.logicalExpression(context)
- if endtoken.typ == itemTernary {
- var left, right Expression
- left, endtoken = t.parseExpression(context)
- if endtoken.typ != itemColon {
- t.unexpected(endtoken, "ternary expression")
- }
- right, endtoken = t.parseExpression(context)
- expression = t.newTernaryExpr(expression.Position(), t.lex.lineNumber(), expression, left, right)
- }
- return expression, endtoken
- }
- func (t *Template) comparativeExpression(context string) (Expression, item) {
- left, endtoken := t.numericComparativeExpression(context)
- for endtoken.typ == itemEquals || endtoken.typ == itemNotEquals {
- right, rightendtoken := t.numericComparativeExpression(context)
- left, endtoken = t.newComparativeExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
- }
- return left, endtoken
- }
- func (t *Template) numericComparativeExpression(context string) (Expression, item) {
- left, endtoken := t.additiveExpression(context)
- for endtoken.typ >= itemGreat && endtoken.typ <= itemLessEquals {
- right, rightendtoken := t.additiveExpression(context)
- left, endtoken = t.newNumericComparativeExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
- }
- return left, endtoken
- }
- func (t *Template) additiveExpression(context string) (Expression, item) {
- left, endtoken := t.multiplicativeExpression(context)
- for endtoken.typ == itemAdd || endtoken.typ == itemMinus {
- right, rightendtoken := t.multiplicativeExpression(context)
- left, endtoken = t.newAdditiveExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
- }
- return left, endtoken
- }
- func (t *Template) multiplicativeExpression(context string) (left Expression, endtoken item) {
- left, endtoken = t.unaryExpression(context)
- for endtoken.typ >= itemMul && endtoken.typ <= itemMod {
- right, rightendtoken := t.unaryExpression(context)
- left, endtoken = t.newMultiplicativeExpr(left.Position(), t.lex.lineNumber(), left, right, endtoken), rightendtoken
- }
- return left, endtoken
- }
- func (t *Template) unaryExpression(context string) (Expression, item) {
- next := t.nextNonSpace()
- switch next.typ {
- case itemNot:
- expr, endToken := t.comparativeExpression(context)
- return t.newNotExpr(expr.Position(), t.lex.lineNumber(), expr), endToken
- case itemMinus, itemAdd:
- return t.newAdditiveExpr(next.pos, t.lex.lineNumber(), nil, t.operand(), next), t.nextNonSpace()
- default:
- t.backup()
- }
- operand := t.operand()
- return operand, t.nextNonSpace()
- }
- func (t *Template) assignmentOrExpression(context string) (operand Expression) {
- t.peekNonSpace()
- line := t.lex.lineNumber()
- var right, left []Expression
- var isSet bool
- var isLet bool
- var returned item
- operand, returned = t.parseExpression(context)
- pos := operand.Position()
- if returned.typ == itemComma || returned.typ == itemAssign {
- isSet = true
- } else {
- if operand == nil {
- t.unexpected(returned, context)
- }
- t.backup()
- return operand
- }
- if isSet {
- leftloop:
- for {
- switch operand.Type() {
- case NodeField, NodeChain, NodeIdentifier:
- left = append(left, operand)
- default:
- t.errorf("unexpected node in assign")
- }
- switch returned.typ {
- case itemComma:
- operand, returned = t.parseExpression(context)
- case itemAssign:
- isLet = returned.val == ":="
- break leftloop
- default:
- t.unexpected(returned, "assignment")
- }
- }
- if isLet {
- for _, operand := range left {
- if operand.Type() != NodeIdentifier {
- t.errorf("unexpected node type %s in variable declaration", operand)
- }
- }
- }
- for {
- operand, returned = t.parseExpression("assignment")
- right = append(right, operand)
- if returned.typ != itemComma {
- t.backup()
- break
- }
- }
- var isIndexExprGetLookup bool
- if context == "range" {
- if len(left) > 2 || len(right) > 1 {
- t.errorf("unexpected number of operands in assign on range")
- }
- } else {
- if len(left) != len(right) {
- if len(left) == 2 && len(right) == 1 && right[0].Type() == NodeIndexExpr {
- isIndexExprGetLookup = true
- } else {
- t.errorf("unexpected number of operands in assign on range")
- }
- }
- }
- operand = t.newSet(pos, line, isLet, isIndexExprGetLookup, left, right)
- return
- }
- return
- }
- func (t *Template) expression(context string) Expression {
- expr, tk := t.parseExpression(context)
- if expr == nil {
- t.unexpected(tk, context)
- }
- t.backup()
- return expr
- }
- func (t *Template) pipeline(context string, baseExprMutate Expression) (pipe *PipeNode) {
- pos := t.peekNonSpace().pos
- pipe = t.newPipeline(pos, t.lex.lineNumber())
- var token item
- if baseExprMutate != nil {
- //special case
- pipe.append(t.command(baseExprMutate))
- token = t.nextNonSpace()
- if token.typ == itemPipe {
- token = t.nextNonSpace()
- } else {
- t.backup()
- t.expect(itemRightDelim, context)
- return
- }
- } else {
- token = t.nextNonSpace()
- }
- loop:
- for {
- switch token.typ {
- case itemBool, itemCharConstant, itemComplex, itemField, itemIdentifier,
- itemNumber, itemNil, itemRawString, itemString, itemLeftParen, itemNot:
- t.backup()
- pipe.append(t.command(nil))
- token = t.nextNonSpace()
- if token.typ == itemPipe {
- token = t.nextNonSpace()
- continue loop
- } else {
- t.backup()
- break loop
- }
- default:
- t.backup()
- break loop
- }
- }
- t.expect(itemRightDelim, context)
- return
- }
- func (t *Template) command(baseExpr Expression) *CommandNode {
- cmd := t.newCommand(t.peekNonSpace().pos)
- if baseExpr == nil {
- cmd.BaseExpr = t.expression("command")
- } else {
- cmd.BaseExpr = baseExpr
- }
- if t.nextNonSpace().typ == itemColon {
- cmd.Call = true
- cmd.Args = t.parseArguments()
- } else {
- t.backup()
- }
- if cmd.BaseExpr == nil {
- 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 *Template) operand() Expression {
- node := t.term()
- if node == nil {
- t.errorf("unexpected token %s on operand", t.next())
- }
- RESET:
- if t.peek().typ == itemField {
- chain := t.newChain(t.peek().pos, node)
- for t.peekNonSpace().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 NodeBool, NodeString, NodeNumber, NodeNil:
- t.errorf("unexpected . after term %q", node.String())
- default:
- node = chain
- }
- }
- nodeTYPE := node.Type()
- if nodeTYPE == NodeIdentifier ||
- nodeTYPE == NodeCallExpr ||
- nodeTYPE == NodeField ||
- nodeTYPE == NodeChain ||
- nodeTYPE == NodeIndexExpr {
- switch t.nextNonSpace().typ {
- case itemLeftParen:
- callExpr := t.newCallExpr(node.Position(), t.lex.lineNumber(), node)
- callExpr.Args = t.parseArguments()
- t.expect(itemRightParen, "call expression")
- node = callExpr
- goto RESET
- case itemLeftBrackets:
- base := node
- var index Expression
- var next item
- //found colon is slice expression
- if t.peekNonSpace().typ != itemColon {
- index, next = t.parseExpression("index|slice expression")
- } else {
- next = t.nextNonSpace()
- }
- switch next.typ {
- case itemColon:
- var lenexpr Expression
- if t.peekNonSpace().typ != itemRightBrackets {
- lenexpr = t.expression("index expression")
- }
- node = t.newSliceExpr(node.Position(), node.line(), base, index, lenexpr)
- case itemRightBrackets:
- node = t.newIndexExpr(node.Position(), node.line(), base, index)
- fallthrough
- default:
- t.backup()
- }
- t.expect(itemRightBrackets, "index expression")
- goto RESET
- default:
- t.backup()
- }
- }
- return node
- }
- func (t *Template) parseArguments() (args []Expression) {
- if t.peekNonSpace().typ != itemRightParen {
- loop:
- for {
- expr, endtoken := t.parseExpression("call expression")
- args = append(args, expr)
- switch endtoken.typ {
- case itemComma:
- continue loop
- default:
- t.backup()
- break loop
- }
- }
- }
- return
- }
- func (t *Template) 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, 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 *Template) parseControl(allowElseIf bool, context string) (pos Pos, line int, set *SetNode, expression Expression, list, elseList *ListNode) {
- line = t.lex.lineNumber()
- expression = t.assignmentOrExpression(context)
- pos = expression.Position()
- if expression.Type() == NodeSet {
- set = expression.(*SetNode)
- if context != "range" {
- t.expect(itemColonComma, context)
- expression = t.expression(context)
- } else {
- expression = nil
- }
- }
- t.expect(itemRightDelim, context)
- var next Node
- list, next = t.itemList()
- 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.
- 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 pos, line, set, expression, list, elseList
- }
- // If:
- // {{if expression}} itemList {{end}}
- // {{if expression}} itemList {{else}} itemList {{end}}
- // If keyword is past.
- func (t *Template) ifControl() Node {
- return t.newIf(t.parseControl(true, "if"))
- }
- // Range:
- // {{range expression}} itemList {{end}}
- // {{range expression}} itemList {{else}} itemList {{end}}
- // Range keyword is past.
- func (t *Template) rangeControl() Node {
- return t.newRange(t.parseControl(false, "range"))
- }
- // End:
- // {{end}}
- // End keyword is past.
- func (t *Template) endControl() Node {
- return t.newEnd(t.expect(itemRightDelim, "end").pos)
- }
- // Content:
- // {{content}}
- // Content keyword is past.
- func (t *Template) contentControl() Node {
- return t.newContent(t.expect(itemRightDelim, "content").pos)
- }
- // Else:
- // {{else}}
- // Else keyword is past.
- func (t *Template) 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, t.lex.lineNumber())
- }
- return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
- }
- // term:
- // literal (number, string, nil, boolean)
- // function (identifier)
- // .
- // .Field
- // variable
- // '(' expression ')'
- // A term is a simple "expression".
- // A nil return means the next item is not a term.
- func (t *Template) term() Node {
- switch token := t.nextNonSpace(); token.typ {
- case itemError:
- t.errorf("%s", token.val)
- case itemIdentifier:
- return t.newIdentifier(token.val, token.pos, t.lex.lineNumber())
- case itemNil:
- return t.newNil(token.pos)
- 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:
- pipe := t.expression("parenthesized expression")
- if token := t.next(); token.typ != itemRightParen {
- t.errorf("unclosed right paren: unexpected %s", token)
- }
- return pipe
- case itemString, itemRawString:
- s, err := unquote(token.val)
- if err != nil {
- t.error(err)
- }
- return t.newString(token.pos, token.val, s)
- }
- t.backup()
- return nil
- }
|