Imports #
"fmt"
"strings"
"unicode"
"unicode/utf8"
"fmt"
"strconv"
"strings"
"bytes"
"fmt"
"runtime"
"strconv"
"strings"
"fmt"
"strings"
"unicode"
"unicode/utf8"
"fmt"
"strconv"
"strings"
"bytes"
"fmt"
"runtime"
"strconv"
"strings"
const NodeAction
const NodeBool
const NodeBreak
const NodeChain
const NodeCommand
const NodeComment
const NodeContinue
const NodeDot
const NodeField
const NodeIdentifier
const NodeIf
const NodeList
const NodeNil
const NodeNumber
const NodePipe
const NodeRange
const NodeString
const NodeTemplate
const NodeText NodeType = iota
const NodeVariable
const NodeWith
const ParseComments Mode = *ast.BinaryExpr
const SkipFuncCheck
const eof = *ast.UnaryExpr
const itemAssign
const itemBlock
const itemBool
const itemBreak
const itemChar
const itemCharConstant
const itemComment
const itemComplex
const itemContinue
const itemDeclare
const itemDefine
const itemDot
const itemEOF
const itemElse
const itemEnd
const itemError itemType = iota
const itemField
const itemIdentifier
const itemIf
Keywords appear after all the rest.
const itemKeyword
const itemLeftDelim
const itemLeftParen
const itemNil
const itemNumber
const itemPipe
const itemRange
const itemRawString
const itemRightDelim
const itemRightParen
const itemSpace
const itemString
const itemTemplate
const itemText
const itemVariable
const itemWith
var key = map[string]itemType{...}
const leftComment = "/*"
const leftDelim = "{{"
const nodeElse
const nodeEnd
const rightComment = "*/"
const rightDelim = "}}"
Trimming spaces. If the action begins "{{- " rather than "{{", then all space/tab/newlines preceding the action are trimmed; conversely if it ends " -}}" the leading spaces are trimmed. This is done entirely in the lexer; the parser never sees it happen. We require an ASCII space (' ', \t, \r, \n) to be present to avoid ambiguity with things like "{{-3}}". It reads better with the space present anyway. For simplicity, only ASCII does the job.
const spaceChars = " \t\r\n"
var textFormat = "%s"
Trimming spaces. If the action begins "{{- " rather than "{{", then all space/tab/newlines preceding the action are trimmed; conversely if it ends " -}}" the leading spaces are trimmed. This is done entirely in the lexer; the parser never sees it happen. We require an ASCII space (' ', \t, \r, \n) to be present to avoid ambiguity with things like "{{-3}}". It reads better with the space present anyway. For simplicity, only ASCII does the job.
const trimMarker = '-'
Trimming spaces. If the action begins "{{- " rather than "{{", then all space/tab/newlines preceding the action are trimmed; conversely if it ends " -}}" the leading spaces are trimmed. This is done entirely in the lexer; the parser never sees it happen. We require an ASCII space (' ', \t, \r, \n) to be present to avoid ambiguity with things like "{{-3}}". It reads better with the space present anyway. For simplicity, only ASCII does the job.
const trimMarkerLen = *ast.CallExpr
A mode value is a set of flags (or 0). Modes control parser behavior.
type Mode uint
NodeType identifies the type of a parse tree node.
type NodeType int
Pos represents a byte position in the original input text from which this template was parsed.
type Pos int
itemType identifies the type of lex items.
type itemType int
stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*lexer) stateFn
A Node is an element in the parse tree. The interface is trivial. The interface contains an unexported method so that only types local to this package can satisfy it.
type Node interface {
Type() NodeType
String() string
Copy() Node
Position() Pos
tree() *Tree
writeTo(*strings.Builder)
}
ActionNode holds an action (something bounded by delimiters). Control actions have their own nodes; ActionNode represents simple ones such as field evaluations and parenthesized pipelines.
type ActionNode struct {
NodeType
Pos
tr *Tree
Line int
Pipe *PipeNode
}
BoolNode holds a boolean constant.
type BoolNode struct {
NodeType
Pos
tr *Tree
True bool
}
BranchNode is the common representation of if, range, and with.
type BranchNode struct {
NodeType
Pos
tr *Tree
Line int
Pipe *PipeNode
List *ListNode
ElseList *ListNode
}
BreakNode represents a {{break}} action.
type BreakNode struct {
tr *Tree
NodeType
Pos
Line int
}
ChainNode holds a term followed by a chain of field accesses (identifier starting with '.'). The names may be chained ('.x.y'). The periods are dropped from each ident.
type ChainNode struct {
NodeType
Pos
tr *Tree
Node Node
Field []string
}
CommandNode holds a command (a pipeline inside an evaluating action).
type CommandNode struct {
NodeType
Pos
tr *Tree
Args []Node
}
CommentNode holds a comment.
type CommentNode struct {
NodeType
Pos
tr *Tree
Text string
}
ContinueNode represents a {{continue}} action.
type ContinueNode struct {
tr *Tree
NodeType
Pos
Line int
}
DotNode holds the special identifier '.'.
type DotNode struct {
NodeType
Pos
tr *Tree
}
FieldNode holds a field (identifier starting with '.'). The names may be chained ('.x.y'). The period is dropped from each ident.
type FieldNode struct {
NodeType
Pos
tr *Tree
Ident []string
}
IdentifierNode holds an identifier.
type IdentifierNode struct {
NodeType
Pos
tr *Tree
Ident string
}
IfNode represents an {{if}} action and its commands.
type IfNode struct {
BranchNode
}
ListNode holds a sequence of nodes.
type ListNode struct {
NodeType
Pos
tr *Tree
Nodes []Node
}
NilNode holds the special identifier 'nil' representing an untyped nil constant.
type NilNode struct {
NodeType
Pos
tr *Tree
}
NumberNode holds a number: signed or unsigned integer, float, or complex. The value is parsed and stored under all the types that can represent the value. This simulates in a small amount of code the behavior of Go's ideal constants.
type NumberNode struct {
NodeType
Pos
tr *Tree
IsInt bool
IsUint bool
IsFloat bool
IsComplex bool
Int64 int64
Uint64 uint64
Float64 float64
Complex128 complex128
Text string
}
PipeNode holds a pipeline with optional declaration
type PipeNode struct {
NodeType
Pos
tr *Tree
Line int
IsAssign bool
Decl []*VariableNode
Cmds []*CommandNode
}
RangeNode represents a {{range}} action and its commands.
type RangeNode struct {
BranchNode
}
StringNode holds a string constant. The value has been "unquoted".
type StringNode struct {
NodeType
Pos
tr *Tree
Quoted string
Text string
}
TemplateNode represents a {{template}} action.
type TemplateNode struct {
NodeType
Pos
tr *Tree
Line int
Name string
Pipe *PipeNode
}
TextNode holds plain text.
type TextNode struct {
NodeType
Pos
tr *Tree
Text []byte
}
Tree is the representation of a single parsed template.
type Tree struct {
Name string
ParseName string
Root *ListNode
Mode Mode
text string
funcs []map[string]any
lex *lexer
token [3]item
peekCount int
vars []string
treeSet map[string]*Tree
actionLine int
rangeDepth int
}
VariableNode holds a list of variable names, possibly with chained field accesses. The dollar sign is part of the (first) name.
type VariableNode struct {
NodeType
Pos
tr *Tree
Ident []string
}
WithNode represents a {{with}} action and its commands.
type WithNode struct {
BranchNode
}
elseNode represents an {{else}} action. Does not appear in the final tree.
type elseNode struct {
NodeType
Pos
tr *Tree
Line int
}
endNode represents an {{end}} action. It does not appear in the final parse tree.
type endNode struct {
NodeType
Pos
tr *Tree
}
item represents a token or text string returned from the scanner.
type item struct {
typ itemType
pos Pos
val string
line int
}
lexOptions control behavior of the lexer. All default to false.
type lexOptions struct {
emitComment bool
breakOK bool
continueOK bool
}
lexer holds the state of the scanner.
type lexer struct {
name string
input string
leftDelim string
rightDelim string
pos Pos
start Pos
atEOF bool
parenDepth int
line int
startLine int
item item
insideAction bool
options lexOptions
}
Add adds the named field (which should start with a period) to the end of the chain.
func (c *ChainNode) Add(field string)
func (w *WithNode) Copy() Node
Copy returns a copy of the [Tree]. Any parsing state is discarded.
func (t *Tree) Copy() *Tree
func (v *VariableNode) Copy() Node
func (n *NilNode) Copy() Node
func (f *FieldNode) Copy() Node
func (i *IdentifierNode) Copy() Node
func (b *BreakNode) Copy() Node
func (c *ChainNode) Copy() Node
func (c *CommandNode) Copy() Node
func (b *BoolNode) Copy() Node
func (a *ActionNode) Copy() Node
func (n *NumberNode) Copy() Node
func (s *StringNode) Copy() Node
func (p *PipeNode) Copy() Node
func (e *endNode) Copy() Node
func (c *CommentNode) Copy() Node
func (e *elseNode) Copy() Node
func (t *TextNode) Copy() Node
func (b *BranchNode) Copy() Node
func (i *IfNode) Copy() Node
func (l *ListNode) Copy() Node
func (d *DotNode) Copy() Node
func (c *ContinueNode) Copy() Node
func (r *RangeNode) Copy() Node
func (t *TemplateNode) Copy() Node
func (l *ListNode) CopyList() *ListNode
func (p *PipeNode) CopyPipe() *PipeNode
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 string, context string)
IsEmptyTree reports whether this tree (node) is empty of everything but space or comments.
func IsEmptyTree(n Node) bool
New allocates a new parse tree with the given name.
func New(name string, funcs ...map[string]any) *Tree
NewIdentifier returns a new [IdentifierNode] with the given identifier name.
func NewIdentifier(ident string) *IdentifierNode
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 string, leftDelim string, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]any) (tree *Tree, err error)
Parse returns a map from template name to [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 string, text string, leftDelim string, rightDelim string, funcs ...map[string]any) (map[string]*Tree, error)
func (p Pos) Position() Pos
SetPos sets the position. [NewIdentifier] is a public method so we can't modify its signature. Chained for convenience. TODO: fix one day?
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode
SetTree sets the parent tree for the node. [NewIdentifier] is a public method so we can't modify its signature. Chained for convenience. TODO: fix one day?
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode
func (b *BoolNode) String() string
func (e *endNode) String() string
func (f *FieldNode) String() string
func (c *ContinueNode) String() string
func (l *ListNode) String() string
func (s *StringNode) String() string
func (c *ChainNode) String() string
func (a *ActionNode) String() string
func (b *BreakNode) String() string
func (t *TextNode) String() string
func (i *IdentifierNode) String() string
func (i item) String() string
func (d *DotNode) String() string
func (b *BranchNode) String() string
func (c *CommentNode) String() string
func (n *NumberNode) String() string
func (e *elseNode) String() string
func (n *NilNode) String() string
func (t *TemplateNode) String() string
func (v *VariableNode) String() string
func (p *PipeNode) String() string
func (c *CommandNode) String() string
func (e *elseNode) Type() NodeType
func (d *DotNode) Type() NodeType
Type returns itself and provides an easy default implementation for embedding in a Node. Embedded in all non-trivial Nodes.
func (t NodeType) Type() NodeType
func (n *NilNode) Type() NodeType
accept consumes the next rune if it's from the valid set.
func (l *lexer) accept(valid string) bool
acceptRun consumes a run of runes from the valid set.
func (l *lexer) acceptRun(valid string)
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)
add adds tree to t.treeSet.
func (t *Tree) add()
func (c *CommandNode) append(arg Node)
func (p *PipeNode) append(command *CommandNode)
func (l *ListNode) append(n Node)
atRightDelim reports whether the lexer is at a right delimiter, possibly preceded by a trim marker.
func (l *lexer) atRightDelim() (delim bool, trimSpaces bool)
atTerminator reports whether the input is at valid termination character to appear after an identifier. Breaks .X.Y into two pieces. Also catches cases like "$x+2" not being acceptable without a space, in case we decide one day to implement arithmetic.
func (l *lexer) atTerminator() bool
backup steps back one rune.
func (l *lexer) backup()
backup backs the input stream up one token.
func (t *Tree) backup()
backup2 backs the input stream up two tokens. The zeroth token is already there.
func (t *Tree) backup2(t1 item)
backup3 backs the input stream up three tokens The zeroth token is already there.
func (t *Tree) backup3(t2 item, t1 item)
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
Break: {{break}} Break keyword is past.
func (t *Tree) breakControl(pos Pos, line int) Node
func (t *Tree) checkPipeline(pipe *PipeNode, context string)
func (t *Tree) clearActionLine()
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
Continue: {{continue}} Continue keyword is past.
func (t *Tree) continueControl(pos Pos, line int) Node
Else: {{else}} Else keyword is past.
func (t *Tree) elseControl() Node
emit passes the trailing text as an item back to the parser.
func (l *lexer) emit(t itemType) stateFn
emitItem passes the specified item to the parser.
func (l *lexer) emitItem(i item) stateFn
End: {{end}} End keyword is past.
func (t *Tree) endControl() Node
error terminates processing.
func (t *Tree) error(err error)
errorf returns an error token and terminates the scan by passing back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...any) stateFn
errorf formats the error and terminates processing.
func (t *Tree) errorf(format string, args ...any)
expect consumes the next token and guarantees it has the required type.
func (t *Tree) expect(expected itemType, context string) item
expectOneOf consumes the next token and guarantees it has one of the required types.
func (t *Tree) expectOneOf(expected1 itemType, expected2 itemType, context string) item
hasFunction reports if a function name exists in the Tree's maps.
func (t *Tree) hasFunction(name string) bool
func hasLeftTrimMarker(s string) bool
func hasRightTrimMarker(s string) bool
If: {{if pipeline}} itemList {{end}} {{if pipeline}} itemList {{else}} itemList {{end}} If keyword is past.
func (t *Tree) ifControl() Node
ignore skips over the pending input before this point. It tracks newlines in the ignored text, so use it only for text that is skipped without calling l.next.
func (l *lexer) ignore()
isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
func isAlphaNumeric(r rune) bool
isSpace reports whether r is a space character.
func isSpace(r rune) bool
itemList: textOrAction* Terminates at {{end}} or {{else}}, returned separately.
func (t *Tree) itemList() (list *ListNode, next Node)
leftTrimLength returns the length of the spaces at the beginning of the string.
func leftTrimLength(s string) Pos
lex creates a new scanner for the input string.
func lex(name string, input string, left string, right string) *lexer
lexChar scans a character constant. The initial quote is already scanned. Syntax checking is done by the parser.
func lexChar(l *lexer) stateFn
lexComment scans a comment. The left comment marker is known to be present.
func lexComment(l *lexer) stateFn
lexField scans a field: .Alphanumeric. The . has been scanned.
func lexField(l *lexer) stateFn
lexFieldOrVariable scans a field or variable: [.$]Alphanumeric. The . or $ has been scanned.
func lexFieldOrVariable(l *lexer, typ itemType) stateFn
lexIdentifier scans an alphanumeric.
func lexIdentifier(l *lexer) stateFn
lexInsideAction scans the elements inside action delimiters.
func lexInsideAction(l *lexer) stateFn
lexLeftDelim scans the left delimiter, which is known to be present, possibly with a trim marker. (The text to be trimmed has already been emitted.)
func lexLeftDelim(l *lexer) stateFn
lexNumber scans a number: decimal, octal, hex, float, or imaginary. This isn't a perfect number scanner - for instance it accepts "." and "0x0.2" and "089" - but when it's wrong the input is invalid and the parser (via strconv) will notice.
func lexNumber(l *lexer) stateFn
lexQuote scans a quoted string.
func lexQuote(l *lexer) stateFn
lexRawQuote scans a raw quoted string.
func lexRawQuote(l *lexer) stateFn
lexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker.
func lexRightDelim(l *lexer) stateFn
lexSpace scans a run of space characters. We have not consumed the first space, which is known to be present. Take care if there is a trim-marked right delimiter, which starts with a space.
func lexSpace(l *lexer) stateFn
lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn
lexVariable scans a Variable: $Alphanumeric. The $ has been scanned.
func lexVariable(l *lexer) stateFn
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode
func (t *Tree) newBool(pos Pos, true bool) *BoolNode
func (t *Tree) newBreak(pos Pos, line int) *BreakNode
func (t *Tree) newChain(pos Pos, node Node) *ChainNode
func (t *Tree) newCommand(pos Pos) *CommandNode
func (t *Tree) newComment(pos Pos, text string) *CommentNode
func (t *Tree) newContinue(pos Pos, line int) *ContinueNode
func (t *Tree) newDot(pos Pos) *DotNode
func (t *Tree) newElse(pos Pos, line int) *elseNode
func (t *Tree) newEnd(pos Pos) *endNode
func (t *Tree) newField(pos Pos, ident string) *FieldNode
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list *ListNode, elseList *ListNode) *IfNode
func (t *Tree) newList(pos Pos) *ListNode
func (t *Tree) newNil(pos Pos) *NilNode
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error)
func (t *Tree) newPipeline(pos Pos, line int, vars []*VariableNode) *PipeNode
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list *ListNode, elseList *ListNode) *RangeNode
func (t *Tree) newString(pos Pos, orig string, text string) *StringNode
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode
func (t *Tree) newText(pos Pos, text string) *TextNode
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list *ListNode, elseList *ListNode) *WithNode
next returns the next token.
func (t *Tree) next() item
next returns the next rune in the input.
func (l *lexer) next() rune
nextItem returns the next item from the input. Called by the parser, not in the lexing goroutine.
func (l *lexer) nextItem() item
nextNonSpace returns the next non-space token.
func (t *Tree) nextNonSpace() (token item)
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
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()
func (t *Tree) parseControl(context string) (pos Pos, line int, pipe *PipeNode, list *ListNode, elseList *ListNode)
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()
func (t *Tree) parseTemplateName(token item, context string) (name string)
peek returns but does not consume the next rune in the input.
func (l *lexer) peek() rune
peek returns but does not consume the next token.
func (t *Tree) peek() item
peekNonSpace returns but does not consume the next non-space token.
func (t *Tree) peekNonSpace() item
Pipeline: declarations? command ('|' command)*
func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode)
popVars trims the variable list to the specified length
func (t *Tree) popVars(n int)
Range: {{range pipeline}} itemList {{end}} {{range pipeline}} itemList {{else}} itemList {{end}} Range keyword is past.
func (t *Tree) rangeControl() Node
recover is the handler that turns panics into returns from the top level of Parse.
func (t *Tree) recover(errp *error)
rightTrimLength returns the length of the spaces at the end of the string.
func rightTrimLength(s string) Pos
func (l *lexer) scanNumber() bool
simplifyComplex pulls out any other types that are represented by the complex number. These all require that the imaginary part be zero.
func (n *NumberNode) simplifyComplex()
startParse initializes the parser, using the lexer.
func (t *Tree) startParse(funcs []map[string]any, lex *lexer, treeSet map[string]*Tree)
stopParse terminates parsing.
func (t *Tree) stopParse()
Template: {{template stringValue pipeline}} Template keyword is past. The name must be something that can evaluate to a string.
func (t *Tree) templateControl() 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
textOrAction: text | comment | action
func (t *Tree) textOrAction() Node
thisItem returns the item at the current input point with the specified type and advances the input.
func (l *lexer) thisItem(t itemType) item
func (a *ActionNode) tree() *Tree
func (s *StringNode) tree() *Tree
func (n *NilNode) tree() *Tree
func (v *VariableNode) tree() *Tree
func (c *ContinueNode) tree() *Tree
func (l *ListNode) tree() *Tree
func (f *FieldNode) tree() *Tree
func (i *IdentifierNode) tree() *Tree
func (b *BreakNode) tree() *Tree
func (c *ChainNode) tree() *Tree
func (c *CommandNode) tree() *Tree
func (t *TextNode) tree() *Tree
func (b *BranchNode) tree() *Tree
func (b *BoolNode) tree() *Tree
func (n *NumberNode) tree() *Tree
func (d *DotNode) tree() *Tree
func (e *elseNode) tree() *Tree
func (t *TemplateNode) tree() *Tree
func (c *CommentNode) tree() *Tree
func (p *PipeNode) tree() *Tree
func (e *endNode) tree() *Tree
unexpected complains about the token and terminates processing.
func (t *Tree) unexpected(token item, context string)
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
With: {{with pipeline}} itemList {{end}} {{with pipeline}} itemList {{else}} itemList {{end}} If keyword is past.
func (t *Tree) withControl() Node
func (b *BranchNode) writeTo(sb *strings.Builder)
func (c *ChainNode) writeTo(sb *strings.Builder)
func (c *CommentNode) writeTo(sb *strings.Builder)
func (e *endNode) writeTo(sb *strings.Builder)
func (n *NumberNode) writeTo(sb *strings.Builder)
func (c *CommandNode) writeTo(sb *strings.Builder)
func (s *StringNode) writeTo(sb *strings.Builder)
func (b *BoolNode) writeTo(sb *strings.Builder)
func (t *TextNode) writeTo(sb *strings.Builder)
func (e *elseNode) writeTo(sb *strings.Builder)
func (i *IdentifierNode) writeTo(sb *strings.Builder)
func (a *ActionNode) writeTo(sb *strings.Builder)
func (b *BreakNode) writeTo(sb *strings.Builder)
func (l *ListNode) writeTo(sb *strings.Builder)
func (f *FieldNode) writeTo(sb *strings.Builder)
func (v *VariableNode) writeTo(sb *strings.Builder)
func (c *ContinueNode) writeTo(sb *strings.Builder)
func (t *TemplateNode) writeTo(sb *strings.Builder)
func (n *NilNode) writeTo(sb *strings.Builder)
func (d *DotNode) writeTo(sb *strings.Builder)
func (p *PipeNode) writeTo(sb *strings.Builder)
Generated with Arrow