golang源码分析:easyjson(2)

2023-09-06 19:18:08 浏览数 (1)

easyjson生成代码工具的入口位于:

github.com/mailru/easyjson@v0.7.7/easyjson/main.go

代码语言:javascript复制
func main() {
    for _, fname := range files {
     if err := generate(fname); err != nil {
代码语言:javascript复制
func generate(fname string) (err error) {
    fInfo, err := os.Stat(fname)
    p := parser.Parser{AllStructs: *allStructs}
    if err := p.Parse(fname, fInfo.IsDir()); err != nil {
    
  outName = filepath.Join(fname, p.PkgName "_easyjson.go")
    g := bootstrap.Generator{
    BuildTags:                trimmedBuildTags,
    GenBuildFlags:            trimmedGenBuildFlags,
    PkgPath:                  p.PkgPath,
    PkgName:                  p.PkgName,
    Types:                    p.StructNames,
    SnakeCase:                *snakeCase,
    LowerCamelCase:           *lowerCamelCase,
    NoStdMarshalers:          *noStdMarshalers,
    DisallowUnknownFields:    *disallowUnknownFields,
    SkipMemberNameUnescaping: *skipMemberNameUnescaping,
    OmitEmpty:                *omitEmpty,
    LeaveTemps:               *leaveTemps,
    OutName:                  outName,
    StubsOnly:                *stubs,
    NoFormat:                 *noformat,
    SimpleBytes:              *simpleBytes,
  }
   if err := g.Run(); err != nil {

它首先遍历go struct的抽象语法树,解析得到生成代码需要的struct的各种信息,然后结合输入选项,准备好各种参数后使用Run方法来生成代码。

代码语言:javascript复制
var allStructs = flag.Bool("all", false, "generate marshaler/unmarshalers for all structs in a file")

golang struct 抽象语法树解析的代码位于:

github.com/mailru/easyjson@v0.7.7/parser/parser.go

代码语言:javascript复制
type Parser struct {
  PkgPath     string
  PkgName     string
  StructNames []string
  AllStructs  bool
}

重点看下它的Parse方法,和常用抽象语法树walker的思路一样,采用访问者模式提取有用信息:

代码语言:javascript复制
func (p *Parser) Parse(fname string, isDir bool) error {
      if p.PkgPath, err = getPkgPath(fname, isDir); err != nil {
      fset := token.NewFileSet()
      packages, err := parser.ParseDir(fset, fname, excludeTestFiles, parser.ParseComments)
     for _, pckg := range packages {
      ast.Walk(&visitor{Parser: p}, pckg)

访问者定义如下:

代码语言:javascript复制
type visitor struct {
  *Parser
  name string
}

它的核心方法是Visit方法:可以看到,它除了解析需要的包的信息外,多数逻辑都是通过遍历语法树的各个节点,提取struct的field的各种信息。

代码语言:javascript复制
func (v *visitor) Visit(n ast.Node) (w ast.Visitor) {
  switch n := n.(type) {
  case *ast.Package:
    return v
  case *ast.File:
    v.PkgName = n.Name.String()
    return v


  case *ast.GenDecl:
    skip, explicit := v.needType(n.Doc)


    if skip || explicit {
      for _, nc := range n.Specs {
        switch nct := nc.(type) {
        case *ast.TypeSpec:
          nct.Doc = n.Doc
        }
      }
    }


    return v
  case *ast.TypeSpec:
    skip, explicit := v.needType(n.Doc)
    if skip {
      return nil
    }
    if !explicit && !v.AllStructs {
      return nil
    }


    v.name = n.Name.String()


    // Allow to specify non-structs explicitly independent of '-all' flag.
    if explicit {
      v.StructNames = append(v.StructNames, v.name)
      return nil
    }


    return v
  case *ast.StructType:
    v.StructNames = append(v.StructNames, v.name)
    return nil
  }
  return nil
}

解析完抽象语法树后的信息放在Generator里面,代码位于:

github.com/mailru/easyjson@v0.7.7/bootstrap/bootstrap.go

代码语言:javascript复制
type Generator struct {
  PkgPath, PkgName string
  Types            []string


  NoStdMarshalers          bool
  SnakeCase                bool
  LowerCamelCase           bool
  OmitEmpty                bool
  DisallowUnknownFields    bool
  SkipMemberNameUnescaping bool


  OutName       string
  BuildTags     string
  GenBuildFlags string


  StubsOnly   bool
  LeaveTemps  bool
  NoFormat    bool
  SimpleBytes bool
}

Run方法完成代码的生成:对应每个类型先生成对应的简单的桩代码,用于后面生成详细代码做准备,只包含几个函数定义。然后生成中间代码,最后通过go命令,运行中间代码生成目标代码。

代码语言:javascript复制
  func (g *Generator) Run() error {
      if err := g.writeStub(); err != nil {
      path, err := g.writeMain()

      f, err := os.Create(g.OutName   ".tmp")
      execArgs := []string{"run"}
      execArgs = append(execArgs, "-tags", g.BuildTags, filepath.Base(path))
      cmd := exec.Command("go", execArgs...)
      if err = cmd.Run(); err != nil {
       out, err := format.Source(in)
      return ioutil.WriteFile(g.OutName, out, 0644)
代码语言:javascript复制
func (g *Generator) writeStub() error {
   for _, t := range g.Types {
            fmt.Fprintln(f, "func (", t, ") MarshalEasyJSON(w *jwriter.Writer) {}")
            fmt.Fprintln(f, "func (*", t, ") UnmarshalEasyJSON(l *jlexer.Lexer) {}")
代码语言:javascript复制
// writeMain creates a .go file that launches the generator if 'go run'.
func (g *Generator) writeMain() (path string, err error) {
  f, err := ioutil.TempFile(filepath.Dir(g.OutName), "easyjson-bootstrap")
  
  fmt.Fprintln(f, "func main() {")
  fmt.Fprintf(f, "  g := gen.NewGenerator(%q)n", filepath.Base(g.OutName))
  fmt.Fprintf(f, "  g.SetPkg(%q, %q)n", g.PkgName, g.PkgPath)
  
  sort.Strings(g.Types)
  for _, v := range g.Types {
    fmt.Fprintln(f, "  g.Add(pkg.EasyJSON_exporter_" v "(nil))")
  }
  
  fmt.Fprintln(f, "  if err := g.Run(os.Stdout); err != nil {")

重点看下,中间writeMain代码,它生成生成用于生成最终代码的main函数。在main函数里初始化了gen.NewGenerator,并且设置相关包,然后调用里generator的Run方法。

代码语言:javascript复制
const genPackage = "github.com/mailru/easyjson/gen"
const pkgWriter = "github.com/mailru/easyjson/jwriter"
const pkgLexer = "github.com/mailru/easyjson/jlexer"

对应的定义位于:github.com/mailru/easyjson@v0.7.7/gen/generator.go

代码语言:javascript复制
func NewGenerator(filename string) *Generator {
        ret := &Generator{
    imports: map[string]string{
      pkgWriter:       "jwriter",
      pkgLexer:        "jlexer",
      pkgEasyJSON:     "easyjson",
      "encoding/json": "json",
    },
    fieldNamer:    DefaultFieldNamer{},
    marshalers:    make(map[reflect.Type]bool),
    typesSeen:     make(map[reflect.Type]bool),
    functionNames: make(map[string]reflect.Type),
  }
代码语言:javascript复制
type Generator struct {
  out *bytes.Buffer


  pkgName    string
  pkgPath    string
  buildTags  string
  hashString string


  varCounter int


  noStdMarshalers          bool
  omitEmpty                bool
  disallowUnknownFields    bool
  fieldNamer               FieldNamer
  simpleBytes              bool
  skipMemberNameUnescaping bool


  // package path to local alias map for tracking imports
  imports map[string]string


  // types that marshalers were requested for by user
  marshalers map[reflect.Type]bool


  // types that encoders were already generated for
  typesSeen map[reflect.Type]bool


  // types that encoders were requested for (e.g. by encoders of other types)
  typesUnseen []reflect.Type


  // function name to relevant type maps to track names of de-/encoders in
  // case of a name clash or unnamed structs
  functionNames map[string]reflect.Type
}

在Run函数里完成了需要的函数的生成,比如生成解码函数、编码函数等:

代码语言:javascript复制
func (g *Generator) Run(out io.Writer) error {
      if err := g.genDecoder(t); err != nil {
      if err := g.genEncoder(t); err != nil {
      if err := g.genStructMarshaler(t); err != nil {
      if err := g.genStructUnmarshaler(t); err != nil {
      g.printHeader()

解码函数定义在github.com/mailru/easyjson@v0.7.7/gen/decoder.go

它是根据反射得到具体的类型,然后根据类型来生成对应的代码。

代码语言:javascript复制
func (g *Generator) genDecoder(t reflect.Type) error {
  switch t.Kind() {
  case reflect.Slice, reflect.Array, reflect.Map:
    return g.genSliceArrayDecoder(t)
  default:
    return g.genStructDecoder(t)
  }
}

剩下的就是代码字符串的拼接:

代码语言:javascript复制
func (g *Generator) genSliceArrayDecoder(t reflect.Type) error {
  fname := g.getDecoderName(t)
  typ := g.getType(t)

  fmt.Fprintln(g.out, "func " fname "(in *jlexer.Lexer, out *" typ ") {")
  fmt.Fprintln(g.out, " isTopLevel := in.IsStart()")
  err := g.genTypeDecoderNoCheck(t, "*out", fieldTags{}, 1)

生成解码函数的过程类似,也是基于反射:

代码语言:javascript复制
func (g *Generator) genTypeDecoderNoCheck(t reflect.Type, out string, tags fieldTags, indent int) error {
  if dec := customDecoders[t.String()]; dec != "" {
    fmt.Fprintln(g.out, ws out " = " dec)
      switch t.Kind() {
  case reflect.Slice:
    tmpVar := g.uniqueVarName()
    elem := t.Elem()


    if elem.Kind() == reflect.Uint8 && elem.Name() == "uint8" {
      fmt.Fprintln(g.out, ws "if in.IsNull() {")

对于结构体,它需要生成每个field对应的编解码代码:

代码语言:javascript复制
func (g *Generator) genStructDecoder(t reflect.Type) error {
  fname := g.getDecoderName(t)
  typ := g.getType(t)
      fs, err := getStructFields(t)
      for _, f := range fs {
    g.genRequiredFieldSet(t, f)
  }
代码语言:javascript复制
func getStructFields(t reflect.Type) ([]reflect.StructField, error) {
   for i := 0; i < t.NumField(); i   {
    f := t.Field(i)
    tags := parseFieldTags(f)

在序列化的时候需要一个writer,它内部其实是一个Buffer

代码语言:javascript复制
type Writer struct {
  Flags Flags


  Error        error
  Buffer       buffer.Buffer
  NoEscapeHTML bool
}

在反序列化的时候需要json得lexer,它的源码位于:

github.com/mailru/easyjson@v0.7.7/jlexer/lexer.go

代码语言:javascript复制
type Lexer struct {
  Data []byte // Input data given to the lexer.


  start int   // Start of the current token.
  pos   int   // Current unscanned position in the input stream.
  token token // Last scanned token, if token.kind != tokenUndef.


  firstElement bool // Whether current element is the first in array or an object.
  wantSep      byte // A comma or a colon character, which need to occur before a token.


  UseMultipleErrors bool          // If we want to use multiple errors.
  fatalError        error         // Fatal error occurred during lexing. It is usually a syntax error.
  multipleErrors    []*LexerError // Semantic errors occurred during lexing. Marshalling will be continued after finding this errors.
}

它本质上是一个json反解析状态机。

0 人点赞