轉(zhuǎn)載自:go的text/template源碼解析
為了造出好用埃脏,貼近源生態(tài)的輪子芯义,所以我決定把text/template的源碼熟讀一遍
本篇只是粗略的講解模板的各個(gè)模塊,不會(huì)深入函數(shù)細(xì)節(jié)
首先基本用法開(kāi)始
基本用法
我們這里用的是text_template中example_test的ExampleTemplate()作為例子
通過(guò)一下命令創(chuàng)建一個(gè)text模板,其中Must函數(shù)表示處理錯(cuò)誤并panic,New表示創(chuàng)建一個(gè)空模板,解析模板內(nèi)容的邏輯在Parse函數(shù)中
然后通過(guò)t.Execute
把模板渲染出來(lái)
const letter = `
Dear {{.Name}},
{{if .Attended}}
It was a pleasure to see you at the wedding.
{{- else}}
It is a shame you couldn't make it to the wedding.
{{- end}}
{{with .Gift -}}
Thank you for the lovely {{.}}.
{{end}}
Best wishes,
Josie
`
// Prepare some data to insert into the template.
type Recipient struct {
Name, Gift string
Attended bool
}
var recipients = []Recipient{
{"Aunt Mildred", "bone china tea set", true},
{"Uncle John", "moleskin pants", false},
{"Cousin Rodney", "", false},
}
t := template.Must(template.New("letter").Parse(letter))
// Execute the template for each recipient.
for _, r := range recipients {
err := t.Execute(os.Stdout, r)
if err != nil {
log.Println("executing template:", err)
}
}
數(shù)據(jù)結(jié)構(gòu)
先看看text/template的Template結(jié)構(gòu)禁舷,這個(gè)是整個(gè)template庫(kù)最重要了結(jié)構(gòu)了,也是我們通過(guò)template.New("letter").Parse(letter)
得到對(duì)象
下面我會(huì)加入自己的注釋毅往,以// self
開(kāi)頭
// Template is the representation of a parsed template. The *parse.Tree
// field is exported only for use by html/template and should be treated
// as unexported by all other clients.
type Template struct {
name string // self 模板名字
*parse.Tree // self
*common
leftDelim string //self 左分隔符牵咙,一般是 {{
rightDelim string //self 右分隔符, 一般是 }}
}
// common holds the information shared by related templates.
type common struct {
tmpl map[string]*Template // Map from name to defined templates. //self 模板的子模板,在文件中的 {{ define xxx }} {{ end }} 就會(huì)創(chuàng)建一個(gè)模板
option option
// We use two maps, one for parsing and one for execution.
// This separation makes the API cleaner since it doesn't
// expose reflection to the client.
muFuncs sync.RWMutex // protects parseFuncs and execFuncs
parseFuncs FuncMap //self 以interface{}形式保存的函數(shù)對(duì)象
execFuncs map[string]reflect.Value // self parseFuncs中的函數(shù)最終都會(huì)轉(zhuǎn)換成reflect.Value形式
}
Template中還有一個(gè)parse.Tree
看看它長(zhǎng)什么樣子
// Tree is the representation of a single parsed template.
type Tree struct {
Name string // name of the template represented by the tree.
ParseName string // name of the top-level template during parsing, for error messages.
Root *ListNode // top-level root of the tree.
text string // text parsed to create the template (or its parent) //self 等待解析的文本
// Parsing only; cleared after parse.
funcs []map[string]interface{}
lex *lexer //self 詞法解析器攀唯,用于解析模板中的關(guān)鍵字,比如 '{{' ,'|', '=', 函數(shù)名洁桌,表達(dá)式,等等
token [3]item // three-token lookahead for parser.
peekCount int
vars []string // variables defined at the moment.
treeSet map[string]*Tree
}
// ListNode holds a sequence of nodes.
type ListNode struct {
NodeType //self 節(jié)點(diǎn)類型侯嘀,沒(méi)什么好說(shuō)的
Pos //self 該節(jié)點(diǎn)在文本中的位置另凌,也可以理解為index
tr *Tree //self 該節(jié)點(diǎn)在樹(shù)中的位置
Nodes []Node // The element nodes in lexical order.
}
// A 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 does a deep copy of the Node and all its components.
// To avoid type assertions, some XxxNodes also have specialized
// CopyXxx methods that return *XxxNode.
Copy() Node
Position() Pos // byte position of start of node in full original input string
// tree returns the containing *Tree.
// It is unexported so all implementations of Node are in this package.
tree() *Tree
}
const (
NodeText NodeType = iota // Plain text.
NodeAction // A non-control action such as a field evaluation.
NodeBool // A boolean constant.
NodeChain // A sequence of field accesses.
NodeCommand // An element of a pipeline.
NodeDot // The cursor, dot.
nodeElse // An else action. Not added to tree.
nodeEnd // An end action. Not added to tree.
NodeField // A field or method name.
NodeIdentifier // An identifier; always a function name.
NodeIf // An if action.
NodeList // A list of Nodes.
NodeNil // An untyped nil constant.
NodeNumber // A numerical constant.
NodePipe // A pipeline of commands.
NodeRange // A range action.
NodeString // A string constant.
NodeTemplate // A template invocation action.
NodeVariable // A $ variable.
NodeWith // A with action.
)
創(chuàng)建模板
我們從Parse函數(shù)作為入口看看它做了什么
// Parse parses text as a template body for t.
// Named template definitions ({{define ...}} or {{block ...}} statements) in text
// define additional templates associated with t and are removed from the
// definition of t itself.
//
// Templates can be redefined in successive calls to Parse.
// A template definition with a body containing only white space and comments
// is considered empty and will not replace an existing template's body.
// This allows using Parse to add new named template definitions without
// overwriting the main template body.
func (t *Template) Parse(text string) (*Template, error) {
t.init()
t.muFuncs.RLock()
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
t.muFuncs.RUnlock()
if err != nil {
return nil, err
}
// Add the newly parsed trees, including the one for t, into our common structure.
for name, tree := range trees {
if _, err := t.AddParseTree(name, tree); err != nil {
return nil, err
}
}
return t, nil
}
如果在模板使用語(yǔ)法我就不講了,從注釋中我們還可以知道残拐,該函數(shù)可以重復(fù)調(diào)用途茫,新的模板會(huì)覆蓋舊的,然后我們看看代碼
代碼大致含義 parse.Parse 函數(shù)把文本解析成map[string]*parse.Tree
的樹(shù)map對(duì)象,然后把它append到當(dāng)前模板的t.temp中
然后看看parse.Parse發(fā)生了什么
// Parse returns a map from template name to parse.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, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (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
}
我們接著往下看
// Parse 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, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
defer t.recover(&err)
t.ParseName = t.Name
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim), treeSet)
t.text = text
t.parse()
t.add()
t.stopParse()
return t, nil
}
// lex creates a new scanner for the input string.
func lex(name, input, left, right string) *lexer {
if left == "" {
left = leftDelim
}
if right == "" {
right = rightDelim
}
l := &lexer{
name: name,
input: input,
leftDelim: left,
rightDelim: right,
items: make(chan item),
line: 1,
}
go l.run()
return l
}
// startParse initializes the parser, using the lexer.
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet map[string]*Tree) {
t.Root = nil
t.lex = lex
t.vars = []string{"$"}
t.funcs = funcs
t.treeSet = treeSet
}
lex就是詞法解析器溪食,它不斷的讀取文本中的關(guān)鍵字囊卜,傳給Tree.parse
來(lái)解析
Tree.startParse
并不是真的開(kāi)始解析,它只是初始化Tree
的詞法解析器等字段
Tree.parse
會(huì)讀取從lex中解析的關(guān)鍵詞,構(gòu)建成不同的節(jié)點(diǎn)栅组,保存到樹(shù)中(這個(gè)源碼我會(huì)在下面講解時(shí)才貼)
我們先從lex開(kāi)始看
// lex creates a new scanner for the input string.
func lex(name, input, left, right string) *lexer {
if left == "" {
left = leftDelim
}
if right == "" {
right = rightDelim
}
l := &lexer{
name: name,
input: input,
leftDelim: left,
rightDelim: right,
items: make(chan item),
line: 1,
}
go l.run()
return l
}
// run runs the state machine for the lexer.
func (l *lexer) run() {
for state := lexText; state != nil; {
state = state(l)
}
close(l.items)
}
// lexText scans until an opening action delimiter, "{{".
func lexText(l *lexer) stateFn {
l.width = 0
if x := strings.Index(l.input[l.pos:], l.leftDelim); x >= 0 {
ldn := Pos(len(l.leftDelim))
l.pos += Pos(x)
trimLength := Pos(0)
if strings.HasPrefix(l.input[l.pos+ldn:], leftTrimMarker) {
trimLength = rightTrimLength(l.input[l.start:l.pos])
}
l.pos -= trimLength
if l.pos > l.start {
l.emit(itemText)
}
l.pos += trimLength
l.ignore()
return lexLeftDelim
} else {
l.pos = Pos(len(l.input))
}
// Correctly reached EOF.
if l.pos > l.start {
l.emit(itemText)
}
l.emit(itemEOF)
return nil
}
lex
函數(shù)通過(guò)go l.run
異步執(zhí)行單詞解析雀瓢,并通過(guò)items chan
傳給外面
lexer.Run
通過(guò)不斷執(zhí)行 stateFn
直到它返回一個(gè)空值
第一個(gè)被執(zhí)行的stateFn
是lexText,它負(fù)責(zé)掃描遇到 {{
符號(hào)之前的所有字符玉掸,也就是模板語(yǔ)法之外的文本
l.emit
就是往 l.items
發(fā)送一個(gè) item刃麸,我們看看l.emit是怎么樣的
// emit passes an item back to the client.
func (l *lexer) emit(t itemType) {
l.items <- item{t, l.start, l.input[l.start:l.pos], l.line}
// Some items contain text internally. If so, count their newlines.
switch t {
case itemText, itemRawString, itemLeftDelim, itemRightDelim:
l.line += strings.Count(l.input[l.start:l.pos], "\n")
}
l.start = l.pos
}
其中 l.input[l.start:l.pos]
表示這次分析的 “詞” 對(duì)應(yīng)的位置 ({{,/*,:=
等等也是屬于詞 )
lex函數(shù)解析順序(TODO)
lexText -> lexLeftDelim -> lexComment -> lexText
-> lexInsideAction ->
lexText -> EOF
我們來(lái)開(kāi)開(kāi)Tree.parse
拿到item后怎么處理
Tree.parse
// parse 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() {
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") // name will be updated once we know it.
newT.text = t.text
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)
}
}
}
首先parse這個(gè)函數(shù)的目的是把lex解析出來(lái)的item進(jìn)一步解析成parse.Node
然后放入t.Root中
在解釋這個(gè)函數(shù)之前我也簡(jiǎn)單說(shuō)說(shuō) peek
,next
,backup
,backup2
這幾個(gè)函數(shù)的含義
- peek: 查看棧的最后一個(gè)item
- next: 拿出棧的最后一個(gè)item
- backend: 把最后一個(gè)拿出來(lái)的item塞到棧尾
- backend2: 把最后一個(gè)拿出來(lái)的item塞到棧尾,并額外塞一個(gè)進(jìn)去
- backend3: 和backend2同理司浪,塞2個(gè)進(jìn)去
好了理解了這些我們就基本能看到這個(gè)函數(shù)在干什么了
- 用第0個(gè)item.pos來(lái)初始化
t.Root
- for循環(huán)遍歷item泊业,直到遇到
itemEOF
這個(gè)結(jié)束符item為止 - 遇到 左分隔符(也就是”{{“)判斷這個(gè)分隔符中的是不是 itemDefine(也就是{{define subTemp}}來(lái)定義額外的模板樹(shù)),如果是啊易,開(kāi)始解析子模板樹(shù)并跳過(guò)這一輪循環(huán)吁伺,如果不是回到itemLeftDelim之前從新解析
- 通過(guò)
Tree.textOrAction
返回下一個(gè)Node放入t.Root
中
下一步我們來(lái)看看textOrAction干了什么
textOrAction
// textOrAction:
// text | action
func (t *Tree) textOrAction() Node {
switch token := t.nextNonSpace(); token.typ {
case itemText:
return t.newText(token.pos, token.val)
case itemLeftDelim:
return t.action()
default:
t.unexpected(token, "input")
}
return nil
}
func (t *Tree) newText(pos Pos, text string) *TextNode {
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
}
// 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) {
switch token := t.nextNonSpace(); token.typ {
case itemBlock:
return t.blockControl()
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()
// Do not pop variables; they persist until "end".
return t.newAction(token.pos, token.line, t.pipeline("command"))
}
textOrAction非常簡(jiǎn)單,就是把item分成2部分租谈,遇到itemText就解析成TextNode
遇到{{
就開(kāi)始action解析
所以繼續(xù)看幾個(gè)關(guān)鍵的action函數(shù)這部分就算完成了
IfNode
// If:
// {{if pipeline}} itemList {{end}}
// {{if pipeline}} itemList {{else}} itemList {{end}}
// If keyword is past.
func (t *Tree) ifControl() Node {
return t.newIf(t.parseControl(true, "if"))
}
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
}
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
defer t.popVars(len(t.vars))
pipe = t.pipeline(context)
var next Node
list, next = t.itemList()
switch next.Type() {
case nodeEnd: //done
case nodeElse:
if allowElseIf {
// Special case for "else if". If the "else" is followed immediately by an "if",
// the elseControl will have left the "if" token pending. Treat
// {{if a}}_{{else if b}}_{{end}}
// as
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
// is assumed. This technique works even for long if-else-if chains.
// TODO: Should we allow else-if in with and range?
if t.peek().typ == itemIf {
t.next() // Consume the "if" token.
elseList = t.newList(next.Position())
elseList.append(t.ifControl())
// Do not consume the next item - only one {{end}} required.
break
}
}
elseList, next = t.itemList()
if next.Type() != nodeEnd {
t.errorf("expected end; found %s", next)
}
}
return pipe.Position(), pipe.Line, pipe, list, elseList
}
// itemList:
// textOrAction*
// Terminates at {{end}} or {{else}}, returned separately.
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
}
這個(gè)函數(shù)內(nèi)容太多我就跳過(guò)細(xì)節(jié)了
首先ifControl/rangeControl/withControl/rangeControl需要調(diào)用parseControl篮奄,也可以理解為所有{{ }}
可以支持語(yǔ)句的都需要通過(guò)該函數(shù)來(lái)解析,比如pipeline |
或者函數(shù)調(diào)用等
parseControl
邏輯
- 把
{{}}
中所有內(nèi)容解析成PipeNode
- 調(diào)用
Tree.itemList
嘗試獲取ElseNode和EndNode
在最后割去,我們來(lái)看看下面例子生成的node是怎么樣的,以下是letter模板生成的Node結(jié)構(gòu)窟却,縮進(jìn)表示層級(jí)
testdata/letter.tmpl:1:0 (NodeList)
testdata/letter.tmpl:1:0 (NodeText)
testdata/letter.tmpl:1:7 (NodeAction)
testdata/letter.tmpl:1:7 (NodePipe)
testdata/letter.tmpl:1:7 (NodeCommand)
testdata/letter.tmpl:1:7 (NodeField)
testdata/letter.tmpl:1:14 (NodeText)
testdata/letter.tmpl:2:5 (NodeIf)
testdata/letter.tmpl:2:5 (NodePipe)
testdata/letter.tmpl:2:5 (NodeCommand)
testdata/letter.tmpl:2:5 (NodeField)
testdata/letter.tmpl:2:16 (NodeList)
testdata/letter.tmpl:2:16 (NodeText)
testdata/letter.tmpl:4:10 (NodeList)
testdata/letter.tmpl:4:10 (NodeText)
testdata/letter.tmpl:6:9 (NodeText)
testdata/letter.tmpl:7:7 (NodeWith)
testdata/letter.tmpl:7:7 (NodePipe)
testdata/letter.tmpl:7:7 (NodeCommand)
testdata/letter.tmpl:7:7 (NodeField)
testdata/letter.tmpl:8:4 (NodeList)
testdata/letter.tmpl:8:4 (NodeText)
testdata/letter.tmpl:8:31 (NodeAction)
testdata/letter.tmpl:8:31 (NodePipe)
testdata/letter.tmpl:8:31 (NodeCommand)
testdata/letter.tmpl:8:31 (NodeDot)
testdata/letter.tmpl:8:34 (NodeText)
testdata/letter.tmpl:9:7 (NodeText)
總結(jié)一下
text/template通過(guò) lex 將文本解析成一個(gè)個(gè)item,然后通過(guò)Tree.parse
生成一個(gè)有層級(jí)關(guān)系的node,最后通過(guò) Execute
生成文本呻逆,下面來(lái)介紹模板Execute
其實(shí)和語(yǔ)言編譯原理有點(diǎn)像夸赫,詞法解析器->語(yǔ)法解析器->編譯成一個(gè)對(duì)象->根據(jù)執(zhí)行參數(shù)的不同輸出不同結(jié)果
模板Execute
execute就很簡(jiǎn)單了,基本就是該循環(huán)循環(huán)页慷,該打印打印憔足,
Pipe里面只有當(dāng)node.Pipe.Decl為0才會(huì)把Pipe中的值渲染出來(lái)胁附,不然只是一次賦值酒繁,
稍微能講講的就是node.Pipe
// Execute applies a parsed template to the specified data object,
// and writes the output to wr.
// If an error occurs executing the template or writing its output,
// execution stops, but partial results may already have been written to
// the output writer.
// A template may be executed safely in parallel, although if parallel
// executions share a Writer the output may be interleaved.
//
// If data is a reflect.Value, the template applies to the concrete
// value that the reflect.Value holds, as in fmt.Print.
func (t *Template) Execute(wr io.Writer, data interface{}) error {
return t.execute(wr, data)
}
func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
defer errRecover(&err)
value, ok := data.(reflect.Value)
if !ok {
value = reflect.ValueOf(data)
}
state := &state{
tmpl: t,
wr: wr,
vars: []variable{{"$", value}},
}
if t.Tree == nil || t.Root == nil {
state.errorf("%q is an incomplete or empty template", t.Name())
}
state.walk(value, t.Root)
return
}
// Walk functions step through the major pieces of the template structure,
// generating output as they go.
func (s *state) walk(dot reflect.Value, node parse.Node) {
s.at(node)
switch node := node.(type) {
case *parse.ActionNode:
// Do not pop variables so they persist until next end.
// Also, if the action declares variables, don't print the result.
val := s.evalPipeline(dot, node.Pipe)
if len(node.Pipe.Decl) == 0 {
s.printValue(node, val)
}
case *parse.IfNode:
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
case *parse.ListNode:
for _, node := range node.Nodes {
s.walk(dot, node)
}
case *parse.RangeNode:
s.walkRange(dot, node)
case *parse.TemplateNode:
s.walkTemplate(dot, node)
case *parse.TextNode:
if _, err := s.wr.Write(node.Text); err != nil {
s.writeError(err)
}
case *parse.WithNode:
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
default:
s.errorf("unknown node: %s", node)
}
}
我們來(lái)看看Pipe的解析時(shí)怎么樣的
執(zhí)行里面的cmds,然后跳過(guò)interface{}
對(duì)象拿里面的值
// Eval functions evaluate pipelines, commands, and their elements and extract
// values from the data structure by examining fields, calling methods, and so on.
// The printing of those values happens only through walk functions.
// evalPipeline returns the value acquired by evaluating a pipeline. If the
// pipeline has a variable declaration, the variable will be pushed on the
// stack. Callers should therefore pop the stack after they are finished
// executing commands depending on the pipeline value.
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
if pipe == nil {
return
}
s.at(pipe)
value = missingVal
for _, cmd := range pipe.Cmds {
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
// If the object has type interface{}, dig down one level to the thing inside.
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
value = reflect.ValueOf(value.Interface()) // lovely!
}
}
for _, variable := range pipe.Decl {
if pipe.IsAssign {
s.setVar(variable.Ident[0], value)
} else {
s.push(variable.Ident[0], value)
}
}
return value
}
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
firstWord := cmd.Args[0]
switch n := firstWord.(type) {
case *parse.FieldNode:
return s.evalFieldNode(dot, n, cmd.Args, final)
case *parse.ChainNode:
return s.evalChainNode(dot, n, cmd.Args, final)
case *parse.IdentifierNode:
// Must be a function.
return s.evalFunction(dot, n, cmd, cmd.Args, final)
case *parse.PipeNode:
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
return s.evalPipeline(dot, n)
case *parse.VariableNode:
return s.evalVariableNode(dot, n, cmd.Args, final)
}
s.at(firstWord)
s.notAFunction(cmd.Args, final)
switch word := firstWord.(type) {
case *parse.BoolNode:
return reflect.ValueOf(word.True)
case *parse.DotNode:
return dot
case *parse.NilNode:
s.errorf("nil is not a command")
case *parse.NumberNode:
return s.idealConstant(word)
case *parse.StringNode:
return reflect.ValueOf(word.Text)
}
s.errorf("can't evaluate command %q", firstWord)
panic("not reached")
}
go模板就寫(xiě)到這里控妻,里面代碼給人一種零亂的感覺(jué)州袒,但是代碼意圖和注釋還是很清晰的,看函數(shù)名和注釋能猜到個(gè)大概
拋開(kāi)代碼結(jié)構(gòu)不說(shuō)弓候,go的template的語(yǔ)法自成一派讓人用起來(lái)很不舒服郎哭,每次使用都得查查文檔,如果能和go語(yǔ)法統(tǒng)一就好了