溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

編寫golang編寫注解的靜態(tài)代碼增強器/生成器的方法

發(fā)布時間:2021-10-13 11:31:50 來源:億速云 閱讀:133 作者:iii 欄目:編程語言

本篇內(nèi)容介紹了“編寫golang編寫注解的靜態(tài)代碼增強器/生成器的方法”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

Spring

Spring的主要特性:
1. 控制反轉(zhuǎn)(Inversion of Control, IoC)
2. 面向容器
3. 面向切面(AspectOriented Programming, AOP)

源碼gitee地址:
https://gitee.com/ioly/learning.gooop

原文鏈接:
https://my.oschina.net/ioly

目標

  • 參考spring boot常用注解,使用golang編寫“基于注解的靜態(tài)代碼增強器/生成器”

子目標(Day 7)

  • 因為struct/field/method的掃描是關(guān)鍵,因此今天針對這塊做了單元測試

    • common/Tokens.go:修復(fù)MatchBasicType方法的正則匹配bug。其實func類型的DataType也沒考慮到,但現(xiàn)在暫時可以用type alias規(guī)避,先不追求完美吧。

    • scanner/IStructScanner.go: 修復(fù)若干細節(jié), 并添加返回類型的掃描

    • scanner/IStructScanner_test.go:struct掃描器的單元測試

common/Tokens.go

修復(fù)MatchBasicType方法的正則匹配bug。其實func類型的DataType也沒考慮到,但現(xiàn)在暫時可以用type alias規(guī)避,先不追求完美吧。

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

修復(fù)若干細節(jié), 并添加返回類型的掃描

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編寫注解的靜態(tài)代碼增強器/生成器的方法”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI