Go 源碼閱讀筆記 text/template/exec

前文 Go 源碼閱讀筆記 text/template/parse​ 分析瞭解析要點node

注:標題 text/template/parse 最後的 parse 不是指一個包,或者文件名,由於代碼中各類交叉調用封裝,因此直接用了 parse 這個詞,本文標題中的 exec 也是這樣less

相關文件:ide

  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處理行爲是lua

<!-- 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) }idea

求值入口是 evalPipeline.net

前文分析過template目前的實現全部的 ActionNode 都是封裝成 NodeCommand,因此 evalPipeline 方法中對ActionNode的求值變成 evalCommand,這樣繞了一圈,evalCommand是現階段 template 實現最終的 ActionNode 求值分派主體,NodeCommand 的 Args屬性封裝了真正的節點,而對應不一樣的節點類型必須寫對應的求值方法code

<!-- 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 節點爲例,最終的求值執行方法是blog

<!-- 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的實現,筆者也比較迷惑

相關文章
相關標籤/搜索