| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package main |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "go/ast" |
| "go/format" |
| "go/token" |
| "go/types" |
| "log" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "golang.org/x/tools/go/packages" |
| |
| "github.com/apache/plc4x/plc4go/tools/common" |
| ) |
| |
| var ( |
| typeNames = flag.String("type", "", "comma-separated list of type names; must be set") |
| output = flag.String("output", "", "output file name; default srcdir/<type>_plc4xgen.go") |
| prefix = flag.String("prefix", "", "prefix for all generated files") |
| suffix = flag.String("suffix", "", "suffix for all generated files") |
| buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") |
| licenseFile = flag.String("licenseFile", ".plc4xLicencer.header", "file containing the license (will be searched upwards)") |
| tests = flag.Bool("tests", false, "look at test files") |
| pkgIndex = flag.Int("pkgIndex", -1, "package index (can enforce a different index if multiple packages are found)") |
| verbose = flag.Bool("verbose", false, "verbosity") |
| ) |
| |
| // Usage is a replacement usage function for the flags package. |
| func Usage() { |
| _, _ = fmt.Fprintf(os.Stderr, "Usage of plc4xGenerator:\n") |
| _, _ = fmt.Fprintf(os.Stderr, "\tplc4xGenerator [flags] -type T [directory]\n") |
| _, _ = fmt.Fprintf(os.Stderr, "\tplc4xGenerator [flags] -type T files... # Must be a single package\n") |
| _, _ = fmt.Fprintf(os.Stderr, "Flags:\n") |
| flag.PrintDefaults() |
| } |
| |
| var outputFile string // only used for test |
| |
| func main() { |
| log.SetFlags(0) |
| log.SetPrefix("plc4xGenerator: ") |
| flag.Usage = Usage |
| flag.Parse() |
| if len(*typeNames) == 0 { |
| flag.Usage() |
| os.Exit(2) |
| } |
| typeList := strings.Split(*typeNames, ",") |
| var tags []string |
| if len(*buildTags) > 0 { |
| tags = strings.Split(*buildTags, ",") |
| } |
| |
| args := flag.Args() |
| if len(args) == 0 { |
| args = []string{"."} |
| } |
| |
| // Parse the package once. |
| var dir string |
| generator := Generator{} |
| if len(args) == 1 && isDirectory(args[0]) { |
| dir = args[0] |
| } else { |
| if len(tags) != 0 { |
| log.Fatal("-tags option applies only to directories, not when files are specified") |
| } |
| dir = filepath.Dir(args[0]) |
| } |
| |
| generator.parsePackage(args, tags) |
| |
| // Print the header and package clause. |
| licenseFileName := *licenseFile |
| licenceContent := common.GetLicenseFileContent(licenseFileName, *verbose) |
| generator.Printf("%s", licenceContent) |
| generator.Printf("\n") |
| generator.Printf("// Code generated by \"plc4xGenerator %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) |
| generator.Printf("\n") |
| generator.Printf("package %s", generator.pkg.name) |
| generator.Printf("\n") |
| generator.Printf("import (\n") |
| generator.Printf("\t\"context\"\n") |
| generator.Printf("\t\"encoding/binary\"\n") |
| generator.Printf("") |
| generator.Printf("\t\"github.com/apache/plc4x/plc4go/spi/utils\"\n") |
| generator.Printf("\t\"fmt\"\n") |
| generator.Printf(")\n") |
| generator.Printf("\n") |
| generator.Printf("var _ = fmt.Printf\n") |
| |
| // Run generate for each type. |
| for _, typeName := range typeList { |
| generator.generate(typeName) |
| } |
| |
| // Format the output. |
| src := generator.format() |
| |
| // Write to file. |
| outputName := *output |
| if outputName == "" { |
| baseName := fmt.Sprintf("%s_plc4xgen.go", typeList[0]) |
| outputName = filepath.Join(dir, baseName) |
| } |
| if *prefix != "" { |
| directory, file := filepath.Split(outputName) |
| outputName = filepath.Join(directory, *prefix+file) |
| } |
| |
| if *suffix != "" { |
| directory, file := filepath.Split(outputName) |
| ext := filepath.Ext(file) |
| outputName = filepath.Join(directory, strings.ReplaceAll(file, ext, "")+*suffix+ext) |
| } |
| |
| log.Printf("Writing to %s\n", outputName) |
| err := os.WriteFile(outputName, src, 0644) |
| if err != nil { |
| log.Fatalf("writing output: %s", err) |
| } |
| outputFile = outputName |
| } |
| |
| // isDirectory reports whether the named file is a directory. |
| func isDirectory(name string) bool { |
| info, err := os.Stat(name) |
| if err != nil { |
| log.Fatal(err) |
| } |
| return info.IsDir() |
| } |
| |
| // Generator holds the state of the analysis. Primarily used to buffer |
| // the output for format.Source. |
| type Generator struct { |
| buf bytes.Buffer // Accumulated output. |
| pkg *Package // Package we are scanning. |
| } |
| |
| func (g *Generator) Printf(format string, args ...any) { |
| _, _ = fmt.Fprintf(&g.buf, format, args...) |
| } |
| |
| // File holds a single parsed file and associated data. |
| type File struct { |
| pkg *Package // Package to which this file belongs. |
| file *ast.File // Parsed AST. |
| // These fields are reset for each type being generated. |
| typeName string // Name of the type. |
| fields []Field // Accumulator for fields of that type. |
| |
| trimPrefix string |
| lineComment bool |
| } |
| |
| type Package struct { |
| name string |
| defs map[*ast.Ident]types.Object |
| files []*File |
| } |
| |
| // parsePackage analyzes the single package constructed from the patterns and tags. |
| // parsePackage exits if there is an error. |
| func (g *Generator) parsePackage(patterns []string, tags []string) { |
| cfg := &packages.Config{ |
| Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax, |
| Tests: *tests, |
| BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))}, |
| } |
| pkgs, err := packages.Load(cfg, patterns...) |
| if err != nil { |
| log.Fatal(err) |
| } |
| index := 0 |
| if *pkgIndex > 0 { |
| index = *pkgIndex |
| } |
| if len(pkgs) != 1 { |
| if *pkgIndex < 0 { |
| log.Fatalf("error: %d packages found (%v)", len(pkgs), pkgs) |
| } |
| } |
| g.addPackage(pkgs[index]) |
| } |
| |
| // addPackage adds a type checked Package and its syntax files to the generator. |
| func (g *Generator) addPackage(pkg *packages.Package) { |
| g.pkg = &Package{ |
| name: pkg.Name, |
| defs: pkg.TypesInfo.Defs, |
| files: make([]*File, len(pkg.Syntax)), |
| } |
| |
| for i, file := range pkg.Syntax { |
| g.pkg.files[i] = &File{ |
| file: file, |
| pkg: g.pkg, |
| } |
| } |
| } |
| |
| // generate produces the String method for the named type. |
| func (g *Generator) generate(typeName string) { |
| fields := make([]Field, 0, 100) |
| for _, file := range g.pkg.files { |
| // Set the state for this run of the walker. |
| file.typeName = typeName |
| if file.file != nil { |
| ast.Inspect(file.file, file.genDecl) |
| fields = append(fields, file.fields...) |
| } |
| } |
| if len(fields) == 0 { |
| if *verbose { |
| log.Printf("no fields defined for type %s", typeName) |
| } |
| } |
| // TODO: for now we remove Default from the start (maybe move that to an option) |
| logicalTypeName := "\"" + strings.TrimPrefix(typeName, "Default") + "\"" |
| |
| // Generate code that will fail if the constants change value. |
| g.Printf("func (d *%s) Serialize() ([]byte, error) {\n", typeName) |
| g.Printf("\tif d == nil {\n") |
| g.Printf("\t\treturn nil, fmt.Errorf(\"(*DeviceInfoCache)(nil)\")\n") |
| g.Printf("\t}\n") |
| g.Printf("\twb := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.BigEndian))\n") |
| g.Printf("\tif err := d.SerializeWithWriteBuffer(context.Background(), wb); err != nil {\n") |
| g.Printf("\t\treturn nil, err\n") |
| g.Printf("\t}\n") |
| g.Printf("\treturn wb.GetBytes(), nil\n") |
| g.Printf("}\n\n") |
| g.Printf("func (d *%s) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {\n", typeName) |
| g.Printf("\tif d == nil {\n") |
| g.Printf("\t\treturn fmt.Errorf(\"(*DeviceInfoCache)(nil)\")\n") |
| g.Printf("\t}\n") |
| g.Printf("\tif err := writeBuffer.PushContext(%s); err != nil {\n", logicalTypeName) |
| g.Printf("\t\treturn err\n") |
| g.Printf("\t}\n") |
| for _, field := range fields { |
| fieldType := field.fieldType |
| if field.isDelegate { |
| g.Printf("\t\t\tif err := d.%s.SerializeWithWriteBuffer(ctx, writeBuffer); err != nil {\n", fieldType.(*ast.Ident).Name) |
| g.Printf("\t\t\t\treturn err\n") |
| g.Printf("\t\t\t}\n") |
| continue |
| } |
| fieldName := field.name |
| fieldNameUntitled := "\"" + unTitle(fieldName) + "\"" |
| if field.hasLocker != "" { |
| g.Printf("if err := func()error {\n") |
| g.Printf("\td.%s.Lock()\n", field.hasLocker) |
| g.Printf("\tdefer d.%s.Unlock()\n", field.hasLocker) |
| } |
| needsDereference := false |
| if starFieldType, ok := fieldType.(*ast.StarExpr); ok { |
| fieldType = starFieldType.X |
| needsDereference = true |
| } |
| if field.isStringer { |
| if needsDereference { |
| g.Printf("if d.%s != nil {", field.name) |
| } |
| g.Printf(stringFieldSerialize, "d."+field.name+".String()", fieldNameUntitled) |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| if needsDereference { |
| g.Printf("}\n") |
| } |
| continue |
| } |
| if field.asPtr { |
| g.Printf(stringFieldSerialize, "fmt.Sprintf(\"%p\", d."+field.name+")", fieldNameUntitled) |
| continue |
| } |
| if field.directSerialize { |
| indentTimes := 0 |
| if needsDereference { |
| indentTimes++ |
| g.Printf("if d.%s != nil {", field.name) |
| } |
| g.Printf(indent(indentTimes, serializableDirectFieldTemplate), "d."+field.name+"", fieldNameUntitled) |
| if needsDereference { |
| g.Printf("}\n") |
| } |
| continue |
| } |
| switch fieldType := fieldType.(type) { |
| case *ast.SelectorExpr: |
| { |
| // TODO: bit hacky but not sure how else we catch those ones |
| x := fieldType.X |
| sel := fieldType.Sel |
| xIdent, xIsIdent := x.(*ast.Ident) |
| if xIsIdent { |
| if xIdent.Name == "atomic" { |
| if sel.Name == "Uint32" { |
| g.Printf(uint32FieldSerialize, "d."+field.name+".Load()", fieldNameUntitled) |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| continue |
| } |
| if sel.Name == "Uint64" { |
| g.Printf(uint64FieldSerialize, "d."+field.name+".Load()", fieldNameUntitled) |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| continue |
| } |
| if sel.Name == "Int32" { |
| g.Printf(int32FieldSerialize, "d."+field.name+".Load()", fieldNameUntitled) |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| continue |
| } |
| if sel.Name == "Bool" { |
| g.Printf(boolFieldSerialize, "d."+field.name+".Load()", fieldNameUntitled) |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| continue |
| } |
| if sel.Name == "Value" { |
| g.Printf(serializableFieldTemplate, "d."+field.name+".Load()", fieldNameUntitled) |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| continue |
| } |
| } |
| if xIdent.Name == "sync" { |
| if *verbose { |
| fmt.Printf("\t skipping field %s because it is %v.%v\n", fieldName, x, sel) |
| } |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| continue |
| } |
| if xIdent.Name == "zerolog" { |
| if *verbose { |
| fmt.Printf("\t skipping field %s because it is %v.%v\n", fieldName, x, sel) |
| } |
| continue |
| } |
| if xIdent.Name == "time" { |
| if sel.Name == "Time" || sel.Name == "Duration" { |
| deref := "" |
| indentTimes := 0 |
| if needsDereference { |
| deref = "*" |
| indentTimes++ |
| g.Printf("if d.%s != nil {", field.name) |
| } |
| g.Printf(indent(indentTimes, stringFieldSerialize), "fmt.Sprintf(\"%s\", "+deref+"d."+field.name+")", fieldNameUntitled) |
| |
| if needsDereference { |
| g.Printf("}\n") |
| } |
| continue |
| } |
| } |
| } |
| } |
| g.Printf(serializableFieldTemplate, "d."+field.name, fieldNameUntitled) |
| case *ast.IndexExpr: |
| x := fieldType.X |
| if fieldType, isxFieldSelector := x.(*ast.SelectorExpr); isxFieldSelector { // TODO: we need to refactor this so we can reuse... |
| xIdent, xIsIdent := fieldType.X.(*ast.Ident) |
| sel := fieldType.Sel |
| if xIsIdent && xIdent.Name == "atomic" && sel.Name == "Pointer" { |
| g.Printf(atomicPointerFieldTemplate, "d."+field.name, field.name, fieldNameUntitled) |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| continue |
| } |
| } |
| if *verbose { |
| fmt.Printf("Warn: no support yet for %#q\n", fieldType) |
| } |
| continue |
| case *ast.Ident: |
| deref := "" |
| indentTimes := 0 |
| if needsDereference { |
| deref = "*" |
| indentTimes++ |
| g.Printf("if d.%s != nil {", field.name) |
| } |
| switch fieldType.Name { |
| case "byte": |
| g.Printf(indent(indentTimes, byteFieldSerialize), deref+"d."+field.name, fieldNameUntitled) |
| case "int": |
| g.Printf(indent(indentTimes, int64FieldSerialize), "int64("+deref+"d."+field.name+")", fieldNameUntitled) |
| case "int32": |
| g.Printf(indent(indentTimes, int32FieldSerialize), "int32("+deref+"d."+field.name+")", fieldNameUntitled) |
| case "uint16": |
| g.Printf(indent(indentTimes, uint16FieldSerialize), deref+"d."+field.name, fieldNameUntitled) |
| case "uint32": |
| g.Printf(indent(indentTimes, uint32FieldSerialize), deref+"d."+field.name, fieldNameUntitled) |
| case "bool": |
| g.Printf(indent(indentTimes, boolFieldSerialize), deref+"d."+field.name, fieldNameUntitled) |
| case "string": |
| g.Printf(indent(indentTimes, stringFieldSerialize), deref+"d."+field.name, fieldNameUntitled) |
| case "error": |
| g.Printf(indent(indentTimes, errorFieldSerialize), deref+"d."+field.name, fieldNameUntitled) |
| default: |
| if *verbose { |
| fmt.Printf("\t Warn: no support implemented for Ident with type %v\n", fieldType) |
| } |
| g.Printf("{\n") |
| g.Printf("_value := fmt.Sprintf(\"%%v\", d.%s)\n", fieldName) |
| g.Printf(stringFieldSerialize, "_value", fieldNameUntitled) |
| g.Printf("}\n") |
| } |
| if needsDereference { |
| g.Printf("}\n") |
| } |
| case *ast.ArrayType: |
| if eltType, ok := fieldType.Elt.(*ast.Ident); ok && eltType.Name == "byte" { |
| g.Printf("if err := writeBuffer.WriteByteArray(%s, d.%s); err != nil {\n", fieldNameUntitled, field.name) |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } else { |
| g.Printf("if err := writeBuffer.PushContext(%s, utils.WithRenderAsList(true)); err != nil {\n\t\treturn err\n\t}\n", fieldNameUntitled) |
| g.Printf("for _, elem := range d.%s {", field.name) |
| switch eltType := fieldType.Elt.(type) { |
| case *ast.SelectorExpr, *ast.StarExpr: |
| g.Printf("\n\t\tvar elem any = elem\n") |
| g.Printf(serializableFieldTemplate, "elem", "\"value\"") |
| case *ast.Ident: |
| switch eltType.Name { |
| case "int": |
| g.Printf(int64FieldSerialize, "int64(d."+field.name+")", fieldNameUntitled) |
| case "uint32": |
| g.Printf(uint32FieldSerialize, "d."+field.name, fieldNameUntitled) |
| case "bool": |
| g.Printf(boolFieldSerialize, "elem", "\"\"") |
| case "string": |
| g.Printf(stringFieldSerialize, "elem", "\"\"") |
| case "error": |
| g.Printf(errorFieldSerialize, "elem", "\"\"") |
| default: |
| if *verbose { |
| fmt.Printf("\t Warn: no support implemented for Ident within ArrayType for %v\n", fieldType) |
| } |
| g.Printf("_value := fmt.Sprintf(\"%%v\", elem)\n") |
| g.Printf(stringFieldSerialize, "_value", fieldNameUntitled) |
| } |
| } |
| g.Printf("}\n") |
| g.Printf("if err := writeBuffer.PopContext(%s, utils.WithRenderAsList(true)); err != nil {\n\t\treturn err\n\t}\n", fieldNameUntitled) |
| } |
| case *ast.MapType: |
| g.Printf("if err := writeBuffer.PushContext(%s, utils.WithRenderAsList(true)); err != nil {\n\t\treturn err\n\t}\n", fieldNameUntitled) |
| // TODO: we use serializable or strings as we don't want to over-complex this |
| g.Printf("for _name, elem := range d.%s {\n", fieldName) |
| switch keyType := fieldType.Key.(type) { |
| case *ast.Ident: |
| switch keyType.Name { |
| case "uint", "uint8", "uint16", "uint32", "uint64", "int", "int8", "int16", "int32", "int64": // TODO: add other types |
| g.Printf("\t\tname := fmt.Sprintf(\"%s\", _name)\n", "%v") |
| case "string": |
| g.Printf("\t\tname := _name\n") |
| default: |
| g.Printf("\t\tname := fmt.Sprintf(\"%s\", &_name)\n", "%v") |
| } |
| default: |
| g.Printf("\t\tname := fmt.Sprintf(\"%s\", &_name)\n", "%v") |
| } |
| switch eltType := fieldType.Value.(type) { |
| case *ast.StarExpr, *ast.SelectorExpr: |
| g.Printf("\n\t\tvar elem any = elem\n") |
| g.Printf("\t\tif serializable, ok := elem.(utils.Serializable); ok {\n") |
| g.Printf("\t\t\tif err := writeBuffer.PushContext(name); err != nil {\n") |
| g.Printf("\t\t\t\treturn err\n") |
| g.Printf("\t\t\t}\n") |
| g.Printf("\t\t\tif err := serializable.SerializeWithWriteBuffer(ctx, writeBuffer); err != nil {\n") |
| g.Printf("\t\t\t\treturn err\n") |
| g.Printf("\t\t\t}\n") |
| g.Printf("\t\t\tif err := writeBuffer.PopContext(name); err != nil {\n") |
| g.Printf("\t\t\t\treturn err\n") |
| g.Printf("\t\t\t}\n") |
| g.Printf("\t\t} else {\n") |
| g.Printf("\t\t\telemAsString := fmt.Sprintf(\"%%v\", elem)\n") |
| g.Printf("\t\t\tif err := writeBuffer.WriteString(name, uint32(len(elemAsString)*8), elemAsString); err != nil {\n") |
| g.Printf("\t\t\t\treturn err\n") |
| g.Printf("\t\t\t}\n") |
| g.Printf("\t\t}\n") |
| case *ast.Ident: |
| switch eltType.Name { |
| case "bool": |
| g.Printf(boolFieldSerialize, "elem", "name") |
| case "string": |
| g.Printf(stringFieldSerialize, "elem", "name") |
| case "error": |
| g.Printf(errorFieldSerialize, "elem", "name") |
| default: |
| if *verbose { |
| fmt.Printf("\t Warn: no support implemented for Ident within MapType for %v\n", fieldType) |
| } |
| g.Printf("\t\t_value := fmt.Sprintf(\"%%v\", elem)\n") |
| g.Printf(stringFieldSerialize, "_value", "name") |
| } |
| default: |
| if *verbose { |
| fmt.Printf("\t Warn: no support implemented within MapType %v\n", fieldType.Value) |
| } |
| g.Printf("\t\t_value := fmt.Sprintf(\"%%v\", elem)\n") |
| g.Printf(stringFieldSerialize, "_value", "name") |
| } |
| g.Printf("\t}\n") |
| g.Printf("if err := writeBuffer.PopContext(%s, utils.WithRenderAsList(true)); err != nil {\n\t\treturn err\n\t}\n", fieldNameUntitled) |
| case *ast.ChanType: |
| g.Printf(chanFieldSerialize, "d."+field.name, fieldNameUntitled, field.name) |
| case *ast.FuncType: |
| g.Printf(funcFieldSerialize, "d."+field.name, fieldNameUntitled) |
| default: |
| if *verbose { |
| fmt.Printf("Warn: no support implemented %#v\n", fieldType) |
| } |
| } |
| if field.hasLocker != "" { |
| g.Printf("\treturn nil\n") |
| g.Printf("}(); err != nil {\n") |
| g.Printf("\treturn err\n") |
| g.Printf("}\n") |
| } |
| } |
| g.Printf("\tif err := writeBuffer.PopContext(%s); err != nil {\n", logicalTypeName) |
| g.Printf("\t\treturn err\n") |
| g.Printf("\t}\n") |
| g.Printf("\treturn nil\n") |
| g.Printf("}\n") |
| g.Printf("\n") |
| g.Printf(stringerTemplate, typeName) |
| } |
| |
| // format returns the gofmt-ed contents of the Generator's buffer. |
| func (g *Generator) format() []byte { |
| src, err := format.Source(g.buf.Bytes()) |
| if err != nil { |
| // Should never happen, but can arise when developing this code. |
| // The user can compile the output to see the error. |
| log.Printf("warning: internal error: invalid Go generated: %s", err) |
| log.Printf("warning: compile the package to analyze the error") |
| return g.buf.Bytes() |
| } |
| return src |
| } |
| |
| // Field represents a declared field. |
| type Field struct { |
| name string |
| fieldType ast.Expr |
| isDelegate bool |
| isStringer bool |
| asPtr bool |
| directSerialize bool |
| hasLocker string |
| } |
| |
| func (f *Field) String() string { |
| return f.name |
| } |
| |
| // genDecl processes one declaration clause. |
| func (f *File) genDecl(node ast.Node) bool { |
| decl, ok := node.(*ast.GenDecl) |
| if !ok || decl.Tok != token.TYPE { |
| // We only care about type declarations. |
| return true |
| } |
| for _, spec := range decl.Specs { |
| typeSpec := spec.(*ast.TypeSpec) |
| structDecl, ok := typeSpec.Type.(*ast.StructType) |
| if !ok { |
| continue |
| } |
| if typeSpec.Name.Name != f.typeName { |
| continue |
| } |
| if *verbose { |
| fmt.Printf("Handling %s\n", typeSpec.Name.Name) |
| } |
| for _, field := range structDecl.Fields.List { |
| if field.Tag != nil && field.Tag.Value == "`ignore:\"true\"`" { |
| var name string |
| if len(field.Names) != 0 { |
| name = field.Names[0].Name |
| } else { |
| name = "<delegate>" |
| } |
| if *verbose { |
| fmt.Printf("\t ignoring field %s %v\n", name, field.Type) |
| } |
| continue |
| } |
| isStringer := false |
| if field.Tag != nil && field.Tag.Value == "`stringer:\"true\"`" { // TODO: Check if we do that a bit smarter |
| isStringer = true |
| } |
| hasLocker := "" |
| if field.Tag != nil && strings.HasPrefix(field.Tag.Value, "`hasLocker:\"") { // TODO: Check if we do that a bit smarter |
| hasLocker = strings.TrimPrefix(field.Tag.Value, "`hasLocker:\"") |
| hasLocker = strings.TrimSuffix(hasLocker, "\"`") |
| } |
| asPtr := false |
| if field.Tag != nil && field.Tag.Value == "`asPtr:\"true\"`" { // TODO: Check if we do that a bit smarter |
| asPtr = true |
| } |
| directSerialize := false |
| if field.Tag != nil && field.Tag.Value == "`directSerialize:\"true\"`" { // TODO: Check if we do that a bit smarter |
| directSerialize = true |
| } |
| if len(field.Names) == 0 { |
| if *verbose { |
| fmt.Printf("\t adding delegate\n") |
| } |
| switch ft := field.Type.(type) { |
| case *ast.Ident: |
| f.fields = append(f.fields, Field{ |
| fieldType: ft, |
| isDelegate: true, |
| isStringer: isStringer, |
| asPtr: asPtr, |
| hasLocker: hasLocker, |
| }) |
| continue |
| case *ast.StarExpr: |
| switch set := ft.X.(type) { |
| case *ast.Ident: |
| f.fields = append(f.fields, Field{ |
| fieldType: set, |
| isDelegate: true, |
| isStringer: isStringer, |
| asPtr: asPtr, |
| hasLocker: hasLocker, |
| }) |
| continue |
| case *ast.SelectorExpr: |
| f.fields = append(f.fields, Field{ |
| fieldType: set.Sel, |
| isDelegate: true, |
| isStringer: isStringer, |
| asPtr: asPtr, |
| hasLocker: hasLocker, |
| }) |
| continue |
| default: |
| panic(fmt.Sprintf("Only pointer to struct delegates supported now. Type %T", field.Type)) |
| } |
| case *ast.SelectorExpr: |
| f.fields = append(f.fields, Field{ |
| fieldType: ft.Sel, |
| isDelegate: true, |
| isStringer: isStringer, |
| asPtr: asPtr, |
| hasLocker: hasLocker, |
| }) |
| continue |
| default: |
| panic(fmt.Sprintf("Only struct delegates supported now. Type %T", field.Type)) |
| } |
| } |
| if *verbose { |
| fmt.Printf("\t adding field %s %v\n", field.Names[0].Name, field.Type) |
| } |
| f.fields = append(f.fields, Field{ |
| name: field.Names[0].Name, |
| fieldType: field.Type, |
| isStringer: isStringer, |
| asPtr: asPtr, |
| directSerialize: directSerialize, |
| hasLocker: hasLocker, |
| }) |
| } |
| } |
| return false |
| } |
| |
| func indent(times int, input string) string { |
| if times == 0 { |
| return input |
| } |
| lines := strings.Split(input, "\n") |
| indents := strings.Repeat("\t", times) |
| return indents + strings.Join(lines, "\n"+indents) |
| } |
| |
| var stringerTemplate = ` |
| func (d *%s) String() string { |
| if alternateStringer, ok := any(d).(utils.AlternateStringer); ok { |
| if alternateString, use := alternateStringer.AlternateString(); use { |
| return alternateString |
| } |
| } |
| wb := utils.NewWriteBufferBoxBased(utils.WithWriteBufferBoxBasedMergeSingleBoxes(), utils.WithWriteBufferBoxBasedOmitEmptyBoxes()) |
| if err := wb.WriteSerializable(context.Background(), d); err != nil { |
| return err.Error() |
| } |
| return wb.GetBox().String() |
| } |
| ` |
| |
| var serializableDirectFieldTemplate = ` |
| if err := writeBuffer.PushContext(%[2]s); err != nil { |
| return err |
| } |
| if err := %[1]s.SerializeWithWriteBuffer(ctx, writeBuffer); err != nil { |
| return err |
| } |
| if err := writeBuffer.PopContext(%[2]s); err != nil { |
| return err |
| } |
| ` |
| |
| var serializableFieldTemplate = ` |
| if %[1]s != nil { |
| if serializableField, ok := any(%[1]s).(utils.Serializable); ok { |
| if err := writeBuffer.PushContext(%[2]s); err != nil { |
| return err |
| } |
| if err := serializableField.SerializeWithWriteBuffer(ctx, writeBuffer); err != nil { |
| return err |
| } |
| if err := writeBuffer.PopContext(%[2]s); err != nil { |
| return err |
| } |
| } else { |
| stringValue := fmt.Sprintf("%%v", %[1]s) |
| if err := writeBuffer.WriteString(%[2]s, uint32(len(stringValue)*8), stringValue); err != nil { |
| return err |
| } |
| } |
| } |
| ` |
| |
| var atomicPointerFieldTemplate = ` |
| if %[2]sLoaded :=%[1]s.Load(); %[2]sLoaded != nil && *%[2]sLoaded != nil { |
| %[2]s := *%[2]sLoaded |
| if serializableField, ok := %[2]s.(utils.Serializable); ok { |
| if err := writeBuffer.PushContext(%[3]s); err != nil { |
| return err |
| } |
| if err := serializableField.SerializeWithWriteBuffer(ctx, writeBuffer); err != nil { |
| return err |
| } |
| if err := writeBuffer.PopContext(%[3]s); err != nil { |
| return err |
| } |
| } else { |
| stringValue := fmt.Sprintf("%%v", %[2]s) |
| if err := writeBuffer.WriteString(%[3]s, uint32(len(stringValue)*8), stringValue); err != nil { |
| return err |
| } |
| } |
| } |
| ` |
| |
| var byteFieldSerialize = ` |
| if err := writeBuffer.WriteByte(%[2]s, %[1]s); err != nil { |
| return err |
| } |
| ` |
| |
| var int32FieldSerialize = ` |
| if err := writeBuffer.WriteInt32(%[2]s, 32, %[1]s); err != nil { |
| return err |
| } |
| ` |
| |
| var int64FieldSerialize = ` |
| if err := writeBuffer.WriteInt64(%[2]s, 64, %[1]s); err != nil { |
| return err |
| } |
| ` |
| |
| var uint16FieldSerialize = ` |
| if err := writeBuffer.WriteUint16(%[2]s, 16, %[1]s); err != nil { |
| return err |
| } |
| ` |
| |
| var uint32FieldSerialize = ` |
| if err := writeBuffer.WriteUint32(%[2]s, 32, %[1]s); err != nil { |
| return err |
| } |
| ` |
| |
| var uint64FieldSerialize = ` |
| if err := writeBuffer.WriteUint64(%[2]s, 64, %[1]s); err != nil { |
| return err |
| } |
| ` |
| |
| var boolFieldSerialize = ` |
| if err := writeBuffer.WriteBit(%[2]s, %[1]s); err != nil { |
| return err |
| } |
| ` |
| |
| var stringFieldSerialize = ` |
| if err := writeBuffer.WriteString(%[2]s, uint32(len(%[1]s)*8), %[1]s); err != nil { |
| return err |
| } |
| ` |
| |
| var errorFieldSerialize = ` |
| if %[1]s != nil { |
| _errString := %[1]s.Error() |
| if err := writeBuffer.WriteString(%[2]s, uint32(len(_errString)*8), _errString); err != nil { |
| return err |
| } |
| } |
| ` |
| |
| var chanFieldSerialize = ` |
| _%[3]s_plx4gen_description := fmt.Sprintf("%%d element(s)", len(%[1]s)) |
| if err := writeBuffer.WriteString(%[2]s, uint32(len(_%[3]s_plx4gen_description)*8), _%[3]s_plx4gen_description); err != nil { |
| return err |
| } |
| ` |
| |
| var funcFieldSerialize = ` |
| if err := writeBuffer.WriteBit(%[2]s, %[1]s != nil); err != nil { |
| return err |
| } |
| ` |
| |
| func unTitle(value string) string { |
| return strings.ToLower(string(value[0])) + value[1:] |
| } |