// Go support for Protocol Buffers - Google's data interchange format // // Copyright 2010 Google Inc. All rights reserved. // http://code.google.com/p/goprotobuf/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /* The code generator for the plugin for the Google protocol buffer compiler. It generates Go code from the protocol buffer description files read by the main routine. */ package generator import ( "bytes" "fmt" "go/parser" "go/printer" "go/token" "log" "os" "path" "strconv" "strings" "goprotobuf.googlecode.com/hg/proto" plugin "goprotobuf.googlecode.com/hg/compiler/plugin" descriptor "goprotobuf.googlecode.com/hg/compiler/descriptor" ) // A Plugin provides functionality to add to the output during Go code generation, // such as to produce RPC stubs. type Plugin interface { // Name identifies the plugin. Name() string // Init is called once after data structures are built but before // code generation begins. Init(g *Generator) // Generate produces the code generated by the plugin for this file, // except for the imports, by calling the generator's methods P, In, and Out. Generate(file *FileDescriptor) // GenerateImports produces the import declarations for this file. // It is called after Generate. GenerateImports(file *FileDescriptor) } var plugins []Plugin // RegisterPlugin installs a (second-order) plugin to be run when the Go output is generated. // It is typically called during initialization. func RegisterPlugin(p Plugin) { plugins = append(plugins, p) } // Each type we import as a protocol buffer (other than FileDescriptorProto) needs // a pointer to the FileDescriptorProto that represents it. These types achieve that // wrapping by placing each Proto inside a struct with the pointer to its File. The // structs have the same names as their contents, with "Proto" removed. // FileDescriptor is used to store the things that it points to. // The file and package name method are common to messages and enums. type common struct { file *descriptor.FileDescriptorProto // File this object comes from. } // PackageName is name in the package clause in the generated file. func (c *common) PackageName() string { return uniquePackageOf(c.file) } func (c *common) File() *descriptor.FileDescriptorProto { return c.file } // Descriptor represents a protocol buffer message. type Descriptor struct { common *descriptor.DescriptorProto parent *Descriptor // The containing message, if any. nested []*Descriptor // Inner messages, if any. ext []*ExtensionDescriptor // Extensions, if any. typename []string // Cached typename vector. } // TypeName returns the elements of the dotted type name. // The package name is not part of this name. func (d *Descriptor) TypeName() []string { if d.typename != nil { return d.typename } n := 0 for parent := d; parent != nil; parent = parent.parent { n++ } s := make([]string, n, n) for parent := d; parent != nil; parent = parent.parent { n-- s[n] = proto.GetString(parent.Name) } d.typename = s return s } // EnumDescriptor describes an enum. If it's at top level, its parent will be nil. // Otherwise it will be the descriptor of the message in which it is defined. type EnumDescriptor struct { common *descriptor.EnumDescriptorProto parent *Descriptor // The containing message, if any. typename []string // Cached typename vector. } // TypeName returns the elements of the dotted type name. // The package name is not part of this name. func (e *EnumDescriptor) TypeName() (s []string) { if e.typename != nil { return e.typename } name := proto.GetString(e.Name) if e.parent == nil { s = make([]string, 1) } else { pname := e.parent.TypeName() s = make([]string, len(pname)+1) copy(s, pname) } s[len(s)-1] = name e.typename = s return s } // Everything but the last element of the full type name, CamelCased. // The values of type Foo.Bar are call Foo_value1... not Foo_Bar_value1... . func (e *EnumDescriptor) prefix() string { typeName := e.TypeName() ccPrefix := CamelCaseSlice(typeName[0:len(typeName)-1]) + "_" if e.parent == nil { // If the enum is not part of a message, the prefix is just the type name. ccPrefix = CamelCase(*e.Name) + "_" } return ccPrefix } // The integer value of the named constant in this enumerated type. func (e *EnumDescriptor) integerValueAsString(name string) string { for _, c := range e.Value { if proto.GetString(c.Name) == name { return fmt.Sprint(proto.GetInt32(c.Number)) } } log.Fatal("cannot find value for enum constant") return "" } // ExtensionDescriptor describes an extension. If it's at top level, its parent will be nil. // Otherwise it will be the descriptor of the message in which it is defined. type ExtensionDescriptor struct { common *descriptor.FieldDescriptorProto parent *Descriptor // The containing message, if any. } // TypeName returns the elements of the dotted type name. // The package name is not part of this name. func (e *ExtensionDescriptor) TypeName() (s []string) { name := proto.GetString(e.Name) if e.parent == nil { // top-level extension s = make([]string, 1) } else { pname := e.parent.TypeName() s = make([]string, len(pname)+1) copy(s, pname) } s[len(s)-1] = name return s } // DescName returns the variable name used for the generated descriptor. func (e *ExtensionDescriptor) DescName() string { // The full type name. typeName := e.TypeName() // Each scope of the extension is individually CamelCased, and all are joined with "_" with an "E_" prefix. for i, s := range typeName { typeName[i] = CamelCase(s) } return "E_" + strings.Join(typeName, "_") } // ImportedDescriptor describes a type that has been publicly imported from another file. type ImportedDescriptor struct { common o Object } func (id *ImportedDescriptor) TypeName() []string { return id.o.TypeName() } // FileDescriptor describes an protocol buffer descriptor file (.proto). // It includes slices of all the messages and enums defined within it. // Those slices are constructed by WrapTypes. type FileDescriptor struct { *descriptor.FileDescriptorProto desc []*Descriptor // All the messages defined in this file. enum []*EnumDescriptor // All the enums defined in this file. ext []*ExtensionDescriptor // All the top-level extensions defined in this file. imp []*ImportedDescriptor // All types defined in files publicly imported by this file. // The full list of symbols that are exported, // as a map from the exported object to its symbols. // This is used for supporting public imports. exported map[Object][]Symbol } // PackageName is the package name we'll use in the generated code to refer to this file. func (d *FileDescriptor) PackageName() string { return uniquePackageOf(d.FileDescriptorProto) } // The package named defined in the input for this file, possibly dotted. // If the file does not define a package, use the base of the file name. func (d *FileDescriptor) originalPackageName() string { // Does the file have a package clause? if pkg := proto.GetString(d.Package); pkg != "" { return pkg } // Use the file base name. return BaseName(proto.GetString(d.Name)) } func (d *FileDescriptor) addExport(obj Object, symbol Symbol) { d.exported[obj] = append(d.exported[obj], symbol) } // Symbol is an interface representing an exported Go symbol. type Symbol interface { // GenerateAlias should generate an appropriate alias // for the symbol from the named package. GenerateAlias(g *Generator, pkg string) } type messageSymbol struct { sym string hasExtensions, isMessageSet bool } func (ms messageSymbol) GenerateAlias(g *Generator, pkg string) { remoteSym := pkg + "." + ms.sym g.P("type ", ms.sym, " ", remoteSym) g.P("func (this *", ms.sym, ") Reset() { (*", remoteSym, ")(this).Reset() }") g.P("func (this *", ms.sym, ") String() string { return (*", remoteSym, ")(this).String() }") if ms.hasExtensions { g.P("func (*", ms.sym, ") ExtensionRangeArray() []", g.ProtoPkg, ".ExtensionRange ", "{ return (*", remoteSym, ")(nil).ExtensionRangeArray() }") g.P("func (this *", ms.sym, ") ExtensionMap() map[int32]", g.ProtoPkg, ".Extension ", "{ return (*", remoteSym, ")(this).ExtensionMap() }") if ms.isMessageSet { g.P("func (this *", ms.sym, ") Marshal() ([]byte, os.Error) ", "{ return (*", remoteSym, ")(this).Marshal() }") g.P("func (this *", ms.sym, ") Unmarshal(buf []byte) os.Error ", "{ return (*", remoteSym, ")(this).Unmarshal(buf) }") } } } type enumSymbol string func (es enumSymbol) GenerateAlias(g *Generator, pkg string) { s := string(es) g.P("type ", s, " ", pkg, ".", s) g.P("var ", s, "_name = ", pkg, ".", s, "_name") g.P("var ", s, "_value = ", pkg, ".", s, "_value") g.P("func New", s, "(x ", s, ") *", s, " { e := ", s, "(x); return &e }") } type constOrVarSymbol struct { sym string typ string // either "const" or "var" } func (cs constOrVarSymbol) GenerateAlias(g *Generator, pkg string) { g.P(cs.typ, " ", cs.sym, " = ", pkg, ".", cs.sym) } // Object is an interface abstracting the abilities shared by enums, messages, extensions and imported objects. type Object interface { PackageName() string // The name we use in our output (a_b_c), possibly renamed for uniqueness. TypeName() []string File() *descriptor.FileDescriptorProto } // Each package name we generate must be unique. The package we're generating // gets its own name but every other package must have a unique name that does // not conflict in the code we generate. These names are chosen globally (although // they don't have to be, it simplifies things to do them globally). func uniquePackageOf(fd *descriptor.FileDescriptorProto) string { s, ok := uniquePackageName[fd] if !ok { log.Fatal("internal error: no package name defined for", proto.GetString(fd.Name)) } return s } // Generator is the type whose methods generate the output, stored in the associated response structure. type Generator struct { *bytes.Buffer Request *plugin.CodeGeneratorRequest // The input. Response *plugin.CodeGeneratorResponse // The output. Param map[string]string // Command-line parameters. ImportPrefix string // String to prefix to imported package file names. ImportMap map[string]string // Mapping from import name to generated name ProtoPkg string // The name under which we import the library's package proto. packageName string // What we're calling ourselves. allFiles []*FileDescriptor // All files in the tree genFiles []*FileDescriptor // Those files we will generate output for. file *FileDescriptor // The file we are compiling now. usedPackages map[string]bool // Names of packages used in current file. typeNameToObject map[string]Object // Key is a fully-qualified name in input syntax. indent string } // New creates a new generator and allocates the request and response protobufs. func New() *Generator { g := new(Generator) g.Buffer = new(bytes.Buffer) g.Request = new(plugin.CodeGeneratorRequest) g.Response = new(plugin.CodeGeneratorResponse) return g } // Error reports a problem, including an os.Error, and exits the program. func (g *Generator) Error(err os.Error, msgs ...string) { s := strings.Join(msgs, " ") + ":" + err.String() log.Println("protoc-gen-go: error:", s) g.Response.Error = proto.String(s) os.Exit(1) } // Fail reports a problem and exits the program. func (g *Generator) Fail(msgs ...string) { s := strings.Join(msgs, " ") log.Println("protoc-gen-go: error:", s) g.Response.Error = proto.String(s) os.Exit(1) } // CommandLineParameters breaks the comma-separated list of key=value pairs // in the parameter (a member of the request protobuf) into a key/value map. // It then sets file name mappings defined by those entries. func (g *Generator) CommandLineParameters(parameter string) { g.Param = make(map[string]string) for _, p := range strings.Split(parameter, ",") { if i := strings.Index(p, "="); i < 0 { g.Param[p] = "" } else { g.Param[p[0:i]] = p[i+1:] } } g.ImportMap = make(map[string]string) for k, v := range g.Param { if k == "import_prefix" { g.ImportPrefix = v } else if len(k) > 0 && k[0] == 'M' { g.ImportMap[k[1:]] = v } } } // DefaultPackageName returns the package name printed for the object. // If its file is in a different package, it returns the package name we're using for this file, plus ".". // Otherwise it returns the empty string. func (g *Generator) DefaultPackageName(obj Object) string { pkg := obj.PackageName() if pkg == g.packageName { return "" } return pkg + "." } // For each input file, the unique package name to use, underscored. var uniquePackageName = make(map[*descriptor.FileDescriptorProto]string) // Package names already registered. Key is the name from the .proto file; // value is the name that appears in the generated code. var pkgNamesInUse = make(map[string]bool) // Create and remember a guaranteed unique package name for this file descriptor. // Pkg is the candidate name. If f is nil, it's a builtin package like "proto" and // has no file descriptor. func RegisterUniquePackageName(pkg string, f *FileDescriptor) string { // Convert dots to underscores before finding a unique alias. pkg = strings.Map(DotToUnderscore, pkg) for i, orig := 1, pkg; pkgNamesInUse[pkg]; i++ { // It's a duplicate; must rename. pkg = orig + strconv.Itoa(i) } // Install it. pkgNamesInUse[pkg] = true if f != nil { uniquePackageName[f.FileDescriptorProto] = pkg } return pkg } // SetPackageNames sets the package name for this run. // The package name must agree across all files being generated. // It also defines unique package names for all imported files. func (g *Generator) SetPackageNames() { // Register the name for this package. It will be the first name // registered so is guaranteed to be unmodified. pkg := g.genFiles[0].originalPackageName() g.packageName = RegisterUniquePackageName(pkg, g.genFiles[0]) // Register the proto package name. It might collide with the // name of a package we import. g.ProtoPkg = RegisterUniquePackageName("proto", nil) // Verify that we are generating output for a single package. for _, f := range g.genFiles { thisPkg := f.originalPackageName() if thisPkg != pkg { g.Fail("inconsistent package names:", thisPkg, pkg) } } AllFiles: for _, f := range g.allFiles { for _, genf := range g.genFiles { if f == genf { // In this package already. uniquePackageName[f.FileDescriptorProto] = g.packageName continue AllFiles } } // The file is a dependency, so we want to ignore its go_package option // because that is only relevant for its specific generated output. pkg := proto.GetString(f.Package) if pkg == "" { pkg = BaseName(*f.Name) } RegisterUniquePackageName(pkg, f) } } // WrapTypes walks the incoming data, wrapping DescriptorProtos, EnumDescriptorProtos // and FileDescriptorProtos into file-referenced objects within the Generator. // It also creates the list of files to generate and so should be called before GenerateAllFiles. func (g *Generator) WrapTypes() { g.allFiles = make([]*FileDescriptor, len(g.Request.ProtoFile)) for i, f := range g.Request.ProtoFile { // We must wrap the descriptors before we wrap the enums descs := wrapDescriptors(f) g.buildNestedDescriptors(descs) enums := wrapEnumDescriptors(f, descs) exts := wrapExtensions(f) imps := wrapImported(f, g) g.allFiles[i] = &FileDescriptor{ FileDescriptorProto: f, desc: descs, enum: enums, ext: exts, imp: imps, exported: make(map[Object][]Symbol), } } g.genFiles = make([]*FileDescriptor, len(g.Request.FileToGenerate)) FindFiles: for i, fileName := range g.Request.FileToGenerate { // Search the list. This algorithm is n^2 but n is tiny. for _, file := range g.allFiles { if fileName == proto.GetString(file.Name) { g.genFiles[i] = file continue FindFiles } } g.Fail("could not find file named", fileName) } g.Response.File = make([]*plugin.CodeGeneratorResponse_File, len(g.genFiles)) } // Scan the descriptors in this file. For each one, build the slice of nested descriptors func (g *Generator) buildNestedDescriptors(descs []*Descriptor) { for _, desc := range descs { if len(desc.NestedType) != 0 { desc.nested = make([]*Descriptor, len(desc.NestedType)) n := 0 for _, nest := range descs { if nest.parent == desc { desc.nested[n] = nest n++ } } if n != len(desc.NestedType) { g.Fail("internal error: nesting failure for", proto.GetString(desc.Name)) } } } } // Construct the Descriptor and add it to the slice func addDescriptor(sl []*Descriptor, desc *descriptor.DescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto) []*Descriptor { d := &Descriptor{common{file}, desc, parent, nil, nil, nil} d.ext = make([]*ExtensionDescriptor, len(desc.Extension)) for i, field := range desc.Extension { d.ext[i] = &ExtensionDescriptor{common{file}, field, d} } return append(sl, d) } // Return a slice of all the Descriptors defined within this file func wrapDescriptors(file *descriptor.FileDescriptorProto) []*Descriptor { sl := make([]*Descriptor, 0, len(file.MessageType)+10) for _, desc := range file.MessageType { sl = wrapThisDescriptor(sl, desc, nil, file) } return sl } // Wrap this Descriptor, recursively func wrapThisDescriptor(sl []*Descriptor, desc *descriptor.DescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto) []*Descriptor { sl = addDescriptor(sl, desc, parent, file) me := sl[len(sl)-1] for _, nested := range desc.NestedType { sl = wrapThisDescriptor(sl, nested, me, file) } return sl } // Construct the EnumDescriptor and add it to the slice func addEnumDescriptor(sl []*EnumDescriptor, desc *descriptor.EnumDescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto) []*EnumDescriptor { return append(sl, &EnumDescriptor{common{file}, desc, parent, nil}) } // Return a slice of all the EnumDescriptors defined within this file func wrapEnumDescriptors(file *descriptor.FileDescriptorProto, descs []*Descriptor) []*EnumDescriptor { sl := make([]*EnumDescriptor, 0, len(file.EnumType)+10) // Top-level enums. for _, enum := range file.EnumType { sl = addEnumDescriptor(sl, enum, nil, file) } // Enums within messages. Enums within embedded messages appear in the outer-most message. for _, nested := range descs { for _, enum := range nested.EnumType { sl = addEnumDescriptor(sl, enum, nested, file) } } return sl } // Return a slice of all the top-level ExtensionDescriptors defined within this file. func wrapExtensions(file *descriptor.FileDescriptorProto) []*ExtensionDescriptor { sl := make([]*ExtensionDescriptor, len(file.Extension)) for i, field := range file.Extension { sl[i] = &ExtensionDescriptor{common{file}, field, nil} } return sl } // Return a slice of all the types that are publicly imported into this file. func wrapImported(file *descriptor.FileDescriptorProto, g *Generator) (sl []*ImportedDescriptor) { for _, index := range file.PublicDependency { df := g.fileByName(file.Dependency[index]) for _, d := range df.desc { sl = append(sl, &ImportedDescriptor{common{file}, d}) } for _, e := range df.enum { sl = append(sl, &ImportedDescriptor{common{file}, e}) } for _, ext := range df.ext { sl = append(sl, &ImportedDescriptor{common{file}, ext}) } } return } // BuildTypeNameMap builds the map from fully qualified type names to objects. // The key names for the map come from the input data, which puts a period at the beginning. // It should be called after SetPackageNames and before GenerateAllFiles. func (g *Generator) BuildTypeNameMap() { g.typeNameToObject = make(map[string]Object) for _, f := range g.allFiles { // The names in this loop are defined by the proto world, not us, so the // package name may be empty. If so, the dotted package name of X will // be ".X"; otherwise it will be ".pkg.X". dottedPkg := "." + proto.GetString(f.Package) if dottedPkg != "." { dottedPkg += "." } for _, enum := range f.enum { name := dottedPkg + dottedSlice(enum.TypeName()) g.typeNameToObject[name] = enum } for _, desc := range f.desc { name := dottedPkg + dottedSlice(desc.TypeName()) g.typeNameToObject[name] = desc } } } // ObjectNamed, given a fully-qualified input type name as it appears in the input data, // returns the descriptor for the message or enum with that name. func (g *Generator) ObjectNamed(typeName string) Object { o, ok := g.typeNameToObject[typeName] if !ok { g.Fail("can't find object with type", typeName) } // If the file of this object isn't a direct dependency of the current file, // or in the current file, then this object has been publicly imported into // a dependency of the current file. // We should return the ImportedDescriptor object for it instead. direct := *o.File().Name == *g.file.Name if !direct { for _, dep := range g.file.Dependency { if *g.fileByName(dep).Name == *o.File().Name { direct = true break } } } if !direct { found := false Loop: for _, dep := range g.file.Dependency { df := g.fileByName(*g.fileByName(dep).Name) for _, td := range df.imp { if td.o == o { // Found it! o = td found = true break Loop } } } if !found { log.Printf("protoc-gen-go: WARNING: failed finding publicly imported dependency for %v, used in %v", typeName, *g.file.Name) } } return o } // P prints the arguments to the generated output. It handles strings and int32s, plus // handling indirections because they may be *string, etc. func (g *Generator) P(str ...interface{}) { g.WriteString(g.indent) for _, v := range str { switch s := v.(type) { case string: g.WriteString(s) case *string: g.WriteString(*s) case bool: g.WriteString(fmt.Sprintf("%t", s)) case *bool: g.WriteString(fmt.Sprintf("%t", *s)) case *int32: g.WriteString(fmt.Sprintf("%d", *s)) case float64: g.WriteString(fmt.Sprintf("%g", s)) case *float64: g.WriteString(fmt.Sprintf("%g", *s)) default: g.Fail(fmt.Sprintf("unknown type in printer: %T", v)) } } g.WriteByte('\n') } // In Indents the output one tab stop. func (g *Generator) In() { g.indent += "\t" } // Out unindents the output one tab stop. func (g *Generator) Out() { if len(g.indent) > 0 { g.indent = g.indent[1:] } } // GenerateAllFiles generates the output for all the files we're outputting. func (g *Generator) GenerateAllFiles() { // Initialize the plugins for _, p := range plugins { p.Init(g) } // Generate the output. The generator runs for every file, even the files // that we don't generate output for, so that we can collate the full list // of exported symbols to support public imports. genFileMap := make(map[*FileDescriptor]bool, len(g.genFiles)) for _, file := range g.genFiles { genFileMap[file] = true } i := 0 for _, file := range g.allFiles { g.Reset() g.generate(file) if _, ok := genFileMap[file]; !ok { continue } g.Response.File[i] = new(plugin.CodeGeneratorResponse_File) g.Response.File[i].Name = proto.String(goFileName(*file.Name)) g.Response.File[i].Content = proto.String(g.String()) i++ } } // Run all the plugins associated with the file. func (g *Generator) runPlugins(file *FileDescriptor) { for _, p := range plugins { p.Generate(file) } } // FileOf return the FileDescriptor for this FileDescriptorProto. func (g *Generator) FileOf(fd *descriptor.FileDescriptorProto) *FileDescriptor { for _, file := range g.allFiles { if file.FileDescriptorProto == fd { return file } } g.Fail("could not find file in table:", proto.GetString(fd.Name)) return nil } // Fill the response protocol buffer with the generated output for all the files we're // supposed to generate. func (g *Generator) generate(file *FileDescriptor) { g.file = g.FileOf(file.FileDescriptorProto) g.usedPackages = make(map[string]bool) for _, td := range g.file.imp { g.generateImported(td) } for _, enum := range g.file.enum { g.generateEnum(enum) } for _, desc := range g.file.desc { g.generateMessage(desc) } for _, ext := range g.file.ext { g.generateExtension(ext) } g.generateInitFunction() // Run the plugins before the imports so we know which imports are necessary. g.runPlugins(file) // Generate header and imports last, though they appear first in the output. rem := g.Buffer g.Buffer = new(bytes.Buffer) g.generateHeader() g.generateImports() g.Write(rem.Bytes()) // Reformat generated code. fset := token.NewFileSet() ast, err := parser.ParseFile(fset, "", g, parser.ParseComments) if err != nil { g.Fail("bad Go source code was generated:", err.String()) return } g.Reset() _, err = (&printer.Config{printer.TabIndent | printer.UseSpaces, 8}).Fprint(g, fset, ast) if err != nil { g.Fail("generated Go source code could not be reformatted:", err.String()) } } // Generate the header, including package definition and imports func (g *Generator) generateHeader() { g.P("// Code generated by protoc-gen-go from ", Quote(*g.file.Name)) g.P("// DO NOT EDIT!") g.P() g.P("package ", g.file.PackageName()) g.P() } func (g *Generator) fileByName(filename string) *FileDescriptor { for _, fd := range g.allFiles { if proto.GetString(fd.Name) == filename { return fd } } return nil } // Generate the header, including package definition and imports func (g *Generator) generateImports() { // We almost always need a proto import. Rather than computing when we // do, which is tricky when there's a plugin, just import it and // reference it later. The same argument applies to the os package. g.P("import " + g.ProtoPkg + " " + Quote(g.ImportPrefix+"goprotobuf.googlecode.com/hg/proto")) g.P(`import "math"`) g.P(`import "os"`) for _, s := range g.file.Dependency { fd := g.fileByName(s) // Do not import our own package. if fd.PackageName() == g.packageName { continue } filename := goFileName(s) if substitution, ok := g.ImportMap[s]; ok { filename = substitution } filename = g.ImportPrefix + filename if strings.HasSuffix(filename, ".go") { filename = filename[0 : len(filename)-3] } if _, ok := g.usedPackages[fd.PackageName()]; ok { g.P("import ", fd.PackageName(), " ", Quote(filename)) } else { // TODO: Re-enable this when we are more feature-complete. // For instance, some protos use foreign field extensions, which we don't support. // Until then, this is just annoying spam. //log.Printf("protoc-gen-go: discarding unused import from %v: %v", *g.file.Name, s) g.P("// discarding unused import ", fd.PackageName(), " ", Quote(filename)) } } g.P() // TODO: may need to worry about uniqueness across plugins for _, p := range plugins { p.GenerateImports(g.file) g.P() } g.P("// Reference proto, math & os imports to suppress error if they are not otherwise used.") g.P("var _ = ", g.ProtoPkg, ".GetString") g.P("var _ = math.Inf") g.P("var _ os.Error") g.P() } func (g *Generator) generateImported(id *ImportedDescriptor) { // Don't generate public import symbols for files that we are generating // code for, since those symbols will already be in this package. // We can't simply avoid creating the ImportedDescriptor objects, // because g.genFiles isn't populated at that stage. tn := id.TypeName() sn := tn[len(tn)-1] df := g.FileOf(id.o.File()) filename := *df.Name for _, fd := range g.genFiles { if *fd.Name == filename { g.P("// Ignoring public import of ", sn, " from ", filename) g.P() return } } g.P("// ", sn, " from public import ", filename) g.usedPackages[df.PackageName()] = true for _, sym := range df.exported[id.o] { sym.GenerateAlias(g, df.PackageName()) } g.P() } // Generate the enum definitions for this EnumDescriptor. func (g *Generator) generateEnum(enum *EnumDescriptor) { // The full type name typeName := enum.TypeName() // The full type name, CamelCased. ccTypeName := CamelCaseSlice(typeName) ccPrefix := enum.prefix() g.P("type ", ccTypeName, " int32") g.file.addExport(enum, enumSymbol(ccTypeName)) g.P("const (") g.In() for _, e := range enum.Value { name := ccPrefix + *e.Name g.P(name, " ", ccTypeName, " = ", e.Number) g.file.addExport(enum, constOrVarSymbol{name, "const"}) } g.Out() g.P(")") g.P("var ", ccTypeName, "_name = map[int32]string{") g.In() generated := make(map[int32]bool) // avoid duplicate values for _, e := range enum.Value { duplicate := "" if _, present := generated[*e.Number]; present { duplicate = "// Duplicate value: " } g.P(duplicate, e.Number, ": ", Quote(*e.Name), ",") generated[*e.Number] = true } g.Out() g.P("}") g.P("var ", ccTypeName, "_value = map[string]int32{") g.In() for _, e := range enum.Value { g.P(Quote(*e.Name), ": ", e.Number, ",") } g.Out() g.P("}") g.P("func New", ccTypeName, "(x ", ccTypeName, ") *", ccTypeName, " {") g.In() g.P("e := ", ccTypeName, "(x)") g.P("return &e") g.Out() g.P("}") g.P("func (x ", ccTypeName, ") String() string {") g.In() g.P("return ", g.ProtoPkg, ".EnumName(", ccTypeName, "_name, int32(x))") g.Out() g.P("}") g.P() } // The tag is a string like "varint,2,opt,name=fieldname,def=7" that // identifies details of the field for the protocol buffer marshaling and unmarshaling // code. The fields are: // wire encoding // protocol tag number // opt,req,rep for optional, required, or repeated // packed whether the encoding is "packed" (optional; repeated primitives only) // name= the original declared name // enum= the name of the enum type if it is an enum-typed field. // def= string representation of the default value, if any. // The default value must be in a representation that can be used at run-time // to generate the default value. Thus bools become 0 and 1, for instance. func (g *Generator) goTag(field *descriptor.FieldDescriptorProto, wiretype string) string { optrepreq := "" switch { case isOptional(field): optrepreq = "opt" case isRequired(field): optrepreq = "req" case isRepeated(field): optrepreq = "rep" } defaultValue := proto.GetString(field.DefaultValue) if defaultValue != "" { switch *field.Type { case descriptor.FieldDescriptorProto_TYPE_BOOL: if defaultValue == "true" { defaultValue = "1" } else { defaultValue = "0" } case descriptor.FieldDescriptorProto_TYPE_STRING, descriptor.FieldDescriptorProto_TYPE_BYTES: // Nothing to do. Quoting is done for the whole tag. case descriptor.FieldDescriptorProto_TYPE_ENUM: // For enums we need to provide the integer constant. obj := g.ObjectNamed(proto.GetString(field.TypeName)) enum, ok := obj.(*EnumDescriptor) if !ok { g.Fail("enum type inconsistent for", CamelCaseSlice(obj.TypeName())) } defaultValue = enum.integerValueAsString(defaultValue) } defaultValue = ",def=" + defaultValue } enum := "" if *field.Type == descriptor.FieldDescriptorProto_TYPE_ENUM { // We avoid using obj.PackageName(), because we want to use the // original (proto-world) package name. obj := g.ObjectNamed(proto.GetString(field.TypeName)) enum = ",enum=" if pkg := proto.GetString(obj.File().Package); pkg != "" { enum += pkg + "." } enum += CamelCaseSlice(obj.TypeName()) } packed := "" if field.Options != nil && proto.GetBool(field.Options.Packed) { packed = ",packed" } fieldName := proto.GetString(field.Name) name := fieldName if *field.Type == descriptor.FieldDescriptorProto_TYPE_GROUP { // We must use the type name for groups instead of // the field name to preserve capitalization. // type_name in FieldDescriptorProto is fully-qualified, // but we only want the local part. name = *field.TypeName if i := strings.LastIndex(name, "."); i >= 0 { name = name[i+1:] } } if name == CamelCase(fieldName) { name = "" } else { name = ",name=" + name } return Quote(fmt.Sprintf("%s,%d,%s%s%s%s%s", wiretype, proto.GetInt32(field.Number), optrepreq, packed, name, enum, defaultValue)) } func needsStar(typ descriptor.FieldDescriptorProto_Type) bool { switch typ { case descriptor.FieldDescriptorProto_TYPE_GROUP: return false case descriptor.FieldDescriptorProto_TYPE_MESSAGE: return false case descriptor.FieldDescriptorProto_TYPE_BYTES: return false } return true } // TypeName is the printed name appropriate for an item. If the object is in the current file, // TypeName drops the package name and underscores the rest. // Otherwise the object is from another package; and the result is the underscored // package name followed by the item name. // The result always has an initial capital. func (g *Generator) TypeName(obj Object) string { return g.DefaultPackageName(obj) + CamelCaseSlice(obj.TypeName()) } // TypeNameWithPackage is like TypeName, but always includes the package // name even if the object is in our own package. func (g *Generator) TypeNameWithPackage(obj Object) string { return obj.PackageName() + CamelCaseSlice(obj.TypeName()) } // GoType returns a string representing the type name, and the wire type func (g *Generator) GoType(message *Descriptor, field *descriptor.FieldDescriptorProto) (typ string, wire string) { // TODO: Options. switch *field.Type { case descriptor.FieldDescriptorProto_TYPE_DOUBLE: typ, wire = "float64", "fixed64" case descriptor.FieldDescriptorProto_TYPE_FLOAT: typ, wire = "float32", "fixed32" case descriptor.FieldDescriptorProto_TYPE_INT64: typ, wire = "int64", "varint" case descriptor.FieldDescriptorProto_TYPE_UINT64: typ, wire = "uint64", "varint" case descriptor.FieldDescriptorProto_TYPE_INT32: typ, wire = "int32", "varint" case descriptor.FieldDescriptorProto_TYPE_UINT32: typ, wire = "uint32", "varint" case descriptor.FieldDescriptorProto_TYPE_FIXED64: typ, wire = "uint64", "fixed64" case descriptor.FieldDescriptorProto_TYPE_FIXED32: typ, wire = "uint32", "fixed32" case descriptor.FieldDescriptorProto_TYPE_BOOL: typ, wire = "bool", "varint" case descriptor.FieldDescriptorProto_TYPE_STRING: typ, wire = "string", "bytes" case descriptor.FieldDescriptorProto_TYPE_GROUP: desc := g.ObjectNamed(proto.GetString(field.TypeName)) typ, wire = "*"+g.TypeName(desc), "group" case descriptor.FieldDescriptorProto_TYPE_MESSAGE: desc := g.ObjectNamed(proto.GetString(field.TypeName)) typ, wire = "*"+g.TypeName(desc), "bytes" case descriptor.FieldDescriptorProto_TYPE_BYTES: typ, wire = "[]byte", "bytes" case descriptor.FieldDescriptorProto_TYPE_ENUM: desc := g.ObjectNamed(proto.GetString(field.TypeName)) typ, wire = g.TypeName(desc), "varint" case descriptor.FieldDescriptorProto_TYPE_SFIXED32: typ, wire = "int32", "fixed32" case descriptor.FieldDescriptorProto_TYPE_SFIXED64: typ, wire = "int64", "fixed64" case descriptor.FieldDescriptorProto_TYPE_SINT32: typ, wire = "int32", "zigzag32" case descriptor.FieldDescriptorProto_TYPE_SINT64: typ, wire = "int64", "zigzag64" default: g.Fail("unknown type for", proto.GetString(field.Name)) } if isRepeated(field) { typ = "[]" + typ } else if needsStar(*field.Type) { typ = "*" + typ } return } func (g *Generator) RecordTypeUse(t string) { if obj, ok := g.typeNameToObject[t]; ok { // Call ObjectNamed to get the true object to record the use. obj = g.ObjectNamed(t) g.usedPackages[obj.PackageName()] = true } } // Generate the type and default constant definitions for this Descriptor. func (g *Generator) generateMessage(message *Descriptor) { // The full type name typeName := message.TypeName() // The full type name, CamelCased. ccTypeName := CamelCaseSlice(typeName) g.P("type ", ccTypeName, " struct {") g.In() for _, field := range message.Field { fieldname := CamelCase(*field.Name) typename, wiretype := g.GoType(message, field) jsonName := *field.Name tag := fmt.Sprintf("`protobuf:%s json:%q`", g.goTag(field, wiretype), jsonName+",omitempty") g.P(fieldname, "\t", typename, "\t", tag) g.RecordTypeUse(proto.GetString(field.TypeName)) } // TODO: Use `json:"-"` for these XXX_ fields when that makes it into a Go release. if len(message.ExtensionRange) > 0 { g.P("XXX_extensions\t\tmap[int32]", g.ProtoPkg, ".Extension `json:\",omitempty\"`") } g.P("XXX_unrecognized\t[]byte `json:\",omitempty\"`") g.Out() g.P("}") // Reset and String functions g.P("func (this *", ccTypeName, ") Reset() { *this = ", ccTypeName, "{} }") g.P("func (this *", ccTypeName, ") String() string { return ", g.ProtoPkg, ".CompactTextString(this) }") // Extension support methods var hasExtensions, isMessageSet bool if len(message.ExtensionRange) > 0 { hasExtensions = true // message_set_wire_format only makes sense when extensions are defined. if opts := message.Options; opts != nil && proto.GetBool(opts.MessageSetWireFormat) { isMessageSet = true g.P() g.P("func (this *", ccTypeName, ") Marshal() ([]byte, os.Error) {") g.In() g.P("return ", g.ProtoPkg, ".MarshalMessageSet(this.ExtensionMap())") g.Out() g.P("}") g.P("func (this *", ccTypeName, ") Unmarshal(buf []byte) os.Error {") g.In() g.P("return ", g.ProtoPkg, ".UnmarshalMessageSet(buf, this.ExtensionMap())") g.Out() g.P("}") g.P("// ensure ", ccTypeName, " satisfies proto.Marshaler and proto.Unmarshaler") g.P("var _ ", g.ProtoPkg, ".Marshaler = (*", ccTypeName, ")(nil)") g.P("var _ ", g.ProtoPkg, ".Unmarshaler = (*", ccTypeName, ")(nil)") } g.P() g.P("var extRange_", ccTypeName, " = []", g.ProtoPkg, ".ExtensionRange{") g.In() for _, r := range message.ExtensionRange { end := fmt.Sprint(*r.End - 1) // make range inclusive on both ends g.P("{", r.Start, ", ", end, "},") } g.Out() g.P("}") g.P("func (*", ccTypeName, ") ExtensionRangeArray() []", g.ProtoPkg, ".ExtensionRange {") g.In() g.P("return extRange_", ccTypeName) g.Out() g.P("}") g.P("func (this *", ccTypeName, ") ExtensionMap() map[int32]", g.ProtoPkg, ".Extension {") g.In() g.P("if this.XXX_extensions == nil {") g.In() g.P("this.XXX_extensions = make(map[int32]", g.ProtoPkg, ".Extension)") g.Out() g.P("}") g.P("return this.XXX_extensions") g.Out() g.P("}") } g.file.addExport(message, messageSymbol{ccTypeName, hasExtensions, isMessageSet}) // Default constants for _, field := range message.Field { def := proto.GetString(field.DefaultValue) if def == "" { continue } fieldname := "Default_" + ccTypeName + "_" + CamelCase(*field.Name) typename, _ := g.GoType(message, field) if typename[0] == '*' { typename = typename[1:] } kind := "const " switch { case typename == "bool": case typename == "string": def = Quote(def) case typename == "[]byte": def = "[]byte(" + Quote(def) + ")" kind = "var " case def == "inf", def == "-inf", def == "nan": // These names are known to, and defined by, the protocol language. switch def { case "inf": def = "math.Inf(1)" case "-inf": def = "math.Inf(-1)" case "nan": def = "math.NaN()" } if *field.Type == descriptor.FieldDescriptorProto_TYPE_FLOAT { def = "float32(" + def + ")" } kind = "var " case *field.Type == descriptor.FieldDescriptorProto_TYPE_ENUM: // Must be an enum. Need to construct the prefixed name. obj := g.ObjectNamed(proto.GetString(field.TypeName)) enum, ok := obj.(*EnumDescriptor) if !ok { log.Println("don't know how to generate constant for", fieldname) continue } def = g.DefaultPackageName(enum) + enum.prefix() + def } g.P(kind, fieldname, " ", typename, " = ", def) g.file.addExport(message, constOrVarSymbol{fieldname, kind}) } g.P() for _, ext := range message.ext { g.generateExtension(ext) } } func (g *Generator) generateExtension(ext *ExtensionDescriptor) { ccTypeName := ext.DescName() extendedType := "*" + g.TypeName(g.ObjectNamed(*ext.Extendee)) field := ext.FieldDescriptorProto fieldType, wireType := g.GoType(ext.parent, field) tag := g.goTag(field, wireType) g.RecordTypeUse(*ext.Extendee) if n := ext.FieldDescriptorProto.TypeName; n != nil { // foreign extension type g.RecordTypeUse(*n) } g.P("var ", ccTypeName, " = &", g.ProtoPkg, ".ExtensionDesc{") g.In() g.P("ExtendedType: (", extendedType, ")(nil),") g.P("ExtensionType: (", fieldType, ")(nil),") g.P("Field: ", field.Number, ",") g.P(`Name: "`, g.packageName, ".", strings.Join(ext.TypeName(), "."), `",`) g.P("Tag: ", tag, ",") g.Out() g.P("}") g.P() g.file.addExport(ext, constOrVarSymbol{ccTypeName, "var"}) } func (g *Generator) generateInitFunction() { g.P("func init() {") g.In() for _, enum := range g.file.enum { g.generateEnumRegistration(enum) } for _, d := range g.file.desc { for _, ext := range d.ext { g.generateExtensionRegistration(ext) } } for _, ext := range g.file.ext { g.generateExtensionRegistration(ext) } g.Out() g.P("}") } func (g *Generator) generateEnumRegistration(enum *EnumDescriptor) { // // We always print the full (proto-world) package name here. pkg := proto.GetString(enum.File().Package) if pkg != "" { pkg += "." } // The full type name typeName := enum.TypeName() // The full type name, CamelCased. ccTypeName := CamelCaseSlice(typeName) g.P(g.ProtoPkg+".RegisterEnum(", Quote(pkg+ccTypeName), ", ", ccTypeName+"_name, ", ccTypeName+"_value)") } func (g *Generator) generateExtensionRegistration(ext *ExtensionDescriptor) { g.P(g.ProtoPkg+".RegisterExtension(", ext.DescName(), ")") } // And now lots of helper functions. // Is c an ASCII lower-case letter? func isASCIILower(c byte) bool { return 'a' <= c && c <= 'z' } // Is c an ASCII digit? func isASCIIDigit(c byte) bool { return '0' <= c && c <= '9' } // CamelCase returns the CamelCased name. // If there is an interior underscore followed by a lower case letter, // drop the underscore and convert the letter to upper case. // There is a remote possibility of this rewrite causing a name collision, // but it's so remote we're prepared to pretend it's nonexistent - since the // C++ generator lowercases names, it's extremely unlikely to have two fields // with different capitalizations. // In short, _my_field_name_2 becomes XMyFieldName2. func CamelCase(s string) string { if s == "" { return "" } t := make([]byte, 0, 32) i := 0 if s[0] == '_' { // Need a capital letter; drop the '_'. t = append(t, 'X') i++ } // Invariant: if the next letter is lower case, it must be converted // to upper case. // That is, we process a word at a time, where words are marked by _ or // upper case letter. Digits are treated as words. for ; i < len(s); i++ { c := s[i] if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { continue // Skip the underscore in s. } if isASCIIDigit(c) { t = append(t, c) continue } // Assume we have a letter now - if not, it's a bogus identifier. // The next word is a sequence of characters that must start upper case. if isASCIILower(c) { c ^= ' ' // Make it a capital letter. } t = append(t, c) // Guaranteed not lower case. // Accept lower case sequence that follows. for i+1 < len(s) && isASCIILower(s[i+1]) { i++ t = append(t, s[i]) } } return string(t) } // CamelCaseSlice is like CamelCase, but the argument is a slice of strings to // be joined with "_". func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem, "_")) } // dottedSlice turns a sliced name into a dotted name. func dottedSlice(elem []string) string { return strings.Join(elem, ".") } // Quote returns a Go-source quoted string representation of s. func Quote(s string) string { return fmt.Sprintf("%q", s) } // Given a .proto file name, return the output name for the generated Go program. func goFileName(name string) string { ext := path.Ext(name) if ext == ".proto" || ext == ".protodevel" { name = name[0 : len(name)-len(ext)] } return name + ".pb.go" } // Is this field optional? func isOptional(field *descriptor.FieldDescriptorProto) bool { return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_OPTIONAL } // Is this field required? func isRequired(field *descriptor.FieldDescriptorProto) bool { return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_REQUIRED } // Is this field repeated? func isRepeated(field *descriptor.FieldDescriptorProto) bool { return field.Label != nil && *field.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED } // DotToUnderscore is the mapping function used to generate Go names from package names, // which can be dotted in the input .proto file. It maps dots to underscores. // Because we also get here from package names generated from file names, it also maps // minus signs to underscores. func DotToUnderscore(rune int) int { switch rune { case '.', '-': return '_' } return rune } // BaseName returns the last path element of the name, with the last dotted suffix removed. func BaseName(name string) string { // First, find the last element if i := strings.LastIndex(name, "/"); i >= 0 { name = name[i+1:] } // Now drop the suffix if i := strings.LastIndex(name, "."); i >= 0 { name = name[0:i] } return name }