文件组成
- lex.go 词法定义与解析
- node.go node 定义与创建
- parse.go 生成 template 语法树 tree
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 负责查找
- itemLeftDelim 默认"{{",可以自定义
把处理过程交给 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.
前面说过lex分析的时候分只有两种情况 lexText , lexInsideAction ,因此 parse 中
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 的节点分两大类
- ActionNode,其实封装了各种pipline的识别信息,
- xxxxControl,流程控制类,IfNode,RangeNode,WithNode这些
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