Go 源码阅读笔记 text/template/parse

原创
2013/05/17 13:39
阅读数 1.6K


文件组成

  1. lex.go       词法定义与解析
  2. node.go     node 定义与创建
  3. 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 的节点分两大类

  1. ActionNode,其实封装了各种pipline的识别信息,
  2. 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


展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
8 收藏
0
分享
返回顶部
顶部