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

原创
2013/05/19 16:36
阅读数 886

前文 Go 源码阅读笔记 text/template/parse​ 分析了解析要点

注:标题 text/template/parse 最后的 parse 不是指一个包,或者文件名,因为代码中各种交叉调用封装,所以直接用了 parse 这个词,本文标题中的 exec 也是这样

相关文件:

  1. ​​exec.go

执行一个 template ,执行的入口在 exec.go

<!-- lang: cpp -->
func (s *state) walk(dot reflect.Value, node parse.Node)

walk总是由Tree.Root开始​,按照前文所述两大类 pipeline类对应 ActionNode,流程控制类对应 IfNode,ListNode,RangeNode,TemplateNode,TextNode,WithNode,分别进行处理 如果 walk 中发生了错误,errRecover处理行为是

<!-- lang: cpp -->
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属性封装了真正的节点,而对应不同的节点类型必须写对应的求值方法

<!-- lang: cpp -->
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 节点为例,最终的求值执行方法是

<!-- lang: cpp -->
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的实现,笔者也比较迷惑

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