手撸golang 仿spring ioc/aop 之8 扫码3

缘起

最近阅读 [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 scanner

import (
    "errors"
    "learning/gooop/spring/autogen/common"
    "learning/gooop/spring/autogen/domain"
    "regexp"
    "strings"
)

type IStructScanner interface {
    ScanStruct(file *domain.CodeFileInfo)
}

type tStructScanner int

func (me *tStructScanner) ScanStruct(file *domain.CodeFileInfo) {
    bInStruct := false
    vStructs := []*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 = false
                vStructs[0] = nil
                continue
            }

            // in struct block
            ok, 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 = true
                ss := gStructStartRegexp.FindStringSubmatch(line)

                vStructs[0] = domain.NewStructInfo()
                vStructs[0].LineNO = lineNO
                vStructs[0].CodeFile = file
                vStructs[0].Name = ss[1]
                continue
            }
        }
    }
}

func (me *tStructScanner) scanField(line string) (ok bool, fldName string, fldType string) {
    if !gFieldStartRegexp.MatchString(line) {
        return false, "", ""
    }

    t := line
    s1 := 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)

        // declare
        declare := ss[0]
        offset := len(declare)

        // receiver
        receiver := ss[1]
        if receiver != stru.Name {
            continue
        }
        method := domain.NewMethodInfo()

        // name
        method.Name = ss[2]

        // method input args
        e, args := me.scanMethodArgs(method, strings.TrimSpace(line[offset:]))
        if e != nil {
            panic(e)
        }
        offset += len(args)

        // method return args
        e = me.scanReturnArgs(method, strings.TrimSpace(line[offset:]))
        if e != nil {
            panic(e)
        }

        // end scan method
        stru.AppendMethod(method)
    }
}

func (me *tStructScanner) scanMethodArgs(method *domain.MethodInfo, s string) (error, string) {
    t := s
    offset := 0
    for {
        // name
        b1, s1 := common.Tokens.MatchRegexp(t, `^\w+(\s*,\s*\w+)?\s+`)
        if !b1 {
            break
        }
        argNames := strings.TrimSpace(s1)
        offset += len(s1)
        t = s[offset:]

        // data type
        b2, s2 := common.Tokens.MatchDataType(t)
        if !b2 {
            return gInvalidMethodArgs, ""
        }
        argDataType := s2
        offset += 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 start
    t := s
    b1, 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 {
            // name
            b3, 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):]

            // type
            b5, 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 more
                t = t[len(s6):]
            } else {
                // no more
                break
            }
        }
    }

    // arguments end
    b7, _ := 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 scanner

import (
    "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      int
    Name        string
    CodeFile    *CodeFileInfo
    Fields      []*FieldInfo
    Methods     []*MethodInfo
    Annotations []*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 = me
    fld.LineNO = lineNO
    fld.Name = name
    fld.DataType = dataType
    me.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 = nil
    j, e := json.MarshalIndent(file.Structs, "", "  ")
    if e != nil {
        panic(e)
    }
    t.Log(string(j))
}

测试输出

API server listening at: [::]:36077
=== RUN   Test_StructScan
    IStructScanner_test.go:12: true
    IStructScanner_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)
PASS

Debugger finished with exit code 0

(未完待续)


ioly
42 声望20 粉丝

想当将军 首先要肯打