温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

编写golang编写注解的静态代码增强器/生成器的方法

发布时间:2021-10-13 11:31:50 来源:亿速云 阅读:133 作者:iii 栏目:编程语言

本篇内容介绍了“编写golang编写注解的静态代码增强器/生成器的方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

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

“编写golang编写注解的静态代码增强器/生成器的方法”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI