文件组成
lex.go 要点
itemType 常量表次序规则:itemKeyword 用来分界词法中的关键字和其他词法元素,参见下列实现
func (i item) String() string
func lexIdentifier(l *lexer) stateFn
node.go 要点
各种 NodeType 要实现 Node 接口
type Node interface { Type() NodeType String() string Copy() Node Position() Pos unexported() }
parse.go 要点
每个模板都有唯一的模板名,并对应生成一个 Tree
type Tree struct { Name string ParseName string Root *ListNode text string funcs []map[string]interface{} lex *lexer token [3]item peekCount int vars []string }
Tree 的 Parse 方法中 defer t.recover(&err) 捕获解析过程中发生的 error ,如果是 runtime.Error 就 panic,否则 stopParse
Parse方法首先进行的是词法分析(内部使用goroutine和chan并行完成,其实和节点的生成是同步的)
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))lex.go 中的 lexer定义
type lexer struct { name string // the name of the input; used only for error reports input string // the string being scanned leftDelim string // start of action rightDelim string // end of action state stateFn // the next lexing function to enter pos Pos // current position in the input start Pos // start position of this item width Pos // width of last rune read from input lastPos Pos // position of most recent item returned by nextItem items chan item // channel of scanned items parenDepth int // nesting depth of ( ) exprs }
lexer在解析过程中的顺序总是从一个 lexText 开始,遍历 input 。lexText 负责查找
把处理过程交给 func lexLeftDelim,lexLeftDelim 负责查找注释和非注释(被称做lexInsideAction),处理过程交给 func lexComment,lexComment 解析到注释结束后,又把处理过程交给lexText,进行 lexInsideAction 解析。
lexInsideAction 要点
所有定义的词法都在这个函数中处理,根据词法定义交给具体的词法处理函数,形成词法处理链,完成遍历。每一个词法处理函数负责检查词法有效性。这个过程仅仅是一个词法处理链,遍历 input
某个词法分析通过后,会生成对应的节点,私有方法 parse 完成此过程。
t.parse(treeSet)第一个节点 Tree.Root 要点
type ListNode struct { NodeType Pos Nodes []Node // The element nodes in lexical order. }Root 是一个 ListNode ,保存 Tree 解析所有的 Node.
n := t.textOrAction()这是所有的节点生成的入口点
func (t *Tree) textOrAction() Node { switch token := t.nextNonSpace(); token.typ { case itemText: return newText(token.pos, token.val) case itemLeftDelim: return t.action() //这里所有{{...}}中的代码都由action处理 default: t.unexpected(token, "input") } return nil }
func (t *Tree) action() (n Node) { switch token := t.nextNonSpace(); token.typ { 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() // Do not pop variables; they persist until "end". return newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command")) }而action方法把模板中的关键字划分为 xxxControl 处理,其他都由 newAction 生成 ActionNode ,
也可以理解为:template 的节点分两大类
return newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))这里面的要点:
t.pipeline("command")
看pipeline的实现,其实所有的 pipeline 节点都都封装为 NodeCommand, An element of a pipeline
pipeline 方法中对所有已知的 action 进行处理,阅读代码发现 template 的语法设计中使用这种pipeline的方式,使得解析器只需要向下分析,也就是所谓的 peek ,不需要回退,这种设计无疑简化了分析器,我不知道对以后的扩展是否会有所限制
从 Parse->parse->textOrAction->pipeline 递归向下生成了所有的节点,而最终 parse 中 t.Root.append(n) 把所有生成的节点全部添加到 Tree.Root
从struct来说,经过了一层层的封装 lex.go itemType -> node.go NodeType->node.go XxxxNode 进入Tree.Root.Nodes