988 lignes
25 Kio
Go
988 lignes
25 Kio
Go
|
// 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
|
||
|
}
|