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 NodeActionconst NodeBoolconst NodeBreakconst NodeChainconst NodeCommandconst NodeCommentconst NodeContinueconst NodeDotconst NodeFieldconst NodeIdentifierconst NodeIfconst NodeListconst NodeNilconst NodeNumberconst NodePipeconst NodeRangeconst NodeStringconst NodeTemplateconst NodeText NodeType = iotaconst NodeVariableconst NodeWithconst ParseComments Mode = *ast.BinaryExprconst SkipFuncCheckconst eof = *ast.UnaryExprconst itemAssignconst itemBlockconst itemBoolconst itemBreakconst itemCharconst itemCharConstantconst itemCommentconst itemComplexconst itemContinueconst itemDeclareconst itemDefineconst itemDotconst itemEOFconst itemElseconst itemEndconst itemError itemType = iotaconst itemFieldconst itemIdentifierconst itemIfKeywords appear after all the rest.
const itemKeywordconst itemLeftDelimconst itemLeftParenconst itemNilconst itemNumberconst itemPipeconst itemRangeconst itemRawStringconst itemRightDelimconst itemRightParenconst itemSpaceconst itemStringconst itemTemplateconst itemTextconst itemVariableconst itemWithvar key = map[string]itemType{...}const leftComment = "/*"const leftDelim = "{{"const nodeElseconst nodeEndconst 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.CallExprA mode value is a set of flags (or 0). Modes control parser behavior.
type Mode uintNodeType identifies the type of a parse tree node.
type NodeType intPos represents a byte position in the original input text from which this template was parsed.
type Pos intitemType identifies the type of lex items.
type itemType intstateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*lexer) stateFnA 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() NodeCopy returns a copy of the [Tree]. Any parsing state is discarded.
func (t *Tree) Copy() *Treefunc (v *VariableNode) Copy() Nodefunc (n *NilNode) Copy() Nodefunc (f *FieldNode) Copy() Nodefunc (i *IdentifierNode) Copy() Nodefunc (b *BreakNode) Copy() Nodefunc (c *ChainNode) Copy() Nodefunc (c *CommandNode) Copy() Nodefunc (b *BoolNode) Copy() Nodefunc (a *ActionNode) Copy() Nodefunc (n *NumberNode) Copy() Nodefunc (s *StringNode) Copy() Nodefunc (p *PipeNode) Copy() Nodefunc (e *endNode) Copy() Nodefunc (c *CommentNode) Copy() Nodefunc (e *elseNode) Copy() Nodefunc (t *TextNode) Copy() Nodefunc (b *BranchNode) Copy() Nodefunc (i *IfNode) Copy() Nodefunc (l *ListNode) Copy() Nodefunc (d *DotNode) Copy() Nodefunc (c *ContinueNode) Copy() Nodefunc (r *RangeNode) Copy() Nodefunc (t *TemplateNode) Copy() Nodefunc (l *ListNode) CopyList() *ListNodefunc (p *PipeNode) CopyPipe() *PipeNodeErrorContext 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) boolNew allocates a new parse tree with the given name.
func New(name string, funcs ...map[string]any) *TreeNewIdentifier returns a new [IdentifierNode] with the given identifier name.
func NewIdentifier(ident string) *IdentifierNodeParse 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() PosSetPos 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) *IdentifierNodeSetTree 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) *IdentifierNodefunc (b *BoolNode) String() stringfunc (e *endNode) String() stringfunc (f *FieldNode) String() stringfunc (c *ContinueNode) String() stringfunc (l *ListNode) String() stringfunc (s *StringNode) String() stringfunc (c *ChainNode) String() stringfunc (a *ActionNode) String() stringfunc (b *BreakNode) String() stringfunc (t *TextNode) String() stringfunc (i *IdentifierNode) String() stringfunc (i item) String() stringfunc (d *DotNode) String() stringfunc (b *BranchNode) String() stringfunc (c *CommentNode) String() stringfunc (n *NumberNode) String() stringfunc (e *elseNode) String() stringfunc (n *NilNode) String() stringfunc (t *TemplateNode) String() stringfunc (v *VariableNode) String() stringfunc (p *PipeNode) String() stringfunc (c *CommandNode) String() stringfunc (e *elseNode) Type() NodeTypefunc (d *DotNode) Type() NodeTypeType returns itself and provides an easy default implementation for embedding in a Node. Embedded in all non-trivial Nodes.
func (t NodeType) Type() NodeTypefunc (n *NilNode) Type() NodeTypeaccept consumes the next rune if it's from the valid set.
func (l *lexer) accept(valid string) boolacceptRun 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() boolbackup 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() NodeBreak: {{break}} Break keyword is past.
func (t *Tree) breakControl(pos Pos, line int) Nodefunc (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() *CommandNodeContinue: {{continue}} Continue keyword is past.
func (t *Tree) continueControl(pos Pos, line int) NodeElse: {{else}} Else keyword is past.
func (t *Tree) elseControl() Nodeemit passes the trailing text as an item back to the parser.
func (l *lexer) emit(t itemType) stateFnemitItem passes the specified item to the parser.
func (l *lexer) emitItem(i item) stateFnEnd: {{end}} End keyword is past.
func (t *Tree) endControl() Nodeerror 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) stateFnerrorf 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) itemexpectOneOf consumes the next token and guarantees it has one of the required types.
func (t *Tree) expectOneOf(expected1 itemType, expected2 itemType, context string) itemhasFunction reports if a function name exists in the Tree's maps.
func (t *Tree) hasFunction(name string) boolfunc hasLeftTrimMarker(s string) boolfunc hasRightTrimMarker(s string) boolIf: {{if pipeline}} itemList {{end}} {{if pipeline}} itemList {{else}} itemList {{end}} If keyword is past.
func (t *Tree) ifControl() Nodeignore 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) boolisSpace reports whether r is a space character.
func isSpace(r rune) boolitemList: 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) Poslex creates a new scanner for the input string.
func lex(name string, input string, left string, right string) *lexerlexChar scans a character constant. The initial quote is already scanned. Syntax checking is done by the parser.
func lexChar(l *lexer) stateFnlexComment scans a comment. The left comment marker is known to be present.
func lexComment(l *lexer) stateFnlexField scans a field: .Alphanumeric. The . has been scanned.
func lexField(l *lexer) stateFnlexFieldOrVariable scans a field or variable: [.$]Alphanumeric. The . or $ has been scanned.
func lexFieldOrVariable(l *lexer, typ itemType) stateFnlexIdentifier scans an alphanumeric.
func lexIdentifier(l *lexer) stateFnlexInsideAction scans the elements inside action delimiters.
func lexInsideAction(l *lexer) stateFnlexLeftDelim 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) stateFnlexNumber 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) stateFnlexQuote scans a quoted string.
func lexQuote(l *lexer) stateFnlexRawQuote scans a raw quoted string.
func lexRawQuote(l *lexer) stateFnlexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker.
func lexRightDelim(l *lexer) stateFnlexSpace 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) stateFnlexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFnlexVariable scans a Variable: $Alphanumeric. The $ has been scanned.
func lexVariable(l *lexer) stateFnfunc (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNodefunc (t *Tree) newBool(pos Pos, true bool) *BoolNodefunc (t *Tree) newBreak(pos Pos, line int) *BreakNodefunc (t *Tree) newChain(pos Pos, node Node) *ChainNodefunc (t *Tree) newCommand(pos Pos) *CommandNodefunc (t *Tree) newComment(pos Pos, text string) *CommentNodefunc (t *Tree) newContinue(pos Pos, line int) *ContinueNodefunc (t *Tree) newDot(pos Pos) *DotNodefunc (t *Tree) newElse(pos Pos, line int) *elseNodefunc (t *Tree) newEnd(pos Pos) *endNodefunc (t *Tree) newField(pos Pos, ident string) *FieldNodefunc (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list *ListNode, elseList *ListNode) *IfNodefunc (t *Tree) newList(pos Pos) *ListNodefunc (t *Tree) newNil(pos Pos) *NilNodefunc (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error)func (t *Tree) newPipeline(pos Pos, line int, vars []*VariableNode) *PipeNodefunc (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list *ListNode, elseList *ListNode) *RangeNodefunc (t *Tree) newString(pos Pos, orig string, text string) *StringNodefunc (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNodefunc (t *Tree) newText(pos Pos, text string) *TextNodefunc (t *Tree) newVariable(pos Pos, ident string) *VariableNodefunc (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list *ListNode, elseList *ListNode) *WithNodenext returns the next token.
func (t *Tree) next() itemnext returns the next rune in the input.
func (l *lexer) next() runenextItem returns the next item from the input. Called by the parser, not in the lexing goroutine.
func (l *lexer) nextItem() itemnextNonSpace 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() Nodeparse 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() runepeek returns but does not consume the next token.
func (t *Tree) peek() itempeekNonSpace returns but does not consume the next non-space token.
func (t *Tree) peekNonSpace() itemPipeline: 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() Noderecover 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) Posfunc (l *lexer) scanNumber() boolsimplifyComplex 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() Nodeterm: 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() NodetextOrAction: text | comment | action
func (t *Tree) textOrAction() NodethisItem returns the item at the current input point with the specified type and advances the input.
func (l *lexer) thisItem(t itemType) itemfunc (a *ActionNode) tree() *Treefunc (s *StringNode) tree() *Treefunc (n *NilNode) tree() *Treefunc (v *VariableNode) tree() *Treefunc (c *ContinueNode) tree() *Treefunc (l *ListNode) tree() *Treefunc (f *FieldNode) tree() *Treefunc (i *IdentifierNode) tree() *Treefunc (b *BreakNode) tree() *Treefunc (c *ChainNode) tree() *Treefunc (c *CommandNode) tree() *Treefunc (t *TextNode) tree() *Treefunc (b *BranchNode) tree() *Treefunc (b *BoolNode) tree() *Treefunc (n *NumberNode) tree() *Treefunc (d *DotNode) tree() *Treefunc (e *elseNode) tree() *Treefunc (t *TemplateNode) tree() *Treefunc (c *CommentNode) tree() *Treefunc (p *PipeNode) tree() *Treefunc (e *endNode) tree() *Treeunexpected 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) NodeWith: {{with pipeline}} itemList {{end}} {{with pipeline}} itemList {{else}} itemList {{end}} If keyword is past.
func (t *Tree) withControl() Nodefunc (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