缘起
最近阅读 [Spring Boot技术内幕: 架构设计与实现原理] (朱智胜 , 2020.6)
本系列笔记拟采用golang练习之
Talk is cheap, show me the code.
Spring
Spring的主要特性:1. 控制反转(Inversion of Control, IoC)2. 面向容器3. 面向切面(AspectOriented Programming, AOP)源码gitee地址:https://gitee.com/ioly/learning.gooop原文链接:https://my.oschina.net/ioly
目标
- 参考spring boot常用注解,使用golang编写“基于注解的静态代码增强器/生成器”
子目标(Day 7)
- 因为struct/field/method的扫描是关键,因此今天针对这块做了单元测试
- common/Tokens.go:修复MatchBasicType方法的正则匹配bug。其实func类型的DataType也没考虑到,但现在暂时可以用type alias规避,先不追求完美吧。
- scanner/IStructScanner.go: 修复若干细节, 并添加返回类型的扫描
- scanner/IStructScanner_test.go:struct扫描器的单元测试
common/Tokens.go
修复MatchBasicType方法的正则匹配bug。其实func类型的DataType也没考虑到,但现在暂时可以用type alias规避,先不追求完美吧。
func (me *tTokens) MatchBasicType(s string) (bool, string) {list := []string{"int","string","bool","byte","int32","int64","uint32","uint64","float32","float64","int8","uint8","int16","uint16",`time\.Time`,}for _, it := range list {if ok, t := me.MatchRegexp(s, "^"+it+`(\s+|$)`); ok {return true, strings.TrimSpace(t)}}return false, ""}
scanner/IStructScanner.go
修复若干细节, 并添加返回类型的扫描
package scannerimport ("errors""learning/gooop/spring/autogen/common""learning/gooop/spring/autogen/domain""regexp""strings")type IStructScanner interface {ScanStruct(file *domain.CodeFileInfo)}type tStructScanner intfunc (me *tStructScanner) ScanStruct(file *domain.CodeFileInfo) {bInStruct := falsevStructs := []*domain.StructInfo{nil}for lineNO, line := range file.CleanLines {if bInStruct {// end?if gStructEndRegexp.MatchString(line) {me.scanMethod(vStructs[0], lineNO+1)file.AppendStruct(vStructs[0])bInStruct = falsevStructs[0] = nilcontinue}// in struct blockok, fname, ftype := me.scanField(line)if ok {vStructs[0].AppendField(lineNO, fname, ftype)}} else {// not in struck block// matching start?if gStructStartRegexp.MatchString(line) {bInStruct = truess := gStructStartRegexp.FindStringSubmatch(line)vStructs[0] = domain.NewStructInfo()vStructs[0].LineNO = lineNOvStructs[0].CodeFile = filevStructs[0].Name = ss[1]continue}}}}func (me *tStructScanner) scanField(line string) (ok bool, fldName string, fldType string) {if !gFieldStartRegexp.MatchString(line) {return false, "", ""}t := lines1 := gFieldStartRegexp.FindString(t)fldName = strings.TrimSpace(s1)t = t[len(s1):]b2, s2 := common.Tokens.MatchDataType(t)if !b2 {return false, "", ""}fldType = strings.TrimSpace(s2)return true, fldName, fldType}func (me *tStructScanner) scanMethod(stru *domain.StructInfo, fromLineNO int) {for i, limit := fromLineNO, len(stru.CodeFile.CleanLines); i < limit; i++ {line := stru.CodeFile.CleanLines[i]if !gMethodStartRegex.MatchString(line) {continue}ss := gMethodStartRegex.FindStringSubmatch(line)// declaredeclare := ss[0]offset := len(declare)// receiverreceiver := ss[1]if receiver != stru.Name {continue}method := domain.NewMethodInfo()// namemethod.Name = ss[2]// method input argse, args := me.scanMethodArgs(method, strings.TrimSpace(line[offset:]))if e != nil {panic(e)}offset += len(args)// method return argse = me.scanReturnArgs(method, strings.TrimSpace(line[offset:]))if e != nil {panic(e)}// end scan methodstru.AppendMethod(method)}}func (me *tStructScanner) scanMethodArgs(method *domain.MethodInfo, s string) (error, string) {t := soffset := 0for {// nameb1, s1 := common.Tokens.MatchRegexp(t, `^\w+(\s*,\s*\w+)?\s+`)if !b1 {break}argNames := strings.TrimSpace(s1)offset += len(s1)t = s[offset:]// data typeb2, s2 := common.Tokens.MatchDataType(t)if !b2 {return gInvalidMethodArgs, ""}argDataType := s2offset += len(s2)t = s[offset:]for _, it := range strings.Split(argNames, ",") {method.AppendArgument(it, argDataType)}// ,\s+b3, s3 := common.Tokens.MatchRegexp(t, `\s*,\s*`)if !b3 {break}offset += len(s3)t = s[offset:]}b4, s4 := common.Tokens.MatchRegexp(t, `^\s*\)`)if !b4 {return errors.New("expecting right bracket"), ""}offset += len(s4)return nil, s[0:offset]}func (me *tStructScanner) scanReturnArgs(method *domain.MethodInfo, s string) error {// no args?if gMethodEndRegexp.MatchString(s) {return nil}// args startt := sb1, s1 := common.Tokens.MatchRegexp(t, `\s*\(\s*`)if !b1 {return errors.New("expecting left bracket")}t = t[len(s1):]// unnamed args?b2, s2 := common.Tokens.MatchDataType(t)if b2 {t = t[len(s2):]method.AppendUnnamedReturn(s2)// more unnamed args?for {b3, s3 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)if !b3 {break}t = t[len(s3):]b4, s4 := common.Tokens.MatchDataType(t)if !b4 {return errors.New("expecting data type")}t = t[len(s4):]method.AppendUnnamedReturn(s4)}} else {// named args?for {// nameb3, s3 := common.Tokens.MatchIdentifier(t)if !b3 {return errors.New("expecting identifier")}t = t[len(s3):]// \s+b4, s4 := common.Tokens.MatchSpaces(t)if !b4 {return errors.New("expecting spaces")}t = t[len(s4):]// typeb5, s5 := common.Tokens.MatchDataType(t)if !b5 {return errors.New("expecting data type")}t = t[len(s5):]// more?b6, s6 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)if b6 {// yes moret = t[len(s6):]} else {// no morebreak}}}// arguments endb7, _ := common.Tokens.MatchRegexp(t, `^\s*\)\s*`)if !b7 {return errors.New("expecting end of arguments")}return nil}var gStructStartRegexp = regexp.MustCompile(`^\s*type\s+(\w+)\s+struct\s+\{`)var gStructEndRegexp = regexp.MustCompile(`^\s*}`)var gFieldStartRegexp = regexp.MustCompile(`^\s*\w+\s+`)var gMethodStartRegex = regexp.MustCompile(`^\s*func\s+\(\s*\w+\s+\*?(\w+)\s*\)\s+(\w+)\s*\(`)var gInvalidMethodArgs = errors.New("invalid method arguments")var gMethodEndRegexp = regexp.MustCompile(`^\s*\{`)var DefaultStructScanner IStructScanner = new(tStructScanner)
scanner/IStructScanner_test.go
struct扫描器的单元测试
package scannerimport ("encoding/json""learning/gooop/spring/autogen/domain""strings""testing")func Test_StructScan(t *testing.T) {s := `type _mystruct struct {`t.Log(gStructStartRegexp.MatchString(s))code := `type StructInfo struct {LineNO intName stringCodeFile *CodeFileInfoFields []*FieldInfoMethods []*MethodInfoAnnotations []*AnnotationInfo}func NewStructInfo() *StructInfo {it := new(StructInfo)it.Fields = []*FieldInfo{}it.Methods = []*MethodInfo{}it.Annotations = []*AnnotationInfo{}return it}func (me *StructInfo) AppendField(lineNO int, name string, dataType string) {fld := NewFieldInfo()fld.Struct = mefld.LineNO = lineNOfld.Name = namefld.DataType = dataTypeme.Fields = append(me.Fields, fld)}func (me *StructInfo) AppendMethod(method *MethodInfo) {me.Methods = append(me.Methods, method)}func (me *StructInfo) AppendAnnotation(ant *AnnotationInfo) {me.Annotations = append(me.Annotations, ant)}`file := domain.NewCodeFileInfo()file.CleanLines = strings.Split(code, "\n")DefaultStructScanner.ScanStruct(file)file.CleanLines = nilj, e := json.MarshalIndent(file.Structs, "", " ")if e != nil {panic(e)}t.Log(string(j))}
测试输出
API server listening at: [::]:36077=== RUN Test_StructScanIStructScanner_test.go:12: trueIStructScanner_test.go:58: [{"LineNO": 1,"Name": "StructInfo","Fields": [{"LineNO": 2,"Name": "LineNO","DataType": "int","Annotations": []},{"LineNO": 3,"Name": "Name","DataType": "string","Annotations": []},{"LineNO": 4,"Name": "CodeFile","DataType": "*CodeFileInfo","Annotations": []},{"LineNO": 5,"Name": "Fields","DataType": "[]*FieldInfo","Annotations": []},{"LineNO": 6,"Name": "Methods","DataType": "[]*MethodInfo","Annotations": []},{"LineNO": 7,"Name": "Annotations","DataType": "[]*AnnotationInfo","Annotations": []}],"Methods": [{"LineNO": 0,"Name": "AppendField","Arguments": [{"Name": "lineNO","DataType": "int"},{"Name": "name","DataType": "string"},{"Name": "dataType","DataType": "string"}],"Annotations": [],"Returns": []},{"LineNO": 0,"Name": "AppendMethod","Arguments": [{"Name": "method","DataType": "*MethodInfo"}],"Annotations": [],"Returns": []},{"LineNO": 0,"Name": "AppendAnnotation","Arguments": [{"Name": "ant","DataType": "*AnnotationInfo"}],"Annotations": [],"Returns": []}],"Annotations": []}]--- PASS: Test_StructScan (0.01s)PASSDebugger finished with exit code 0
(未完待续)
