package parse
import (
"bytes"
"fmt"
"runtime"
"strconv"
"strings"
)
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
}
type Mode uint
const (
ParseComments Mode = 1 << iota
SkipFuncCheck
)
func (t *Tree ) Copy () *Tree {
if t == nil {
return nil
}
return &Tree {
Name : t .Name ,
ParseName : t .ParseName ,
Root : t .Root .CopyList (),
text : t .text ,
}
}
func Parse (name , text , leftDelim , rightDelim string , funcs ...map [string ]any ) (map [string ]*Tree , error ) {
treeSet := make (map [string ]*Tree )
t := New (name )
t .text = text
_ , err := t .Parse (text , leftDelim , rightDelim , treeSet , funcs ...)
return treeSet , err
}
func (t *Tree ) next () item {
if t .peekCount > 0 {
t .peekCount --
} else {
t .token [0 ] = t .lex .nextItem ()
}
return t .token [t .peekCount ]
}
func (t *Tree ) backup () {
t .peekCount ++
}
func (t *Tree ) backup2 (t1 item ) {
t .token [1 ] = t1
t .peekCount = 2
}
func (t *Tree ) backup3 (t2 , t1 item ) {
t .token [1 ] = t1
t .token [2 ] = t2
t .peekCount = 3
}
func (t *Tree ) 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 ]
}
func (t *Tree ) nextNonSpace () (token item ) {
for {
token = t .next ()
if token .typ != itemSpace {
break
}
}
return token
}
func (t *Tree ) peekNonSpace () item {
token := t .nextNonSpace ()
t .backup ()
return token
}
func New (name string , funcs ...map [string ]any ) *Tree {
return &Tree {
Name : name ,
funcs : funcs ,
}
}
func (t *Tree ) ErrorContext (n Node ) (location , context string ) {
pos := int (n .Position ())
tree := n .tree ()
if tree == nil {
tree = t
}
text := tree .text [:pos ]
byteNum := strings .LastIndex (text , "\n" )
if byteNum == -1 {
byteNum = pos
} else {
byteNum ++
byteNum = pos - byteNum
}
lineNum := 1 + strings .Count (text , "\n" )
context = n .String ()
return fmt .Sprintf ("%s:%d:%d" , tree .ParseName , lineNum , byteNum ), context
}
func (t *Tree ) errorf (format string , args ...any ) {
t .Root = nil
format = fmt .Sprintf ("template: %s:%d: %s" , t .ParseName , t .token [0 ].line , format )
panic (fmt .Errorf (format , args ...))
}
func (t *Tree ) error (err error ) {
t .errorf ("%s" , err )
}
func (t *Tree ) expect (expected itemType , context string ) item {
token := t .nextNonSpace ()
if token .typ != expected {
t .unexpected (token , context )
}
return token
}
func (t *Tree ) expectOneOf (expected1 , expected2 itemType , context string ) item {
token := t .nextNonSpace ()
if token .typ != expected1 && token .typ != expected2 {
t .unexpected (token , context )
}
return token
}
func (t *Tree ) unexpected (token item , context string ) {
if token .typ == itemError {
extra := ""
if t .actionLine != 0 && t .actionLine != token .line {
extra = fmt .Sprintf (" in action started at %s:%d" , t .ParseName , t .actionLine )
if strings .HasSuffix (token .val , " action" ) {
extra = extra [len (" in action" ):]
}
}
t .errorf ("%s%s" , token , extra )
}
t .errorf ("unexpected %s in %s" , token , context )
}
func (t *Tree ) recover (errp *error ) {
e := recover ()
if e != nil {
if _ , ok := e .(runtime .Error ); ok {
panic (e )
}
if t != nil {
t .stopParse ()
}
*errp = e .(error )
}
}
func (t *Tree ) startParse (funcs []map [string ]any , lex *lexer , treeSet map [string ]*Tree ) {
t .Root = nil
t .lex = lex
t .vars = []string {"$" }
t .funcs = funcs
t .treeSet = treeSet
lex .options = lexOptions {
emitComment : t .Mode &ParseComments != 0 ,
breakOK : !t .hasFunction ("break" ),
continueOK : !t .hasFunction ("continue" ),
}
}
func (t *Tree ) stopParse () {
t .lex = nil
t .vars = nil
t .funcs = nil
t .treeSet = nil
}
func (t *Tree ) Parse (text , leftDelim , rightDelim string , treeSet map [string ]*Tree , funcs ...map [string ]any ) (tree *Tree , err error ) {
defer t .recover (&err )
t .ParseName = t .Name
lexer := lex (t .Name , text , leftDelim , rightDelim )
t .startParse (funcs , lexer , treeSet )
t .text = text
t .parse ()
t .add ()
t .stopParse ()
return t , nil
}
func (t *Tree ) add () {
tree := t .treeSet [t .Name ]
if tree == nil || IsEmptyTree (tree .Root ) {
t .treeSet [t .Name ] = t
return
}
if !IsEmptyTree (t .Root ) {
t .errorf ("template: multiple definition of template %q" , t .Name )
}
}
func IsEmptyTree (n Node ) bool {
switch n := n .(type ) {
case nil :
return true
case *ActionNode :
case *CommentNode :
return true
case *IfNode :
case *ListNode :
for _ , node := range n .Nodes {
if !IsEmptyTree (node ) {
return false
}
}
return true
case *RangeNode :
case *TemplateNode :
case *TextNode :
return len (bytes .TrimSpace (n .Text )) == 0
case *WithNode :
default :
panic ("unknown node: " + n .String ())
}
return false
}
func (t *Tree ) parse () {
t .Root = t .newList (t .peek ().pos )
for t .peek ().typ != itemEOF {
if t .peek ().typ == itemLeftDelim {
delim := t .next ()
if t .nextNonSpace ().typ == itemDefine {
newT := New ("definition" )
newT .text = t .text
newT .Mode = t .Mode
newT .ParseName = t .ParseName
newT .startParse (t .funcs , t .lex , t .treeSet )
newT .parseDefinition ()
continue
}
t .backup2 (delim )
}
switch n := t .textOrAction (); n .Type () {
case nodeEnd , nodeElse :
t .errorf ("unexpected %s" , n )
default :
t .Root .append (n )
}
}
}
func (t *Tree ) parseDefinition () {
const context = "define clause"
name := t .expectOneOf (itemString , itemRawString , context )
var err error
t .Name , err = strconv .Unquote (name .val )
if err != nil {
t .error (err )
}
t .expect (itemRightDelim , context )
var end Node
t .Root , end = t .itemList ()
if end .Type () != nodeEnd {
t .errorf ("unexpected %s in %s" , end , context )
}
t .add ()
t .stopParse ()
}
func (t *Tree ) 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 :
return list , n
}
list .append (n )
}
t .errorf ("unexpected EOF" )
return
}
func (t *Tree ) textOrAction () Node {
switch token := t .nextNonSpace (); token .typ {
case itemText :
return t .newText (token .pos , token .val )
case itemLeftDelim :
t .actionLine = token .line
defer t .clearActionLine ()
return t .action ()
case itemComment :
return t .newComment (token .pos , token .val )
default :
t .unexpected (token , "input" )
}
return nil
}
func (t *Tree ) clearActionLine () {
t .actionLine = 0
}
func (t *Tree ) action () (n Node ) {
switch token := t .nextNonSpace (); token .typ {
case itemBlock :
return t .blockControl ()
case itemBreak :
return t .breakControl (token .pos , token .line )
case itemContinue :
return t .continueControl (token .pos , token .line )
case itemElse :
return t .elseControl ()
case itemEnd :
return t .endControl ()
case itemIf :
return t .ifControl ()
case itemRange :
return t .rangeControl ()
case itemTemplate :
return t .templateControl ()
case itemWith :
return t .withControl ()
}
t .backup ()
token := t .peek ()
return t .newAction (token .pos , token .line , t .pipeline ("command" , itemRightDelim ))
}
func (t *Tree ) breakControl (pos Pos , line int ) Node {
if token := t .nextNonSpace (); token .typ != itemRightDelim {
t .unexpected (token , "{{break}}" )
}
if t .rangeDepth == 0 {
t .errorf ("{{break}} outside {{range}}" )
}
return t .newBreak (pos , line )
}
func (t *Tree ) continueControl (pos Pos , line int ) Node {
if token := t .nextNonSpace (); token .typ != itemRightDelim {
t .unexpected (token , "{{continue}}" )
}
if t .rangeDepth == 0 {
t .errorf ("{{continue}} outside {{range}}" )
}
return t .newContinue (pos , line )
}
func (t *Tree ) pipeline (context string , end itemType ) (pipe *PipeNode ) {
token := t .peekNonSpace ()
pipe = t .newPipeline (token .pos , token .line , nil )
decls :
if v := t .peekNonSpace (); v .typ == itemVariable {
t .next ()
tokenAfterVariable := t .peek ()
next := t .peekNonSpace ()
switch {
case next .typ == itemAssign , next .typ == itemDeclare :
pipe .IsAssign = next .typ == itemAssign
t .nextNonSpace ()
pipe .Decl = append (pipe .Decl , t .newVariable (v .pos , v .val ))
t .vars = append (t .vars , v .val )
case next .typ == itemChar && next .val == "," :
t .nextNonSpace ()
pipe .Decl = append (pipe .Decl , t .newVariable (v .pos , v .val ))
t .vars = append (t .vars , v .val )
if context == "range" && len (pipe .Decl ) < 2 {
switch t .peekNonSpace ().typ {
case itemVariable , itemRightDelim , itemRightParen :
goto decls
default :
t .errorf ("range can only initialize variables" )
}
}
t .errorf ("too many declarations in %s" , context )
case tokenAfterVariable .typ == itemSpace :
t .backup3 (v , tokenAfterVariable )
default :
t .backup2 (v )
}
}
for {
switch token := t .nextNonSpace (); token .typ {
case end :
t .checkPipeline (pipe , context )
return
case itemBool , itemCharConstant , itemComplex , itemDot , itemField , itemIdentifier ,
itemNumber , itemNil , itemRawString , itemString , itemVariable , itemLeftParen :
t .backup ()
pipe .append (t .command ())
default :
t .unexpected (token , context )
}
}
}
func (t *Tree ) checkPipeline (pipe *PipeNode , context string ) {
if len (pipe .Cmds ) == 0 {
t .errorf ("missing value for %s" , context )
}
for i , c := range pipe .Cmds [1 :] {
switch c .Args [0 ].Type () {
case NodeBool , NodeDot , NodeNil , NodeNumber , NodeString :
t .errorf ("non executable command in pipeline stage %d" , i +2 )
}
}
}
func (t *Tree ) parseControl (context string ) (pos Pos , line int , pipe *PipeNode , list , elseList *ListNode ) {
defer t .popVars (len (t .vars ))
pipe = t .pipeline (context , itemRightDelim )
if context == "range" {
t .rangeDepth ++
}
var next Node
list , next = t .itemList ()
if context == "range" {
t .rangeDepth --
}
switch next .Type () {
case nodeEnd :
case nodeElse :
if context == "if" && t .peek ().typ == itemIf {
t .next ()
elseList = t .newList (next .Position ())
elseList .append (t .ifControl ())
} else if context == "with" && t .peek ().typ == itemWith {
t .next ()
elseList = t .newList (next .Position ())
elseList .append (t .withControl ())
} else {
elseList , next = t .itemList ()
if next .Type () != nodeEnd {
t .errorf ("expected end; found %s" , next )
}
}
}
return pipe .Position (), pipe .Line , pipe , list , elseList
}
func (t *Tree ) ifControl () Node {
return t .newIf (t .parseControl ("if" ))
}
func (t *Tree ) rangeControl () Node {
r := t .newRange (t .parseControl ("range" ))
return r
}
func (t *Tree ) withControl () Node {
return t .newWith (t .parseControl ("with" ))
}
func (t *Tree ) endControl () Node {
return t .newEnd (t .expect (itemRightDelim , "end" ).pos )
}
func (t *Tree ) elseControl () Node {
peek := t .peekNonSpace ()
if peek .typ == itemIf || peek .typ == itemWith {
return t .newElse (peek .pos , peek .line )
}
token := t .expect (itemRightDelim , "else" )
return t .newElse (token .pos , token .line )
}
func (t *Tree ) blockControl () Node {
const context = "block clause"
token := t .nextNonSpace ()
name := t .parseTemplateName (token , context )
pipe := t .pipeline (context , itemRightDelim )
block := New (name )
block .text = t .text
block .Mode = t .Mode
block .ParseName = t .ParseName
block .startParse (t .funcs , t .lex , t .treeSet )
var end Node
block .Root , end = block .itemList ()
if end .Type () != nodeEnd {
t .errorf ("unexpected %s in %s" , end , context )
}
block .add ()
block .stopParse ()
return t .newTemplate (token .pos , token .line , name , pipe )
}
func (t *Tree ) templateControl () Node {
const context = "template clause"
token := t .nextNonSpace ()
name := t .parseTemplateName (token , context )
var pipe *PipeNode
if t .nextNonSpace ().typ != itemRightDelim {
t .backup ()
pipe = t .pipeline (context , itemRightDelim )
}
return t .newTemplate (token .pos , token .line , name , pipe )
}
func (t *Tree ) parseTemplateName (token item , context string ) (name string ) {
switch token .typ {
case itemString , itemRawString :
s , err := strconv .Unquote (token .val )
if err != nil {
t .error (err )
}
name = s
default :
t .unexpected (token , context )
}
return
}
func (t *Tree ) command () *CommandNode {
cmd := t .newCommand (t .peekNonSpace ().pos )
for {
t .peekNonSpace ()
operand := t .operand ()
if operand != nil {
cmd .append (operand )
}
switch token := t .next (); token .typ {
case itemSpace :
continue
case itemRightDelim , itemRightParen :
t .backup ()
case itemPipe :
default :
t .unexpected (token , "operand" )
}
break
}
if len (cmd .Args ) == 0 {
t .errorf ("empty command" )
}
return cmd
}
func (t *Tree ) operand () Node {
node := t .term ()
if node == nil {
return nil
}
if t .peek ().typ == itemField {
chain := t .newChain (t .peek ().pos , node )
for t .peek ().typ == itemField {
chain .Add (t .next ().val )
}
switch node .Type () {
case NodeField :
node = t .newField (chain .Position (), chain .String ())
case NodeVariable :
node = t .newVariable (chain .Position (), chain .String ())
case NodeBool , NodeString , NodeNumber , NodeNil , NodeDot :
t .errorf ("unexpected . after term %q" , node .String ())
default :
node = chain
}
}
return node
}
func (t *Tree ) term () Node {
switch token := t .nextNonSpace (); token .typ {
case itemIdentifier :
checkFunc := t .Mode &SkipFuncCheck == 0
if checkFunc && !t .hasFunction (token .val ) {
t .errorf ("function %q not defined" , token .val )
}
return NewIdentifier (token .val ).SetTree (t ).SetPos (token .pos )
case itemDot :
return t .newDot (token .pos )
case itemNil :
return t .newNil (token .pos )
case itemVariable :
return t .useVar (token .pos , token .val )
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 :
return t .pipeline ("parenthesized pipeline" , itemRightParen )
case itemString , itemRawString :
s , err := strconv .Unquote (token .val )
if err != nil {
t .error (err )
}
return t .newString (token .pos , token .val , s )
}
t .backup ()
return nil
}
func (t *Tree ) hasFunction (name string ) bool {
for _ , funcMap := range t .funcs {
if funcMap == nil {
continue
}
if funcMap [name ] != nil {
return true
}
}
return false
}
func (t *Tree ) popVars (n int ) {
t .vars = t .vars [:n ]
}
func (t *Tree ) useVar (pos Pos , name string ) Node {
v := t .newVariable (pos , name )
for _ , varName := range t .vars {
if varName == v .Ident [0 ] {
return v
}
}
t .errorf ("undefined variable %q" , v .Ident [0 ])
return nil
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .