前文 分析了解析要点
注:标题 text/template/parse 最后的 parse 不是指一个包,或者文件名,因为代码中各种交叉调用封装,所以直接用了 parse 这个词,本文标题中的 exec 也是这样
相关文件:
- exec.go
执行一个 template ,执行的入口在 exec.go
func (s *state) walk(dot reflect.Value, node parse.Node)
walk总是由Tree.Root开始,按照前文所述两大类 pipeline类对应 ActionNode,流程控制类对应 IfNode,ListNode,RangeNode,TemplateNode,TextNode,WithNode,分别进行处理 如果 walk 中发生了错误,errRecover处理行为是
func errRecover(errp *error) { e := recover() if e != nil { switch err := e.(type) { case runtime.Error: panic(e) case error: *errp = err default: panic(e) } }}
而对于 ActionNode 就是一个求值的过程,walk 方法中对于 ActionNode 的处理,代码明确写着 <!-- lang: cpp --> case *parse.ActionNode: val := s.evalPipeline(dot, node.Pipe) if len(node.Pipe.Decl) == 0 { s.printValue(node, val) }
求值入口是 evalPipeline
分析过template目前的实现所有的 ActionNode 都是封装成 NodeCommand,所以 evalPipeline 方法中对ActionNode的求值变成 evalCommand,这样绕了一圈,evalCommand是现阶段 template 实现最终的 ActionNode 求值分派主体,NodeCommand 的 Args属性封装了真正的节点,而对应不同的节点类型必须写对应的求值方法
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")}
以 FieldNode 节点为例,最终的求值执行方法是
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value { if !receiver.IsValid() { return zero } typ := receiver.Type() receiver, _ = indirect(receiver) // Unless it's an interface, need to get to a value of type *T to guarantee // we see all methods of T and *T. ptr := receiver if ptr.Kind() != reflect.Interface && ptr.CanAddr() { ptr = ptr.Addr() } if method := ptr.MethodByName(fieldName); method.IsValid() { return s.evalCall(dot, method, node, fieldName, args, final) } hasArgs := len(args) > 1 || final.IsValid() // It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil. receiver, isNil := indirect(receiver) if isNil { s.errorf("nil pointer evaluating %s.%s", typ, fieldName) } switch receiver.Kind() { case reflect.Struct: tField, ok := receiver.Type().FieldByName(fieldName) if ok { field := receiver.FieldByIndex(tField.Index) if tField.PkgPath != "" { // field is unexported s.errorf("%s is an unexported field of struct type %s", fieldName, typ) } // If it's a function, we must call it. if hasArgs { s.errorf("%s has arguments but cannot be invoked as function", fieldName) } return field } s.errorf("%s is not a field of struct type %s", fieldName, typ) case reflect.Map: // If it's a map, attempt to use the field name as a key. nameVal := reflect.ValueOf(fieldName) if nameVal.Type().AssignableTo(receiver.Type().Key()) { if hasArgs { s.errorf("%s is not a method but has arguments", fieldName) } return receiver.MapIndex(nameVal) } } s.errorf("can't evaluate field %s in type %s", fieldName, typ) panic("not reached")}
看到这里,路线清晰了,从外到内 流程控制类:包含 IfNode,ListNode,RangeNode,TemplateNode,TextNode,WithNode都是独立的 。 ActionNode求值:包含 funcMap,内建函数, .Field,全部用NodeCommand进行封装 所有的节点类型必定有对应的逻辑控制代码或者求值代码实现
解析模板并依靠 itemType 生成节点 Tree.term(), Tree.pipeline和Tree.operandd 根据 itemType类型判断是否生成 NodeCommand.
感受:笔者没有自己完成一个递归向下lex的经验,感觉,go的template实现是一个务实的实现,所谓务实是指不纠结于严格的逻辑分类,总体看上去,层层的封装一点都没有少,猜测是为以后的扩展打基础,那些不严格的逻辑分类(或者说成是偷懒)估计只是在现阶段暂时性的。实现一个lex,到底是会走向效率还是走向严格逻辑,看完template的实现,笔者也比较迷惑