├── .gitignore ├── example ├── test.lua ├── Make.bat ├── addressbook_gen.sp ├── addressbook_gen.sproto ├── addressbook.sp ├── addressbook_gen.lua ├── addressbook_test.go ├── addressbook_gen.go └── addressbook_gen.cs ├── enum.go ├── meta ├── commentparser_test.go ├── parse_misc.go ├── parser_test.go ├── parse_enum.go ├── parse_struct.go ├── parse_enumfield.go ├── fileset.go ├── commentgroup.go ├── lazyfield.go ├── fieldtype.go ├── parse_all.go ├── commentparser.go ├── parse_structfield.go ├── struct.go ├── file.go ├── field.go └── parser.go ├── pb2sproto ├── Make.bat ├── pb.proto ├── pb.sp ├── main.go └── gen_proto.go ├── sprotogen ├── gen_sproto.go ├── gen_emmylua.go ├── strhash.go ├── enumvaluegroup.go ├── util.go ├── gen_lua.go ├── gen_sp.go ├── main.go ├── model.go ├── gen_go.go └── gen_csharp.go ├── LICENSE ├── sproto.go ├── pack_test.go ├── pack.go ├── README.md ├── README_en.md ├── decode.go ├── encode.go ├── meta.go └── sproto_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | pb2sproto/meta.pb 3 | 4 | example/luadll.dll 5 | -------------------------------------------------------------------------------- /example/test.lua: -------------------------------------------------------------------------------- 1 | local a= require "addressbook" 2 | 3 | for k, v in pairs( a.NameByID) do 4 | print(k, v) 5 | end 6 | 7 | for k, v in pairs( a.IDByName) do 8 | print(k, v) 9 | end 10 | -------------------------------------------------------------------------------- /enum.go: -------------------------------------------------------------------------------- 1 | package sproto 2 | 3 | import "strconv" 4 | 5 | func EnumName(m map[int32]string, v int32) string { 6 | s, ok := m[v] 7 | if ok { 8 | return s 9 | } 10 | return strconv.Itoa(int(v)) 11 | } 12 | -------------------------------------------------------------------------------- /meta/commentparser_test.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import "testing" 4 | 5 | func TestCommentParser(t *testing.T) { 6 | 7 | v, err := parseComment("[agent] client -> battle # comment") 8 | if err != nil{ 9 | t.Error(err) 10 | t.FailNow() 11 | } 12 | 13 | t.Log(v) 14 | } 15 | -------------------------------------------------------------------------------- /meta/parse_misc.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import "strings" 4 | 5 | func parseFileTag(p *sprotoParser, fileD *FileDescriptor, srcName string) { 6 | 7 | p.Expect(Token_FileTag) 8 | 9 | rawTagStr := p.Expect(Token_String).Value() 10 | for _, tagStr := range strings.Split(rawTagStr, " ") { 11 | fileD.fileTag = append(fileD.fileTag, strings.TrimSpace(tagStr)) 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /pb2sproto/Make.bat: -------------------------------------------------------------------------------- 1 | set CURR_DIR=%cd% 2 | 3 | : Build generator 4 | cd ..\..\..\..\.. 5 | set GOPATH=%cd% 6 | go build -o %CURR_DIR%\protoc-gen-meta.exe github.com/davyxu/pbmeta/protoc-gen-meta 7 | cd %CURR_DIR% 8 | 9 | protoc.exe --plugin=protoc-gen-meta=protoc-gen-meta.exe --meta_out=meta.pb:. --proto_path "." pb.proto 10 | :@IF %ERRORLEVEL% NEQ 0 pause 11 | 12 | go build -o pb2sproto.exe gen_proto.go main.go 13 | 14 | pb2sproto --pbmeta=meta.pb --outdir=. -------------------------------------------------------------------------------- /meta/parser_test.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestParser(t *testing.T) { 9 | 10 | fileD, err := ParseFile("../example/addressbook.sp") 11 | 12 | if err != nil { 13 | t.Log(err) 14 | t.FailNow() 15 | } 16 | 17 | v, _ := fileD.StructByName["PhoneNumber"] 18 | //f, _ := v.FieldByName["number"] 19 | 20 | tag, _ := v.MatchTag("agent") 21 | fmt.Println("tag: ", tag) 22 | 23 | fmt.Println(fileD.String()) 24 | } 25 | -------------------------------------------------------------------------------- /sprotogen/gen_sproto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const sprotoCodeTemplate = `# Generated by github.com/davyxu/gosproto/sprotogen 4 | # DO NOT EDIT! 5 | 6 | {{range .Structs}} 7 | .{{.Name}} { 8 | {{range .StFields}} 9 | {{.Name}} {{.TagNumber}} : {{.CompatibleTypeString}} 10 | {{end}} 11 | } 12 | {{end}} 13 | 14 | ` 15 | 16 | func gen_sproto(fm *fileModel, filename string) { 17 | 18 | addData(fm, "sproto") 19 | 20 | generateCode("sp->sproto", sprotoCodeTemplate, filename, fm, nil) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sprotogen/gen_emmylua.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const emmyluaCodeTemplate = `-- Generated by github.com/davyxu/gosproto/sprotogen 4 | -- DO NOT EDIT! 5 | 6 | {{range .Structs}} 7 | ---@class {{.Name}} {{range .StFields}} 8 | ---@field public {{.Name}} {{.CSTypeName}} {{end}} 9 | local {{.Name}} = {} 10 | {{end}} 11 | 12 | ` 13 | 14 | func gen_emmylua(fm *fileModel, filename string) { 15 | 16 | addData(fm, "lua") 17 | 18 | generateCode("sp->emmylua", emmyluaCodeTemplate, filename, fm, nil) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /pb2sproto/pb.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package gamedef; 4 | 5 | 6 | enum MyCar { 7 | Monkey = 0; 8 | Monk = 1; 9 | Pig = 2; 10 | } 11 | 12 | 13 | // This is phone number 14 | message PhoneNumber { 15 | 16 | string Number = 1; // mobile phone number 17 | 18 | int32 Type = 2; // phone type 19 | } 20 | 21 | // Person 22 | message Person { 23 | 24 | string Name = 1; 25 | 26 | int32 Id = 2; 27 | 28 | string Email = 3; 29 | 30 | repeated PhoneNumber phone = 4; 31 | 32 | bool Staff = 5; 33 | 34 | } 35 | 36 | // All person list 37 | message AddressBook { 38 | 39 | repeated Person person = 1; 40 | 41 | } 42 | 43 | -------------------------------------------------------------------------------- /pb2sproto/pb.sp: -------------------------------------------------------------------------------- 1 | # Generated by github.com/davyxu/gosproto/pb2sproto 2 | # Source: pb.proto 3 | 4 | 5 | 6 | .MyCar { 7 | 8 | Monkey 0 9 | 10 | Monk 1 11 | 12 | Pig 2 13 | 14 | } 15 | 16 | 17 | 18 | # This is phone number 19 | .PhoneNumber { 20 | 21 | Number 1 : string # mobile phone number 22 | 23 | Type 2 : int32 # phone type 24 | 25 | } 26 | 27 | # Person 28 | .Person { 29 | 30 | Name 1 : string 31 | 32 | Id 2 : int32 33 | 34 | Email 3 : string 35 | 36 | phone 4 : *PhoneNumber 37 | 38 | Staff 5 : boolean 39 | 40 | } 41 | 42 | # All person list 43 | .AddressBook { 44 | 45 | person 1 : *Person 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /meta/parse_enum.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | func parseEnum(p *sprotoParser, fileD *FileDescriptor, srcName string) { 8 | 9 | // enum 10 | enumToken := p.Expect(Token_Enum) 11 | 12 | d := newDescriptor(fileD) 13 | d.Type = DescriptorType_Enum 14 | 15 | // 名字 16 | d.Name = p.Expect(Token_Identifier).Value() 17 | 18 | d.CommentGroup = p.CommentGroupByLine(enumToken.Line()) 19 | 20 | // { 21 | p.Expect(Token_CurlyBraceL) 22 | 23 | for p.TokenID() != Token_CurlyBraceR { 24 | 25 | // 字段 26 | parseEnumField(p, d) 27 | 28 | } 29 | 30 | p.Expect(Token_CurlyBraceR) 31 | 32 | // } 33 | 34 | // 名字重复检查 35 | 36 | if fileD.NameExists(d.Name) { 37 | panic(errors.New("Duplicate name: " + d.Name)) 38 | } 39 | 40 | fileD.addObject(d, srcName) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /meta/parse_struct.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | func parseStruct(p *sprotoParser, fileD *FileDescriptor, srcName string) { 8 | 9 | dotToken := p.RawToken() 10 | 11 | p.NextToken() 12 | 13 | d := newDescriptor(fileD) 14 | d.Type = DescriptorType_Struct 15 | 16 | // 名字 17 | d.Name = p.Expect(Token_Identifier).Value() 18 | 19 | d.CommentGroup = p.CommentGroupByLine(dotToken.Line()) 20 | 21 | // { 22 | p.Expect(Token_CurlyBraceL) 23 | 24 | for p.TokenID() != Token_CurlyBraceR { 25 | 26 | // 字段 27 | parseStructField(p, d) 28 | 29 | } 30 | 31 | p.Expect(Token_CurlyBraceR) 32 | 33 | // } 34 | 35 | // 名字重复检查 36 | 37 | if fileD.NameExists(d.Name) { 38 | panic(errors.New("Duplicate name: " + d.Name)) 39 | } 40 | 41 | fileD.addObject(d, srcName) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /example/Make.bat: -------------------------------------------------------------------------------- 1 | set CURR_DIR=%cd% 2 | 3 | : Build generator 4 | cd ..\..\..\..\.. 5 | set GOPATH=%cd% 6 | go build -o %CURR_DIR%\sprotogen.exe github.com/davyxu/gosproto/sprotogen 7 | @IF %ERRORLEVEL% NEQ 0 pause 8 | cd %CURR_DIR% 9 | 10 | : Generate go source file by sproto 11 | sprotogen --go_out=addressbook_gen.go --package=example --cellnet_reg=true addressbook.sp 12 | @IF %ERRORLEVEL% NEQ 0 pause 13 | 14 | : Convert to standard sproto file 15 | sprotogen --sproto_out=addressbook_gen.sproto addressbook.sp 16 | @IF %ERRORLEVEL% NEQ 0 pause 17 | 18 | : Generate c# source file by sproto 19 | sprotogen --cs_out=addressbook_gen.cs --package=example addressbook.sp 20 | @IF %ERRORLEVEL% NEQ 0 pause 21 | 22 | : Generate lua source file by sproto 23 | sprotogen --lua_out=addressbook_gen.lua --package=example addressbook.sp 24 | @IF %ERRORLEVEL% NEQ 0 pause -------------------------------------------------------------------------------- /meta/parse_enumfield.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import "errors" 4 | 5 | func parseEnumField(p *sprotoParser, d *Descriptor) { 6 | 7 | fd := newFieldDescriptor(d) 8 | 9 | nameToken := p.RawToken() 10 | 11 | // 字段名 12 | fd.Name = p.Expect(Token_Identifier).Value() 13 | 14 | if _, ok := d.FieldByName[fd.Name]; ok { 15 | panic(errors.New("Duplicate field name: " + d.Name)) 16 | } 17 | 18 | // 有等号 19 | if p.TokenID() == Token_Assign { 20 | p.NextToken() 21 | 22 | // tag 23 | fd.Tag = p.Expect(Token_Numeral).ToInt() 24 | 25 | } else { 26 | 27 | if len(d.Fields) == 0 { 28 | fd.AutoTag = 0 29 | } else { 30 | fd.AutoTag = d.MaxTag() + 1 31 | } 32 | 33 | } 34 | 35 | fd.Type = FieldType_Int32 36 | 37 | fd.CommentGroup = p.CommentGroupByLine(nameToken.Line()) 38 | 39 | checkField(d, fd) 40 | 41 | d.addField(fd) 42 | 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /example/addressbook_gen.sp: -------------------------------------------------------------------------------- 1 | # Generated by github.com/davyxu/gosproto/sprotogen 2 | # DO NOT EDIT! 3 | 4 | 5 | enum MyCar { 6 | 7 | Monkey 8 | Monk 9 | Pig 10 | } 11 | 12 | 13 | 14 | 15 | message PhoneNumber { 16 | 17 | number string 18 | 19 | type int32 20 | } 21 | 22 | 23 | message Person { 24 | 25 | name string 26 | 27 | id int32 28 | 29 | email string 30 | 31 | phone PhoneNumber 32 | } 33 | 34 | message AddressBook { 35 | 36 | person []Person 37 | } 38 | 39 | 40 | # [agent] client -> battle # comment 41 | message MyData { 42 | 43 | 44 | name string 45 | 46 | type MyCar 47 | 48 | int32 int32 // extend standard 49 | # extend standard 50 | uint32 uint32 51 | 52 | int64 int64 53 | 54 | uint64 uint64 55 | } 56 | 57 | 58 | message MyProfile { 59 | 60 | 61 | nameField MyData 62 | 63 | nameArray sMyData 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /example/addressbook_gen.sproto: -------------------------------------------------------------------------------- 1 | # Generated by github.com/davyxu/gosproto/sprotogen 2 | # DO NOT EDIT! 3 | 4 | 5 | .PhoneNumber { 6 | 7 | number 0 : string 8 | 9 | type 1 : integer 10 | 11 | } 12 | 13 | .Person { 14 | 15 | name 0 : string 16 | 17 | id 1 : integer 18 | 19 | email 2 : string 20 | 21 | phone 3 : *PhoneNumber 22 | 23 | } 24 | 25 | .AddressBook { 26 | 27 | person 0 : *Person 28 | 29 | } 30 | 31 | .MyData { 32 | 33 | name 0 : string 34 | 35 | Type 1 : integer 36 | 37 | Int32 2 : integer 38 | 39 | Uint32 3 : integer 40 | 41 | Int64 4 : integer 42 | 43 | Uint64 5 : integer 44 | 45 | Bool 6 : boolean 46 | 47 | Float32 7 : integer 48 | 49 | Float64 8 : integer 50 | 51 | Stream 9 : string 52 | 53 | } 54 | 55 | .MyProfile { 56 | 57 | nameField 0 : MyData 58 | 59 | nameArray 1 : *MyData 60 | 61 | nameMap 2 : *MyData(Type) 62 | 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /meta/fileset.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | type FileDescriptorSet struct { 4 | Files []*FileDescriptor 5 | 6 | unknownFields []*lazyField 7 | } 8 | 9 | func (self *FileDescriptorSet) resolveAll() error { 10 | 11 | for _, v := range self.unknownFields { 12 | if _, err := v.resolve(2); err != nil { 13 | return err 14 | } 15 | } 16 | 17 | return nil 18 | } 19 | 20 | func (self *FileDescriptorSet) addFile(file *FileDescriptor) { 21 | file.FileSet = self 22 | self.Files = append(self.Files, file) 23 | } 24 | 25 | func (self *FileDescriptorSet) parseType(name string) (ft FieldType, structType *Descriptor) { 26 | 27 | for _, file := range self.Files { 28 | 29 | if ft, structType = file.rawParseType(name); ft != FieldType_None { 30 | return ft, structType 31 | } 32 | } 33 | 34 | return FieldType_None, nil 35 | } 36 | 37 | func NewFileDescriptorSet() *FileDescriptorSet { 38 | return &FileDescriptorSet{} 39 | } 40 | -------------------------------------------------------------------------------- /sprotogen/strhash.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var crcTable = make([]uint32, 256) 4 | 5 | const crcPOLY uint32 = 0x04c11db7 6 | 7 | var crcTableInitialized = false 8 | 9 | func initCRCTable() { 10 | 11 | if crcTableInitialized { 12 | return 13 | } 14 | 15 | var i uint32 16 | var c uint32 17 | var j uint32 18 | 19 | for i = 0; i < 256; i++ { 20 | 21 | c = i << 24 22 | 23 | for j = 8; j != 0; j = j - 1 { 24 | 25 | if (c & 0x80000000) != 0 { 26 | c = (c << 1) ^ crcPOLY 27 | } else { 28 | c = c << 1 29 | } 30 | 31 | crcTable[i] = c 32 | } 33 | } 34 | 35 | crcTableInitialized = true 36 | } 37 | 38 | // 字符串转为32位整形值 39 | func StringHash(s string) uint32 { 40 | initCRCTable() 41 | 42 | var hash uint32 43 | var b uint32 44 | 45 | for _, c := range s { 46 | 47 | b = uint32(c) 48 | 49 | hash = ((hash >> 8) & 0x00FFFFFF) ^ crcTable[(hash^b)&0x000000FF] 50 | } 51 | 52 | return hash 53 | } 54 | -------------------------------------------------------------------------------- /pb2sproto/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/davyxu/pbmeta" 9 | ) 10 | 11 | var paramOut = flag.String("outdir", "", "output directory") 12 | 13 | var paramPBMeta = flag.String("pbmeta", "", "Input Google Protobuf Descripte Binary file format, use protoc generated!") 14 | 15 | func getPbMeta(filename string) (*pbmeta.DescriptorPool, error) { 16 | 17 | // 请先运行ExportPluginMeta导出test.pb 18 | fds, err := pbmeta.LoadFileDescriptorSet(filename) 19 | 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | // 描述池 25 | return pbmeta.NewDescriptorPool(fds), nil 26 | 27 | } 28 | 29 | func main() { 30 | 31 | flag.Parse() 32 | 33 | pool, err := getPbMeta(*paramPBMeta) 34 | if err != nil { 35 | fmt.Println(err) 36 | os.Exit(1) 37 | } 38 | 39 | for fileIndex := 0; fileIndex < pool.FileCount(); fileIndex++ { 40 | 41 | gen_proto(pool.File(fileIndex), *paramOut) 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /example/addressbook.sp: -------------------------------------------------------------------------------- 1 | // extend standard 2 | 3 | 4 | message PhoneNumber { 5 | 6 | // 头注释 7 | number string // 尾注释 8 | 9 | // 整形 10 | type int32 11 | 12 | } 13 | 14 | 15 | message Person { 16 | 17 | name string 18 | 19 | id int32 20 | 21 | email string 22 | 23 | phone []PhoneNumber 24 | 25 | } 26 | 27 | 28 | message AddressBook { 29 | 30 | person []Person 31 | 32 | } 33 | 34 | 35 | enum MyCar { 36 | 37 | Monkey 38 | 39 | Monk 40 | 41 | Pig 42 | 43 | } 44 | 45 | 46 | message MyData { 47 | 48 | name string 49 | 50 | Type MyCar 51 | 52 | Int32 int32 // extend standard 53 | 54 | Uint32 uint32 55 | 56 | Int64 int64 57 | 58 | Uint64 uint64 59 | 60 | Bool bool 61 | 62 | Float32 float32 // [ExtendPrecision]100 # 手动设置精度, 默认1000 63 | 64 | Float64 float64 65 | 66 | Stream bytes 67 | 68 | } 69 | 70 | 71 | message MyProfile { 72 | 73 | nameField MyData 74 | 75 | nameArray []MyData 76 | 77 | nameMap []MyData(Type) 78 | 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /meta/commentgroup.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/davyxu/golexer" 7 | ) 8 | 9 | type TaggedComment struct { 10 | Name string 11 | Value string 12 | } 13 | 14 | type CommentGroup struct { 15 | Pos golexer.TokenPos 16 | 17 | Leading string 18 | Trailing string 19 | 20 | taggedComments []TaggedComment 21 | } 22 | 23 | func (self *CommentGroup) addLineComment(text string) { 24 | 25 | if text == "" { 26 | return 27 | } 28 | 29 | if v, err := parseComment(text); err == nil && v.Name != "" { 30 | self.taggedComments = append(self.taggedComments, v) 31 | } 32 | } 33 | 34 | func (self *CommentGroup) MatchTag(tag string) (string, bool) { 35 | 36 | for _, c := range self.taggedComments { 37 | if c.Name == tag { 38 | return c.Value, true 39 | } 40 | } 41 | 42 | return "", false 43 | 44 | } 45 | 46 | func (self *CommentGroup) String() string { 47 | return fmt.Sprintf("Leading: %s Trailing: %s", self.Leading, self.Trailing) 48 | } 49 | 50 | func newCommentGroup() *CommentGroup { 51 | return &CommentGroup{} 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Davy xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sproto.go: -------------------------------------------------------------------------------- 1 | package sproto 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrNonPtr = errors.New("sproto: called with Non-Ptr type") 9 | ErrNonStruct = errors.New("sproto: Encode called with Non-Ptr") 10 | ErrNil = errors.New("sproto: Encode called with nil") 11 | ErrDecode = errors.New("sproto: Decode msg failed") 12 | ErrUnpack = errors.New("sproto: Unpack data failed") 13 | ) 14 | 15 | func Append(dst, src []byte) []byte { 16 | l := len(dst) 17 | if l+len(src) > cap(dst) { 18 | // allocate double what's needed, for future growth 19 | buf := make([]byte, (l+len(src))*2) 20 | copy(buf, dst) 21 | dst = buf 22 | } 23 | dst = dst[0 : l+len(src)] 24 | copy(dst[l:], src) 25 | return dst 26 | } 27 | 28 | // encode && pack 29 | func EncodePacked(sp interface{}) ([]byte, error) { 30 | unpacked, err := Encode(sp) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return Pack(unpacked), nil 35 | } 36 | 37 | // unpack && decode 38 | func DecodePacked(data []byte, sp interface{}) error { 39 | unpacked, err := Unpack(data) 40 | if err != nil { 41 | return err 42 | } 43 | _, err = Decode(unpacked, sp) 44 | return err 45 | } 46 | -------------------------------------------------------------------------------- /meta/lazyfield.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/davyxu/golexer" 7 | ) 8 | 9 | type lazyField struct { 10 | typeName string 11 | mainIndexName string 12 | 13 | fd *FieldDescriptor 14 | 15 | d *Descriptor 16 | 17 | tp golexer.TokenPos 18 | 19 | miss bool 20 | } 21 | 22 | func newLazyField(typeName string, fd *FieldDescriptor, d *Descriptor, tp golexer.TokenPos) *lazyField { 23 | return &lazyField{typeName: typeName, 24 | fd: fd, 25 | d: d, 26 | tp: tp} 27 | } 28 | 29 | func (self *lazyField) resolve(pass int) (bool, error) { 30 | 31 | self.fd.Type, self.fd.Complex = self.fd.parseType(self.typeName) 32 | 33 | if self.fd.Type == FieldType_None { 34 | if pass > 1 { 35 | 36 | fmt.Println(self.tp.String()) 37 | 38 | return true, errors.New("type not found: " + self.typeName) 39 | } else { 40 | 41 | self.miss = true 42 | return true, nil 43 | } 44 | } 45 | 46 | if self.mainIndexName != "" { 47 | if indexFd, ok := self.fd.Complex.FieldByName[self.mainIndexName]; ok { 48 | self.fd.MainIndex = indexFd 49 | } else { 50 | if pass > 1 { 51 | return true, errors.New("Main index not found:" + self.mainIndexName) 52 | } else { 53 | return true, nil 54 | } 55 | 56 | } 57 | } 58 | 59 | return false, nil 60 | } 61 | -------------------------------------------------------------------------------- /meta/fieldtype.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type FieldType int 8 | 9 | const ( 10 | FieldType_None FieldType = iota 11 | FieldType_Integer 12 | FieldType_Int32 13 | FieldType_Int64 14 | FieldType_UInt32 15 | FieldType_UInt64 16 | FieldType_Bool 17 | FieldType_String 18 | FieldType_Struct 19 | FieldType_Enum 20 | FieldType_Float32 21 | FieldType_Float64 22 | FieldType_Bytes 23 | ) 24 | 25 | var fieldtypeByStr = map[string]FieldType{ 26 | "boolean": FieldType_Bool, 27 | "integer": FieldType_Integer, 28 | "int32": FieldType_Int32, 29 | "int64": FieldType_Int64, 30 | "uint32": FieldType_UInt32, 31 | "uint64": FieldType_UInt64, 32 | "string": FieldType_String, 33 | "struct": FieldType_Struct, 34 | "enum": FieldType_Enum, 35 | "float32": FieldType_Float32, 36 | "float64": FieldType_Float64, 37 | "bytes": FieldType_Bytes, 38 | } 39 | 40 | var strByFieldtype = map[FieldType]string{} 41 | 42 | func init() { 43 | for k, v := range fieldtypeByStr { 44 | strByFieldtype[v] = k 45 | } 46 | } 47 | 48 | func ParseFieldType(str string) FieldType { 49 | 50 | if t, ok := fieldtypeByStr[str]; ok { 51 | return t 52 | } 53 | 54 | if str == "bool" { 55 | return FieldType_Bool 56 | } 57 | 58 | return FieldType_None 59 | } 60 | 61 | func (self FieldType) String() string { 62 | 63 | if v, ok := strByFieldtype[self]; ok { 64 | return v 65 | } 66 | 67 | return fmt.Sprintf("none(%d)", self) 68 | } 69 | -------------------------------------------------------------------------------- /sprotogen/enumvaluegroup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davyxu/gosproto/meta" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // 本文件内功能仅做项目内部功能使用, 不做通用功能 12 | 13 | func enumValueGroup(fm *fileModel) { 14 | 15 | fileset := fm.FileDescriptorSet 16 | 17 | var maxGroup int64 18 | 19 | var allTagNumbers = map[int]*meta.FieldDescriptor{} 20 | 21 | for _, file := range fileset.Files { 22 | 23 | for _, e := range file.Enums { 24 | 25 | if rawValue, ok := e.MatchTag("EnumValueOffset"); ok { 26 | 27 | if offset, err := strconv.ParseInt(strings.TrimSpace(rawValue), 10, 32); err == nil { 28 | 29 | e.TagBase = int(offset) 30 | 31 | if offset > maxGroup { 32 | maxGroup = offset 33 | } 34 | } 35 | 36 | } 37 | 38 | if strings.HasSuffix(e.Name, "Result") { 39 | 40 | e.EnumValueIgnoreType = true 41 | 42 | //fmt.Printf("%s (%s)\n", e.Name, e.File.FileName) 43 | 44 | for _, fd := range e.Fields { 45 | 46 | //fmt.Printf(" %s = %d\n", fd.Name, fd.TagNumber()) 47 | 48 | if fd.TagNumber() == 0 { 49 | continue 50 | } 51 | 52 | if prev, ok := allTagNumbers[fd.TagNumber()]; ok { 53 | 54 | fmt.Printf("Duplicated enum value: %d %s.%s(%s) prev: %s.%s(%s)\n", fd.TagNumber(), fd.Struct.Name, fd.Name, fd.Struct.File.FileName, prev.Struct.Name, prev.Name, prev.Struct.File.FileName) 55 | os.Exit(1) 56 | } 57 | 58 | allTagNumbers[fd.TagNumber()] = fd 59 | } 60 | 61 | } 62 | 63 | } 64 | 65 | } 66 | 67 | fmt.Printf("max group: %d\n", maxGroup) 68 | 69 | } 70 | -------------------------------------------------------------------------------- /sprotogen/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/parser" 7 | "go/printer" 8 | "go/token" 9 | "io/ioutil" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "text/template" 14 | ) 15 | 16 | type generateOption struct { 17 | formatGoCode bool 18 | 19 | outputData []byte 20 | } 21 | 22 | // 字段首字母大写 23 | func publicFieldName(name string) string { 24 | return strings.ToUpper(string(name[0])) + name[1:] 25 | } 26 | 27 | func generateCode(templateName, templateStr, output string, model interface{}, opt *generateOption) { 28 | 29 | var err error 30 | 31 | if opt == nil { 32 | opt = &generateOption{} 33 | } 34 | 35 | var bf bytes.Buffer 36 | 37 | tpl, err := template.New(templateName).Parse(templateStr) 38 | if err != nil { 39 | goto OnError 40 | } 41 | 42 | err = tpl.Execute(&bf, model) 43 | if err != nil { 44 | goto OnError 45 | } 46 | 47 | if opt.formatGoCode { 48 | if err = formatCode(&bf); err != nil { 49 | fmt.Println("format golang code err", err) 50 | } 51 | } 52 | 53 | opt.outputData = bf.Bytes() 54 | 55 | if output != "" { 56 | 57 | os.MkdirAll(filepath.Dir(output), 666) 58 | 59 | err = ioutil.WriteFile(output, bf.Bytes(), 0666) 60 | 61 | if err != nil { 62 | goto OnError 63 | } 64 | } 65 | return 66 | 67 | OnError: 68 | fmt.Println(err) 69 | os.Exit(1) 70 | } 71 | 72 | func formatCode(bf *bytes.Buffer) error { 73 | 74 | fset := token.NewFileSet() 75 | 76 | ast, err := parser.ParseFile(fset, "", bf, parser.ParseComments) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | bf.Reset() 82 | 83 | err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(bf, fset, ast) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /meta/parse_all.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/davyxu/golexer" 7 | "io/ioutil" 8 | ) 9 | 10 | func ParseFile(fileName string) (*FileDescriptorSet, error) { 11 | 12 | fileset := NewFileDescriptorSet() 13 | 14 | fileD := NewFileDescriptor() 15 | fileD.FileName = fileName 16 | 17 | err := rawPaseFile(fileD, fileName) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | fileset.addFile(fileD) 23 | 24 | return fileset, fileset.resolveAll() 25 | } 26 | 27 | func ParseFileList(fileset *FileDescriptorSet, filelist []string) (string, error) { 28 | 29 | for _, filename := range filelist { 30 | 31 | fileD := NewFileDescriptor() 32 | fileD.FileName = filename 33 | fileset.addFile(fileD) 34 | 35 | if err := rawPaseFile(fileD, filename); err != nil { 36 | return filename, err 37 | } 38 | 39 | } 40 | 41 | return "", fileset.resolveAll() 42 | 43 | } 44 | 45 | // 从文件解析 46 | func rawPaseFile(fileD *FileDescriptor, fileName string) error { 47 | 48 | data, err := ioutil.ReadFile(fileName) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | return rawParse(fileD, string(data), fileName) 54 | } 55 | 56 | // 解析字符串 57 | func rawParse(fileD *FileDescriptor, data string, srcName string) (retErr error) { 58 | 59 | p := newSProtoParser(srcName) 60 | 61 | defer golexer.ErrorCatcher(func(err error) { 62 | 63 | retErr = fmt.Errorf("%s %s", p.PreTokenPos().String(), err.Error()) 64 | 65 | }) 66 | 67 | p.Lexer().Start(data) 68 | 69 | p.NextToken() 70 | 71 | for p.TokenID() != Token_EOF { 72 | 73 | switch p.TokenID() { 74 | case Token_Dot, Token_Message: 75 | parseStruct(p, fileD, srcName) 76 | case Token_Enum: 77 | parseEnum(p, fileD, srcName) 78 | case Token_FileTag: 79 | parseFileTag(p, fileD, srcName) 80 | default: 81 | panic(errors.New("Unknown token: " + p.TokenValue())) 82 | } 83 | 84 | } 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /meta/commentparser.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import "github.com/davyxu/golexer" 4 | 5 | // 自定义的token id 6 | const ( 7 | CommentToken_EOF = iota 8 | CommentToken_LeftBrace 9 | CommentToken_RightBrace 10 | CommentToken_WhiteSpace 11 | CommentToken_LineEnd 12 | CommentToken_UnixStyleComment 13 | CommentToken_Identifier 14 | CommentToken_Unknown 15 | ) 16 | 17 | type CommentParser struct { 18 | *golexer.Parser 19 | } 20 | 21 | func parseComment(src string) (ret TaggedComment, retErr error) { 22 | 23 | p := NewCommentParser(src) 24 | 25 | // defer golexer.ErrorCatcher(func(err error) { 26 | 27 | // fmt.Printf("%s %s\n", p.PreTokenPos().String(), err.Error()) 28 | 29 | // retErr = err 30 | 31 | // }) 32 | 33 | p.Lexer().Start(src) 34 | 35 | p.NextToken() 36 | 37 | for p.TokenID() != CommentToken_EOF { 38 | 39 | //log.Debugln("#", self.TokenID(), self.TokenValue()) 40 | 41 | if p.TokenID() == CommentToken_WhiteSpace { 42 | p.NextToken() 43 | continue 44 | } 45 | 46 | // 读取标头 47 | if p.TokenID() == CommentToken_LeftBrace { 48 | 49 | p.NextToken() 50 | 51 | tagNameToken := p.Expect(CommentToken_Identifier) 52 | 53 | p.Expect(CommentToken_RightBrace) 54 | 55 | ret.Name = tagNameToken.Value() 56 | 57 | for { 58 | 59 | ret.Value += p.TokenValue() 60 | 61 | p.NextToken() 62 | 63 | if p.TokenID() == CommentToken_LineEnd || p.TokenID() == CommentToken_EOF { 64 | break 65 | } 66 | } 67 | 68 | } 69 | 70 | p.NextToken() 71 | 72 | } 73 | 74 | return 75 | } 76 | 77 | func NewCommentParser(src string) *CommentParser { 78 | 79 | l := golexer.NewLexer() 80 | 81 | // 匹配顺序从高到低 82 | 83 | l.AddMatcher(golexer.NewSignMatcher(CommentToken_LeftBrace, "[")) 84 | l.AddMatcher(golexer.NewSignMatcher(CommentToken_RightBrace, "]")) 85 | 86 | l.AddMatcher(golexer.NewWhiteSpaceMatcher(CommentToken_WhiteSpace)) 87 | l.AddIgnoreMatcher(golexer.NewLineEndMatcher(CommentToken_LineEnd)) 88 | l.AddIgnoreMatcher(golexer.NewUnixStyleCommentMatcher(CommentToken_UnixStyleComment)) 89 | 90 | l.AddMatcher(golexer.NewIdentifierMatcher(CommentToken_Identifier)) 91 | 92 | l.AddMatcher(golexer.NewUnknownMatcher(CommentToken_Unknown)) 93 | 94 | return &CommentParser{ 95 | Parser: golexer.NewParser(l, src), 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /meta/parse_structfield.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | func parseStructField(p *sprotoParser, d *Descriptor) { 9 | 10 | fd := newFieldDescriptor(d) 11 | 12 | nameToken := p.RawToken() 13 | // 字段名 14 | fd.Name = p.Expect(Token_Identifier).Value() 15 | 16 | if _, ok := d.FieldByName[fd.Name]; ok { 17 | panic(errors.New("Duplicate field name: " + d.Name)) 18 | } 19 | 20 | if p.TokenID() == Token_Numeral { 21 | // tag 22 | fd.Tag = p.Expect(Token_Numeral).ToInt() 23 | } else { // 没写就自动生成 24 | 25 | if len(d.Fields) == 0 { 26 | fd.AutoTag = 0 27 | } else { 28 | fd.AutoTag = d.MaxTag() + 1 29 | } 30 | 31 | } 32 | 33 | if p.TokenID() == Token_Colon { 34 | p.NextToken() 35 | } 36 | 37 | tp := p.TokenPos() 38 | 39 | var typeName string 40 | 41 | switch p.TokenID() { 42 | // 数组 43 | case Token_Star: 44 | p.NextToken() 45 | 46 | fd.Repeatd = true 47 | 48 | typeName = p.Expect(Token_Identifier).Value() 49 | case Token_BracketL: 50 | p.NextToken() 51 | p.Expect(Token_BracketR) 52 | fd.Repeatd = true 53 | 54 | typeName = p.Expect(Token_Identifier).Value() 55 | 56 | case Token_Identifier: 57 | // 普通字段 58 | typeName = p.TokenValue() 59 | p.NextToken() 60 | break 61 | default: 62 | } 63 | 64 | // 根据类型名查找类型及结构体类型 65 | 66 | pf := newLazyField(typeName, fd, d, tp) 67 | 68 | // map的索引解析 ( 69 | if p.TokenID() == Token_ParenL { 70 | p.NextToken() 71 | 72 | // 索引的字段 73 | pf.mainIndexName = p.Expect(Token_Identifier).Value() 74 | 75 | p.Expect(Token_ParenR) 76 | 77 | } 78 | // ) 79 | 80 | fd.CommentGroup = p.CommentGroupByLine(nameToken.Line()) 81 | 82 | // 尝试首次解析 83 | if need2Pass, _ := pf.resolve(1); need2Pass { 84 | d.File.FileSet.unknownFields = append(d.File.FileSet.unknownFields, pf) 85 | } 86 | 87 | checkField(d, fd) 88 | 89 | d.addField(fd) 90 | 91 | return 92 | } 93 | 94 | func checkField(d *Descriptor, fd *FieldDescriptor) { 95 | 96 | if _, ok := d.FieldByName[fd.Name]; ok { 97 | panic(errors.New(fmt.Sprintf("Duplicate field name: %s in %s", fd.Name, d.Name))) 98 | } 99 | 100 | if _, ok := d.FieldByTag[fd.TagNumber()]; ok { 101 | panic(errors.New(fmt.Sprintf("Duplicate field tag: %d in %s", fd.TagNumber(), d.Name))) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /meta/struct.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | ) 7 | 8 | type DescriptorType int 9 | 10 | const ( 11 | DescriptorType_None DescriptorType = iota 12 | DescriptorType_Enum 13 | DescriptorType_Struct 14 | ) 15 | 16 | type Descriptor struct { 17 | *CommentGroup 18 | Name string 19 | SrcName string 20 | Type DescriptorType 21 | 22 | Fields []*FieldDescriptor 23 | FieldByName map[string]*FieldDescriptor 24 | FieldByTag map[int]*FieldDescriptor 25 | 26 | File *FileDescriptor 27 | 28 | TagBase int 29 | 30 | EnumValueIgnoreType bool 31 | } 32 | 33 | func (self *Descriptor) MaxTag() (ret int) { 34 | 35 | for _, fd := range self.Fields { 36 | if fd.TagNumber() > ret { 37 | ret = fd.TagNumber() 38 | } 39 | 40 | } 41 | 42 | return 43 | } 44 | 45 | func (self *Descriptor) TypeName() string { 46 | switch self.Type { 47 | case DescriptorType_Enum: 48 | return "enum" 49 | case DescriptorType_Struct: 50 | return "struct" 51 | } 52 | 53 | return "none" 54 | } 55 | 56 | // c# 要使用的fieldcount 57 | func (self *Descriptor) MaxFieldCount() int { 58 | maxn := len(self.Fields) 59 | lastTag := -1 60 | 61 | for _, fd := range self.Fields { 62 | if fd.TagNumber() < lastTag { 63 | panic(errors.New("tag must in ascending order")) 64 | } 65 | 66 | if fd.TagNumber() > lastTag+1 { 67 | maxn++ 68 | } 69 | 70 | lastTag = fd.TagNumber() 71 | } 72 | 73 | return maxn 74 | } 75 | 76 | func (self *Descriptor) String() string { 77 | 78 | var bf bytes.Buffer 79 | 80 | bf.WriteString(self.Name) 81 | 82 | bf.WriteString(":") 83 | bf.WriteString("\n") 84 | 85 | for _, fd := range self.Fields { 86 | bf.WriteString(" ") 87 | bf.WriteString(fd.String()) 88 | bf.WriteString("\n") 89 | } 90 | 91 | bf.WriteString("\n") 92 | 93 | return bf.String() 94 | } 95 | 96 | func (self *Descriptor) addField(fd *FieldDescriptor) { 97 | self.Fields = append(self.Fields, fd) 98 | self.FieldByName[fd.Name] = fd 99 | self.FieldByTag[fd.Tag] = fd 100 | } 101 | 102 | func newDescriptor(f *FileDescriptor) *Descriptor { 103 | return &Descriptor{ 104 | CommentGroup: newCommentGroup(), 105 | File: f, 106 | FieldByName: make(map[string]*FieldDescriptor), 107 | FieldByTag: make(map[int]*FieldDescriptor), 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pack_test.go: -------------------------------------------------------------------------------- 1 | package sproto_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/davyxu/gosproto" 8 | ) 9 | 10 | type PackTestCase struct { 11 | Name string 12 | Unpacked []byte 13 | Packed []byte 14 | } 15 | 16 | var packTestCases []*PackTestCase = []*PackTestCase{ 17 | &PackTestCase{ 18 | Name: "SimplePack", 19 | Unpacked: []byte{0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x19, 0x00, 0x00, 0x00, 0xaa, 0x01, 0x00, 0x00}, 20 | Packed: []byte{0x51, 0x08, 0x03, 0x02, 0x31, 0x19, 0xaa, 0x01}, 21 | }, 22 | &PackTestCase{ 23 | Name: "FFPack", 24 | Unpacked: bytes.Join([][]byte{ 25 | bytes.Repeat([]byte{0x8a}, 30), 26 | []byte{0x00, 0x00}, 27 | }, nil), 28 | Packed: bytes.Join([][]byte{ 29 | []byte{0xff, 0x03}, 30 | bytes.Repeat([]byte{0x8a}, 30), 31 | []byte{0x00, 0x00}, 32 | }, nil), 33 | }, 34 | } 35 | 36 | func TestPack(t *testing.T) { 37 | var allUnpacked, allPacked []byte 38 | for _, tc := range packTestCases { 39 | packed := sproto.Pack(tc.Unpacked) 40 | if !bytes.Equal(packed, tc.Packed) { 41 | t.Log("packed:", packed) 42 | t.Log("expected:", tc.Packed) 43 | t.Fatalf("test case *%s* failed", tc.Name) 44 | } 45 | allUnpacked = sproto.Append(allUnpacked, tc.Unpacked) 46 | allPacked = sproto.Append(allPacked, packed) 47 | } 48 | 49 | packed := sproto.Pack(allUnpacked) 50 | if !bytes.Equal(packed, allPacked) { 51 | t.Log("packed:", packed) 52 | t.Log("expected:", allPacked) 53 | t.Fatal("test case *total* failed") 54 | } 55 | } 56 | 57 | func TestUnpack(t *testing.T) { 58 | var allUnpacked, allPacked []byte 59 | for _, tc := range packTestCases { 60 | unpacked, err := sproto.Unpack(tc.Packed) 61 | if err != nil { 62 | t.Fatalf("test case *%s* failed with error:%s", tc.Name, err) 63 | } 64 | if !bytes.Equal(unpacked, tc.Unpacked) { 65 | t.Log("unpacked:", unpacked) 66 | t.Log("expected:", tc.Unpacked) 67 | t.Fatalf("test case *%s* failed", tc.Name) 68 | } 69 | allUnpacked = sproto.Append(allUnpacked, tc.Unpacked) 70 | allPacked = sproto.Append(allPacked, tc.Packed) 71 | } 72 | unpacked, err := sproto.Unpack(allPacked) 73 | if err != nil { 74 | t.Fatalf("test case *total* failed with error:%s", err) 75 | } 76 | if !bytes.Equal(unpacked, allUnpacked) { 77 | t.Log("unpacked:", unpacked) 78 | t.Log("expected:", allUnpacked) 79 | t.Fatal("test case *total* failed") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /example/addressbook_gen.lua: -------------------------------------------------------------------------------- 1 | -- Generated by github.com/davyxu/gosproto/sprotogen 2 | -- DO NOT EDIT! 3 | 4 | 5 | Enum = { 6 | 7 | MyCar = { 8 | Monkey = 0, 9 | Monk = 1, 10 | Pig = 2, 11 | }, 12 | 13 | } 14 | 15 | local sproto = { 16 | Schema = [[ 17 | 18 | .PhoneNumber { 19 | number 0 : string 20 | type 1 : integer 21 | } 22 | 23 | .Person { 24 | name 0 : string 25 | id 1 : integer 26 | email 2 : string 27 | phone 3 : *PhoneNumber 28 | } 29 | 30 | .AddressBook { 31 | person 0 : *Person 32 | } 33 | 34 | .MyData { 35 | name 0 : string 36 | Type 1 : integer 37 | Int32 2 : integer 38 | Uint32 3 : integer 39 | Int64 4 : integer 40 | Uint64 5 : integer 41 | Bool 6 : boolean 42 | Float32 7 : integer 43 | Float64 8 : integer 44 | Stream 9 : string 45 | } 46 | 47 | .MyProfile { 48 | nameField 0 : MyData 49 | nameArray 1 : *MyData 50 | nameMap 2 : *MyData(Type) 51 | } 52 | 53 | ]], 54 | 55 | NameByID = { 56 | [4271979557] = "PhoneNumber", 57 | [1498745430] = "Person", 58 | [2618161298] = "AddressBook", 59 | [2244887298] = "MyData", 60 | [438153711] = "MyProfile", 61 | }, 62 | 63 | IDByName = {}, 64 | 65 | ResetByID = { 66 | [4271979557] = function( obj ) -- PhoneNumber 67 | if obj == nil then return end 68 | obj.number = "" 69 | obj.type = 0 70 | end, 71 | [1498745430] = function( obj ) -- Person 72 | if obj == nil then return end 73 | obj.name = "" 74 | obj.id = 0 75 | obj.email = "" 76 | obj.phone = nil 77 | end, 78 | [2618161298] = function( obj ) -- AddressBook 79 | if obj == nil then return end 80 | obj.person = nil 81 | end, 82 | [2244887298] = function( obj ) -- MyData 83 | if obj == nil then return end 84 | obj.name = "" 85 | obj.Type = 0 86 | obj.Int32 = 0 87 | obj.Uint32 = 0 88 | obj.Int64 = 0 89 | obj.Uint64 = 0 90 | obj.Bool = false 91 | obj.Float32 = 0 92 | obj.Float64 = 0 93 | obj.Stream = nil 94 | end, 95 | [438153711] = function( obj ) -- MyProfile 96 | if obj == nil then return end 97 | obj.nameField = nil 98 | obj.nameArray = nil 99 | obj.nameMap = nil 100 | end, 101 | }, 102 | } 103 | 104 | local t = sproto.IDByName 105 | for k, v in pairs(sproto.NameByID) do 106 | t[v] = k 107 | end 108 | 109 | return sproto 110 | 111 | -------------------------------------------------------------------------------- /sprotogen/gen_lua.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/davyxu/gosproto/meta" 5 | ) 6 | 7 | const luaCodeTemplate = `-- Generated by github.com/davyxu/gosproto/sprotogen 8 | -- DO NOT EDIT! 9 | {{if .EnumValueGroup}} 10 | ResultToString = function ( result ) 11 | if result == 0 then 12 | return "OK" 13 | end 14 | 15 | local str = ResultByID[result] 16 | if str == nil then 17 | return string.format("unknown result: %d", result ) 18 | end 19 | 20 | return str 21 | end 22 | 23 | ResultByID = { 24 | {{range $a, $enumObj := .Enums}} {{if .IsResultEnum}} {{range .Fields}} {{if ne .TagNumber 0}} 25 | [{{.TagNumber}}] = "{{$enumObj.Name}}.{{.Name}}", {{end}} {{end}} {{end}} {{end}} 26 | } 27 | 28 | {{end}} 29 | 30 | Enum = { 31 | {{range $a, $enumObj := .Enums}} 32 | {{$enumObj.Name}} = { {{range .Fields}} 33 | {{.Name}} = {{.TagNumber}}, {{end}} 34 | }, 35 | {{end}} 36 | } 37 | 38 | local sproto = { 39 | Schema = [[ 40 | {{range .Structs}} 41 | .{{.Name}} { {{range .StFields}} 42 | {{.Name}} {{.TagNumber}} : {{.CompatibleTypeString}} {{end}} 43 | } 44 | {{end}} 45 | ]], 46 | 47 | NameByID = { {{range .Structs}} 48 | [{{.MsgID}}] = "{{.Name}}",{{end}} 49 | }, 50 | 51 | IDByName = {}, 52 | 53 | ResetByID = { {{range .Structs}} 54 | [{{.MsgID}}] = function( obj ) -- {{.Name}} 55 | if obj == nil then return end {{range .StFields}} 56 | obj.{{.Name}} = {{.LuaDefaultValueString}} {{end}} 57 | end, {{end}} 58 | }, 59 | } 60 | 61 | local t = sproto.IDByName 62 | for k, v in pairs(sproto.NameByID) do 63 | t[v] = k 64 | end 65 | 66 | return sproto 67 | 68 | ` 69 | 70 | func (self *fieldModel) LuaDefaultValueString() string { 71 | 72 | if self.Repeatd { 73 | return "nil" 74 | } 75 | 76 | switch self.Type { 77 | case meta.FieldType_Bool: 78 | return "false" 79 | case meta.FieldType_Int32, 80 | meta.FieldType_Int64, 81 | meta.FieldType_UInt32, 82 | meta.FieldType_UInt64, 83 | meta.FieldType_Integer, 84 | meta.FieldType_Float32, 85 | meta.FieldType_Float64, 86 | meta.FieldType_Enum: 87 | return "0" 88 | case meta.FieldType_String: 89 | return "\"\"" 90 | case meta.FieldType_Struct, 91 | meta.FieldType_Bytes: 92 | return "nil" 93 | } 94 | 95 | return "unknown type" + self.Type.String() 96 | } 97 | 98 | func gen_lua(fm *fileModel, filename string) { 99 | 100 | addData(fm, "lua") 101 | 102 | generateCode("sp->lua", luaCodeTemplate, filename, fm, nil) 103 | 104 | } 105 | -------------------------------------------------------------------------------- /meta/file.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import "bytes" 4 | 5 | type FileDescriptor struct { 6 | FileName string 7 | 8 | Structs []*Descriptor 9 | 10 | StructByName map[string]*Descriptor 11 | 12 | Enums []*Descriptor 13 | 14 | EnumByName map[string]*Descriptor 15 | 16 | ObjectsBySrcName map[string]*Descriptor 17 | Objects []*Descriptor 18 | 19 | FileSet *FileDescriptorSet 20 | 21 | fileTag []string 22 | } 23 | 24 | func (self *FileDescriptor) MatchTag(tag string) bool { 25 | 26 | if len(self.fileTag) == 0 { 27 | return true 28 | } 29 | 30 | for _, libtag := range self.fileTag { 31 | if tag == libtag { 32 | return true 33 | } 34 | } 35 | 36 | return false 37 | } 38 | 39 | func (self *FileDescriptor) String() string { 40 | 41 | var bf bytes.Buffer 42 | 43 | bf.WriteString("Enum:") 44 | for _, st := range self.Enums { 45 | bf.WriteString(st.String()) 46 | bf.WriteString("\n") 47 | } 48 | 49 | bf.WriteString("\n") 50 | 51 | bf.WriteString("Structs:") 52 | for _, st := range self.Structs { 53 | bf.WriteString(st.String()) 54 | bf.WriteString("\n") 55 | } 56 | 57 | return bf.String() 58 | } 59 | 60 | func (self *FileDescriptor) NameExists(name string) bool { 61 | if _, ok := self.StructByName[name]; ok { 62 | return true 63 | } 64 | 65 | if _, ok := self.EnumByName[name]; ok { 66 | return true 67 | } 68 | 69 | return false 70 | } 71 | 72 | func (self *FileDescriptor) addObject(d *Descriptor, srcName string) { 73 | 74 | switch d.Type { 75 | case DescriptorType_Enum: 76 | self.Enums = append(self.Enums, d) 77 | self.EnumByName[d.Name] = d 78 | case DescriptorType_Struct: 79 | self.Structs = append(self.Structs, d) 80 | self.StructByName[d.Name] = d 81 | } 82 | 83 | d.SrcName = srcName 84 | 85 | self.Objects = append(self.Objects, d) 86 | 87 | self.ObjectsBySrcName[srcName] = d 88 | } 89 | 90 | func (self *FileDescriptor) rawParseType(name string) (ft FieldType, structType *Descriptor) { 91 | 92 | if d, ok := self.StructByName[name]; ok { 93 | return FieldType_Struct, d 94 | } 95 | 96 | if d, ok := self.EnumByName[name]; ok { 97 | return FieldType_Enum, d 98 | } 99 | 100 | return FieldType_None, nil 101 | 102 | } 103 | 104 | func NewFileDescriptor() *FileDescriptor { 105 | 106 | return &FileDescriptor{ 107 | StructByName: make(map[string]*Descriptor), 108 | EnumByName: make(map[string]*Descriptor), 109 | ObjectsBySrcName: make(map[string]*Descriptor), 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /sprotogen/gen_sp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/davyxu/gosproto/meta" 11 | ) 12 | 13 | const spCodeTemplate = ` 14 | {{range $a, $obj := .Objects}} 15 | {{.SpLeadingComment}} 16 | {{.TypeName}} {{.Name}} { 17 | {{range .StFields}}{{.SpLeadingComment}}{{if $obj.IsStruct}} 18 | {{.SpFieldString}} 19 | {{else}} 20 | {{.Name}} = {{.TagNumber}}{{.SpTrailingComment}} 21 | {{end}} {{end}} 22 | } 23 | {{end}} 24 | 25 | ` 26 | 27 | func addCommentSignAtEachLine(sign, comment string) string { 28 | 29 | if comment == "" { 30 | return "" 31 | } 32 | var out bytes.Buffer 33 | 34 | scanner := bufio.NewScanner(strings.NewReader(comment)) 35 | 36 | var index int 37 | for scanner.Scan() { 38 | 39 | if index > 0 { 40 | out.WriteString("\n") 41 | } 42 | 43 | out.WriteString(sign) 44 | out.WriteString(scanner.Text()) 45 | 46 | index++ 47 | } 48 | 49 | return out.String() 50 | } 51 | 52 | func (self *fieldModel) SpFieldString() string { 53 | if self.st.f.forceAutoTag { 54 | return fmt.Sprintf("%s %s%s", self.Name, self.TypeString(), self.SpTrailingComment()) 55 | } else { 56 | return fmt.Sprintf("%s %s %s%s", self.Name, self.TagString(), self.TypeString(), self.SpTrailingComment()) 57 | } 58 | } 59 | 60 | func (self *fieldModel) SpTrailingComment() string { 61 | 62 | return addCommentSignAtEachLine("//", self.Trailing) 63 | } 64 | 65 | func (self *fieldModel) TagString() string { 66 | if self.AutoTag == -1 { 67 | return strconv.Itoa(self.Tag) 68 | } 69 | 70 | return "" 71 | } 72 | 73 | func (self *fieldModel) SpLeadingComment() string { 74 | 75 | return addCommentSignAtEachLine("//", self.Leading) 76 | } 77 | 78 | func (self *structModel) SpLeadingComment() string { 79 | 80 | return addCommentSignAtEachLine("//", self.Leading) 81 | } 82 | 83 | func (self *structModel) TypeName() string { 84 | switch self.Type { 85 | case meta.DescriptorType_Enum: 86 | return "enum" 87 | case meta.DescriptorType_Struct: 88 | return "message" 89 | } 90 | 91 | return "none" 92 | } 93 | 94 | func gen_sp(fileset *meta.FileDescriptorSet, forceAutoTag bool) { 95 | 96 | //for srcName := range fileD.ObjectsBySrcName { 97 | // fm := &fileModel{ 98 | // FileDescriptorSet: fileset, 99 | // forceAutoTag: forceAutoTag, 100 | // } 101 | // 102 | // addStruct(fm, fileD, srcName) 103 | // 104 | // generateCode("sp->sp", spCodeTemplate, srcName, fm, nil) 105 | //} 106 | 107 | } 108 | -------------------------------------------------------------------------------- /pack.go: -------------------------------------------------------------------------------- 1 | package sproto 2 | 3 | func packSegment(dst []byte, src []byte, ffN int) int { 4 | var header uint8 = 0 5 | notzero := 0 6 | for i, v := range src { 7 | if v != 0 { 8 | notzero++ 9 | header |= (1 << uint(i)) 10 | dst[notzero] = v 11 | } 12 | } 13 | 14 | if ffN > 0 { 15 | if notzero >= 6 { 16 | return 8 17 | } 18 | } 19 | 20 | if notzero == 8 { 21 | return 10 22 | } 23 | 24 | dst[0] = header 25 | return notzero + 1 26 | } 27 | 28 | func writeFF(dst, src []byte, n int) { 29 | align8 := (n + 7) & (^7) 30 | dst[0] = 0xff 31 | dst[1] = uint8(align8/8 - 1) 32 | 33 | // 修复超界: n超过了src的cap 34 | if n > len(src) { 35 | n = len(src) 36 | } 37 | 38 | copy(dst[2:], src[:n]) 39 | for i := n; i < align8; i++ { 40 | dst[2+i] = 0 41 | } 42 | } 43 | 44 | func Pack(src []byte) []byte { 45 | // the worst-case space overhead of packing is 2 bytes per 16 bytes of input 46 | maxsz := len(src) + ((len(src)+2047)/2048)*100 47 | packed := make([]byte, maxsz, maxsz) 48 | offset := 0 49 | 50 | var ffS []byte 51 | var ffN, ffP int 52 | for len(src) > 0 { 53 | var input []byte 54 | if len(src) >= 8 { 55 | input = src[:8] 56 | } else { 57 | input = src 58 | } 59 | used := packSegment(packed[offset:], input, ffN) 60 | if used == 10 { 61 | ffS = src 62 | ffP = offset 63 | ffN = 1 64 | } else if used == 8 && ffN > 0 { 65 | ffN += 1 66 | if ffN == 256 { 67 | writeFF(packed[ffP:], ffS, ffN*8) 68 | ffN = 0 69 | } 70 | } else { 71 | if ffN > 0 { 72 | writeFF(packed[ffP:], ffS, ffN*8) 73 | ffN = 0 74 | } 75 | } 76 | offset += used 77 | if len(src) <= 8 { 78 | break 79 | } 80 | src = src[8:] 81 | } 82 | 83 | if ffN > 0 { 84 | writeFF(packed[ffP:], ffS, len(ffS)) 85 | } 86 | return packed[:offset] 87 | } 88 | 89 | func Unpack(src []byte) ([]byte, error) { 90 | unpacked := make([]byte, 0, len(src)) 91 | buf := make([]byte, 8) 92 | for len(src) > 0 { 93 | sz := len(src) 94 | used := 0 95 | header := src[0] 96 | if header == 0xff { 97 | if sz < 2 { 98 | return nil, ErrUnpack 99 | } 100 | used = 2 + (int(src[1])+1)*8 101 | if sz < used { 102 | return nil, ErrUnpack 103 | } 104 | unpacked = Append(unpacked, src[2:used]) 105 | } else { 106 | notzero := 0 107 | for i := 0; i < 8; i++ { 108 | if header%2 == 1 { 109 | notzero++ 110 | if sz < notzero+1 { 111 | return nil, ErrUnpack 112 | } 113 | buf[i] = src[notzero] 114 | } else { 115 | buf[i] = 0 116 | } 117 | header = header >> 1 118 | } 119 | used = notzero + 1 120 | unpacked = Append(unpacked, buf) 121 | } 122 | src = src[used:] 123 | } 124 | return unpacked, nil 125 | } 126 | -------------------------------------------------------------------------------- /sprotogen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/davyxu/gosproto/meta" 9 | ) 10 | 11 | var paramGoOut = flag.String("go_out", "", "golang output filename") 12 | var paramLuaOut = flag.String("lua_out", "", "lua output filename") 13 | var paramEmmyLuaOut = flag.String("emmylua_out", "", "emmy lua output filename") 14 | var paramCSOut = flag.String("cs_out", "", "csharp output filename") 15 | var paramSprotoOut = flag.String("sproto_out", "", "standard sproto output filename") 16 | var paramPackage = flag.String("package", "", "package name in go files") 17 | var paramCellnetReg = flag.Bool("cellnet_reg", false, "for type go, generate sproto auto register entry for github.com/davyxu/cellnet") 18 | 19 | //var paramForceAutoTag = flag.Bool("forceatag", false, "no ouput field tag in sp mode") 20 | var paramCSClassAttr = flag.String("cs_classattr", "", "add given string to class header as attribute in c sharp file") 21 | var paramCSFieldAttr = flag.String("cs_fieldattr", "", "add given string to class private field as attribute in c sharp file") 22 | var paramVersion = flag.Bool("version", false, "Show version") 23 | 24 | var paramEnumValueGroup = flag.Bool("enumvalgroup", false, "enum value into group") 25 | 26 | func mergeSchema(filelist []string) *meta.FileDescriptorSet { 27 | 28 | if len(filelist) == 0 { 29 | fmt.Println("require sproto file") 30 | os.Exit(1) 31 | } 32 | 33 | fileSet := meta.NewFileDescriptorSet() 34 | errorFileName, err := meta.ParseFileList(fileSet, filelist) 35 | if err != nil { 36 | fmt.Println(errorFileName, err.Error()) 37 | os.Exit(1) 38 | } 39 | 40 | return fileSet 41 | } 42 | 43 | const Version = "0.1.0" 44 | 45 | func genFile(fileset *meta.FileDescriptorSet, generator func(*fileModel, string), filename string) { 46 | 47 | fm := &fileModel{ 48 | FileDescriptorSet: fileset, 49 | PackageName: *paramPackage, 50 | CellnetReg: *paramCellnetReg, 51 | CSClassAttr: *paramCSClassAttr, 52 | CSFieldAttr: *paramCSFieldAttr, 53 | EnumValueGroup: *paramEnumValueGroup, 54 | } 55 | 56 | if *paramEnumValueGroup { 57 | enumValueGroup(fm) 58 | } 59 | 60 | generator(fm, filename) 61 | } 62 | 63 | func main() { 64 | 65 | flag.Parse() 66 | 67 | // 版本 68 | if *paramVersion { 69 | fmt.Println(Version) 70 | return 71 | } 72 | 73 | fileset := mergeSchema(flag.Args()) 74 | 75 | if *paramGoOut != "" { 76 | 77 | genFile(fileset, gen_go, *paramGoOut) 78 | } 79 | 80 | if *paramLuaOut != "" { 81 | genFile(fileset, gen_lua, *paramLuaOut) 82 | } 83 | 84 | if *paramEmmyLuaOut != "" { 85 | genFile(fileset, gen_emmylua, *paramEmmyLuaOut) 86 | } 87 | 88 | if *paramCSOut != "" { 89 | genFile(fileset, gen_csharp, *paramCSOut) 90 | } 91 | 92 | if *paramSprotoOut != "" { 93 | genFile(fileset, gen_sproto, *paramSprotoOut) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /example/addressbook_test.go: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import ( 4 | "math" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/davyxu/gosproto" 9 | ) 10 | 11 | var abData []byte = []byte{ 12 | 1, 0, 0, 0, 122, 0, 0, 0, 13 | 68, 0, 0, 0, 4, 0, 0, 14 | 0, 34, 78, 1, 0, 0, 0, 15 | 5, 0, 0, 0, 65, 108, 105, 16 | 99, 101, 45, 0, 0, 0, 19, 17 | 0, 0, 0, 2, 0, 0, 0, 18 | 4, 0, 9, 0, 0, 0, 49, 19 | 50, 51, 52, 53, 54, 55, 56, 20 | 57, 18, 0, 0, 0, 2, 0, 21 | 0, 0, 6, 0, 8, 0, 0, 22 | 0, 56, 55, 54, 53, 52, 51, 23 | 50, 49, 46, 0, 0, 0, 4, 24 | 0, 0, 0, 66, 156, 1, 0, 25 | 0, 0, 3, 0, 0, 0, 66, 26 | 111, 98, 25, 0, 0, 0, 21, 27 | 0, 0, 0, 2, 0, 0, 0, 28 | 8, 0, 11, 0, 0, 0, 48, 29 | 49, 50, 51, 52, 53, 54, 55, 30 | 56, 57, 48, 31 | } 32 | 33 | func TestMyProfile(t *testing.T) { 34 | 35 | input := &MyData{ 36 | Name: "genji", 37 | Type: MyCar_Pig, 38 | Uint32: math.MaxUint32, 39 | Int64: math.MaxInt64, 40 | Uint64: math.MaxUint64, 41 | Stream: []byte{1, 2, 3, 4}, 42 | } 43 | 44 | input.SetFloat32(3.14159265) 45 | input.SetFloat64(3.14159265) 46 | 47 | var my MyData 48 | 49 | encodeDecodeCompare(t, input, &my) 50 | 51 | t.Log(input.Type.String(), input.Float32(), input.Float64()) 52 | 53 | assert(t, input.Name == "genji") 54 | assert(t, input.Type == MyCar_Pig) 55 | assert(t, input.Uint32 == math.MaxUint32) 56 | assert(t, input.Int64 == math.MaxInt64) 57 | assert(t, input.Uint64 == math.MaxUint64) 58 | 59 | t.Log(input.String()) 60 | } 61 | 62 | func assert(t *testing.T, condition bool) { 63 | if !condition { 64 | t.FailNow() 65 | } 66 | } 67 | 68 | func TestAddressBook(t *testing.T) { 69 | 70 | for _, tp := range SProtoStructs { 71 | t.Log(tp.Name()) 72 | } 73 | 74 | input := &AddressBook{ 75 | Person: []*Person{ 76 | { 77 | Name: "Alice", 78 | Id: int32(10000), 79 | Phone: []*PhoneNumber{ 80 | { 81 | Number: "123456789", 82 | Type: 1, 83 | }, 84 | { 85 | Number: "87654321", 86 | Type: 2, 87 | }, 88 | }, 89 | }, 90 | { 91 | Name: "Bob", 92 | Id: int32(20000), 93 | Phone: []*PhoneNumber{ 94 | { 95 | Number: "01234567890", 96 | Type: int32(3), 97 | }, 98 | }, 99 | }, 100 | }, 101 | } 102 | 103 | data := encodeDecodeCompare(t, input, new(AddressBook)) 104 | 105 | if !reflect.DeepEqual(abData, data) { 106 | t.FailNow() 107 | } 108 | } 109 | 110 | func encodeDecodeCompare(t *testing.T, input, sample interface{}) []byte { 111 | data, err := sproto.Encode(input) 112 | //t.Log(data) 113 | 114 | if err != nil { 115 | t.Log(err) 116 | t.FailNow() 117 | } 118 | 119 | _, err = sproto.Decode(data, sample) 120 | 121 | if err != nil { 122 | t.Log(err) 123 | t.FailNow() 124 | } 125 | 126 | if !reflect.DeepEqual(sample, input) { 127 | t.Log("deep equal failed", input) 128 | t.FailNow() 129 | } 130 | 131 | return data 132 | } 133 | -------------------------------------------------------------------------------- /sprotogen/model.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/davyxu/gosproto/meta" 7 | ) 8 | 9 | type fieldModel struct { 10 | *meta.FieldDescriptor 11 | FieldIndex int 12 | 13 | st *structModel 14 | } 15 | 16 | func (self *fieldModel) UpperName() string { 17 | return strings.ToUpper(string(self.Name[0])) + self.Name[1:] 18 | } 19 | 20 | type structModel struct { 21 | *meta.Descriptor 22 | 23 | StFields []fieldModel 24 | 25 | f *fileModel 26 | } 27 | 28 | 29 | func (self *structModel) IsResultEnum() bool { 30 | return self.IsEnum() && strings.HasSuffix(self.Name, "Result") 31 | } 32 | 33 | func (self *structModel) IsEnum() bool { 34 | return self.Type == meta.DescriptorType_Enum 35 | } 36 | 37 | func (self *structModel) IsStruct() bool { 38 | return self.Type == meta.DescriptorType_Struct 39 | } 40 | 41 | func (self *structModel) MsgID() uint32 { 42 | return StringHash(self.MsgFullName()) 43 | } 44 | 45 | func (self *structModel) MsgFullName() string { 46 | return self.f.PackageName + "." + self.Name 47 | } 48 | func (self *structModel) FieldCount() int { 49 | return len(self.StFields) 50 | } 51 | 52 | type fileModel struct { 53 | *meta.FileDescriptorSet 54 | 55 | Structs []*structModel 56 | 57 | Enums []*structModel 58 | 59 | Objects []*structModel 60 | 61 | PackageName string 62 | 63 | CellnetReg bool 64 | 65 | forceAutoTag bool 66 | 67 | CSClassAttr string 68 | 69 | CSFieldAttr string 70 | 71 | EnumValueGroup bool 72 | } 73 | 74 | func (self *fileModel) Len() int { 75 | return len(self.Structs) 76 | } 77 | 78 | func (self *fileModel) Swap(i, j int) { 79 | self.Structs[i], self.Structs[j] = self.Structs[j], self.Structs[i] 80 | } 81 | 82 | func (self *fileModel) Less(i, j int) bool { 83 | 84 | a := self.Structs[i] 85 | b := self.Structs[j] 86 | 87 | return a.Name < b.Name 88 | } 89 | 90 | func addStruct(fm *fileModel, fileD *meta.FileDescriptor, srcName string) { 91 | 92 | for _, st := range fileD.Objects { 93 | 94 | // 过滤, 只输出某个来源 95 | if srcName != "" && st.SrcName != srcName { 96 | continue 97 | } 98 | 99 | stModel := &structModel{ 100 | Descriptor: st, 101 | } 102 | 103 | for index, fd := range st.Fields { 104 | 105 | fdModel := fieldModel{ 106 | FieldDescriptor: fd, 107 | FieldIndex: index, 108 | st: stModel, 109 | } 110 | 111 | stModel.StFields = append(stModel.StFields, fdModel) 112 | 113 | } 114 | 115 | stModel.f = fm 116 | 117 | fm.Objects = append(fm.Objects, stModel) 118 | 119 | switch stModel.Type { 120 | case meta.DescriptorType_Enum: 121 | fm.Enums = append(fm.Enums, stModel) 122 | case meta.DescriptorType_Struct: 123 | fm.Structs = append(fm.Structs, stModel) 124 | } 125 | 126 | } 127 | } 128 | 129 | func addData(fm *fileModel, matchTag string) { 130 | 131 | for _, file := range fm.FileDescriptorSet.Files { 132 | 133 | if file.MatchTag(matchTag) { 134 | addStruct(fm, file, "") 135 | } 136 | 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /example/addressbook_gen.go: -------------------------------------------------------------------------------- 1 | // Generated by github.com/davyxu/gosproto/sprotogen 2 | // DO NOT EDIT! 3 | 4 | package example 5 | 6 | import ( 7 | "reflect" 8 | "github.com/davyxu/gosproto" 9 | "github.com/davyxu/cellnet/codec/sproto" 10 | "fmt" 11 | ) 12 | 13 | type MyCar int32 14 | 15 | const ( 16 | MyCar_Monkey MyCar = 0 17 | MyCar_Monk MyCar = 1 18 | MyCar_Pig MyCar = 2 19 | ) 20 | 21 | var ( 22 | MyCarMapperValueByName = map[string]int32{ 23 | "Monkey": 0, 24 | "Monk": 1, 25 | "Pig": 2, 26 | } 27 | 28 | MyCarMapperNameByValue = map[int32]string{ 29 | 0: "Monkey", 30 | 1: "Monk", 31 | 2: "Pig", 32 | } 33 | ) 34 | 35 | func (self MyCar) String() string { 36 | return sproto.EnumName(MyCarMapperNameByValue, int32(self)) 37 | } 38 | 39 | type PhoneNumber struct { 40 | Number string `sproto:"string,0,name=Number"` 41 | 42 | Type int32 `sproto:"integer,1,name=Type"` 43 | } 44 | 45 | func (self *PhoneNumber) String() string { return fmt.Sprintf("%+v", *self) } 46 | 47 | type Person struct { 48 | Name string `sproto:"string,0,name=Name"` 49 | 50 | Id int32 `sproto:"integer,1,name=Id"` 51 | 52 | Email string `sproto:"string,2,name=Email"` 53 | 54 | Phone []*PhoneNumber `sproto:"struct,3,array,name=Phone"` 55 | } 56 | 57 | func (self *Person) String() string { return fmt.Sprintf("%+v", *self) } 58 | 59 | type AddressBook struct { 60 | Person []*Person `sproto:"struct,0,array,name=Person"` 61 | } 62 | 63 | func (self *AddressBook) String() string { return fmt.Sprintf("%+v", *self) } 64 | 65 | type MyData struct { 66 | Name string `sproto:"string,0,name=Name"` 67 | 68 | Type MyCar `sproto:"integer,1,name=Type"` 69 | 70 | Int32 int32 `sproto:"integer,2,name=Int32"` 71 | 72 | Uint32 uint32 `sproto:"integer,3,name=Uint32"` 73 | 74 | Int64 int64 `sproto:"integer,4,name=Int64"` 75 | 76 | Uint64 uint64 `sproto:"integer,5,name=Uint64"` 77 | 78 | Bool bool `sproto:"boolean,6,name=Bool"` 79 | 80 | Extend_Float32 int32 `sproto:"integer,7,name=Extend_Float32"` 81 | 82 | Extend_Float64 int64 `sproto:"integer,8,name=Extend_Float64"` 83 | 84 | Stream []byte `sproto:"string,9,name=Stream"` 85 | } 86 | 87 | func (self *MyData) String() string { return fmt.Sprintf("%+v", *self) } 88 | 89 | func (self *MyData) Float32() float32 { 90 | return float32(self.Extend_Float32) * 0.001000 91 | } 92 | func (self *MyData) SetFloat32(v float32) { 93 | self.Extend_Float32 = int32(v * 1000) 94 | } 95 | 96 | func (self *MyData) Float64() float64 { 97 | return float64(self.Extend_Float64) * 0.001000 98 | } 99 | func (self *MyData) SetFloat64(v float64) { 100 | self.Extend_Float64 = int64(v * 1000) 101 | } 102 | 103 | type MyProfile struct { 104 | NameField *MyData `sproto:"struct,0,name=NameField"` 105 | 106 | NameArray []*MyData `sproto:"struct,1,array,name=NameArray"` 107 | 108 | NameMap []*MyData `sproto:"struct,2,array,name=NameMap"` 109 | } 110 | 111 | func (self *MyProfile) String() string { return fmt.Sprintf("%+v", *self) } 112 | 113 | var SProtoStructs = []reflect.Type{ 114 | 115 | reflect.TypeOf((*PhoneNumber)(nil)).Elem(), // 4271979557 116 | reflect.TypeOf((*Person)(nil)).Elem(), // 1498745430 117 | reflect.TypeOf((*AddressBook)(nil)).Elem(), // 2618161298 118 | reflect.TypeOf((*MyData)(nil)).Elem(), // 2244887298 119 | reflect.TypeOf((*MyProfile)(nil)).Elem(), // 438153711 120 | } 121 | 122 | var SProtoEnumValue = map[string]map[int32]string{ 123 | "MyCar": MyCarMapperNameByValue, 124 | } 125 | 126 | func init() { 127 | sprotocodec.AutoRegisterMessageMeta(SProtoStructs) 128 | } 129 | -------------------------------------------------------------------------------- /meta/field.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | type FieldDescriptor struct { 9 | *CommentGroup 10 | Name string 11 | Type FieldType 12 | Tag int 13 | AutoTag int 14 | Repeatd bool 15 | MainIndex *FieldDescriptor 16 | Complex *Descriptor 17 | 18 | Struct *Descriptor 19 | } 20 | 21 | func (self *FieldDescriptor) IsExtendType() bool { 22 | 23 | switch self.Type { 24 | case FieldType_Float32, 25 | FieldType_Float64: 26 | return true 27 | } 28 | 29 | return false 30 | } 31 | 32 | func (self *FieldDescriptor) ExtendTypePrecision() int { 33 | 34 | var precision = 1000 35 | if precisionStr, ok := self.CommentGroup.MatchTag("ExtendPrecision"); ok { 36 | if pcs, err := strconv.ParseInt(precisionStr, 10, 64); err == nil { 37 | precision = int(pcs) 38 | } 39 | } 40 | 41 | switch self.Type { 42 | case FieldType_Float32, 43 | FieldType_Float64: 44 | return precision 45 | } 46 | 47 | return 0 48 | } 49 | 50 | func (self *FieldDescriptor) TagNumber() int { 51 | 52 | var tag int 53 | 54 | if self.AutoTag == -1 { 55 | tag = self.Tag 56 | } else { 57 | tag = self.AutoTag 58 | } 59 | 60 | if tag != 0 { 61 | return self.Struct.TagBase + tag 62 | } 63 | 64 | return tag 65 | } 66 | 67 | func (self *FieldDescriptor) TypeString() string { 68 | return self.typeStr(false) 69 | } 70 | 71 | func (self *FieldDescriptor) CompatibleTypeString() string { 72 | return self.typeStr(true) 73 | } 74 | 75 | func (self *FieldDescriptor) typeStr(compatible bool) (ret string) { 76 | 77 | if self.Repeatd { 78 | if compatible { 79 | ret = "*" 80 | } else { 81 | ret = "[]" 82 | } 83 | 84 | } 85 | 86 | if compatible { 87 | ret += self.CompatibleTypeName() 88 | } else { 89 | ret += self.TypeName() 90 | } 91 | 92 | if self.MainIndex != nil { 93 | ret += fmt.Sprintf("(%s)", self.MainIndex.Name) 94 | } 95 | 96 | return 97 | } 98 | 99 | func (self *FieldDescriptor) String() string { 100 | 101 | return fmt.Sprintf("%s %d : %s", self.Name, self.TagNumber(), self.TypeString()) 102 | } 103 | 104 | func (self *FieldDescriptor) Kind() string { 105 | 106 | return self.Type.String() 107 | } 108 | 109 | func (self *FieldDescriptor) CompatibleTypeName() string { 110 | 111 | switch self.Type { 112 | 113 | case FieldType_Struct: 114 | return self.Complex.Name 115 | case FieldType_Int32, 116 | FieldType_Int64, 117 | FieldType_UInt32, 118 | FieldType_UInt64, 119 | FieldType_Float32, 120 | FieldType_Float64, 121 | FieldType_Enum: 122 | return FieldType_Integer.String() 123 | case FieldType_Bytes: 124 | return FieldType_String.String() 125 | default: 126 | return self.Type.String() 127 | } 128 | 129 | } 130 | 131 | func (self *FieldDescriptor) TypeName() string { 132 | 133 | switch self.Type { 134 | case FieldType_Bool: 135 | return "bool" 136 | 137 | case FieldType_Struct, FieldType_Enum: 138 | return self.Complex.Name 139 | default: 140 | return self.Type.String() 141 | } 142 | 143 | } 144 | 145 | func (self *FieldDescriptor) parseType(name string) (ft FieldType, structType *Descriptor) { 146 | 147 | ft = ParseFieldType(name) 148 | 149 | if ft != FieldType_None { 150 | return ft, nil 151 | } 152 | 153 | if ft, structType = self.Struct.File.FileSet.parseType(name); ft != FieldType_None { 154 | return ft, structType 155 | } 156 | 157 | return FieldType_None, nil 158 | 159 | } 160 | 161 | func newFieldDescriptor(d *Descriptor) *FieldDescriptor { 162 | return &FieldDescriptor{ 163 | CommentGroup: newCommentGroup(), 164 | Struct: d, 165 | AutoTag: -1, 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /meta/parser.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "github.com/davyxu/golexer" 8 | ) 9 | 10 | // 自定义的token id 11 | const ( 12 | Token_EOF = iota 13 | Token_Unknown 14 | Token_LineEnd 15 | Token_Numeral 16 | Token_String 17 | Token_WhiteSpace 18 | Token_Identifier 19 | Token_UnixComment 20 | Token_CStyleComment 21 | Token_Colon // : 22 | Token_ParenL // ( 23 | Token_ParenR // ) 24 | Token_CurlyBraceL // { 25 | Token_CurlyBraceR // } 26 | Token_BracketL // [ 27 | Token_BracketR // ] 28 | Token_Star // * 29 | Token_Dot // . 30 | Token_Enum // Enum 31 | Token_Message // Message 32 | Token_FileTag // fileTag 33 | Token_Assign // = 34 | ) 35 | 36 | type sprotoParser struct { 37 | *golexer.Parser 38 | 39 | commentsByLine map[int]string 40 | } 41 | 42 | func (self *sprotoParser) Expect(id int) golexer.Token { 43 | 44 | if self.Parser.TokenID() != id { 45 | panic(errors.New("Expect " + self.Lexer().MatcherString(id))) 46 | } 47 | 48 | t := self.RawToken() 49 | 50 | self.NextToken() 51 | 52 | return t 53 | } 54 | 55 | func (self *sprotoParser) NextToken() { 56 | 57 | for { 58 | self.Parser.NextToken() 59 | 60 | switch self.TokenID() { 61 | 62 | case Token_UnixComment, 63 | Token_CStyleComment: 64 | self.commentsByLine[self.RawToken().Line()] = self.TokenValue() 65 | default: 66 | return 67 | } 68 | } 69 | 70 | } 71 | 72 | func (self *sprotoParser) CommentGroupByLine(line int) *CommentGroup { 73 | 74 | cg := newCommentGroup() 75 | 76 | if comment, ok := self.commentsByLine[line]; ok { 77 | cg.addLineComment(comment) 78 | cg.Trailing = comment 79 | } 80 | 81 | var buff bytes.Buffer 82 | 83 | start := line - 1 84 | var end int 85 | 86 | for i := line - 1; i >= 1; i-- { 87 | 88 | if _, ok := self.commentsByLine[i]; !ok { 89 | end = i 90 | break 91 | } 92 | 93 | } 94 | 95 | for i := end; i <= start; i++ { 96 | 97 | comment, _ := self.commentsByLine[i] 98 | 99 | if buff.Len() > 0 { 100 | buff.WriteString("\n") 101 | } 102 | 103 | cg.addLineComment(comment) 104 | 105 | buff.WriteString(comment) 106 | } 107 | 108 | cg.Leading = buff.String() 109 | 110 | return cg 111 | } 112 | 113 | func newSProtoParser(srcName string) *sprotoParser { 114 | 115 | l := golexer.NewLexer() 116 | 117 | // 匹配顺序从高到低 118 | 119 | l.AddMatcher(golexer.NewNumeralMatcher(Token_Numeral)) 120 | l.AddMatcher(golexer.NewStringMatcher(Token_String)) 121 | 122 | l.AddIgnoreMatcher(golexer.NewWhiteSpaceMatcher(Token_WhiteSpace)) 123 | l.AddIgnoreMatcher(golexer.NewLineEndMatcher(Token_LineEnd)) 124 | l.AddMatcher(golexer.NewUnixStyleCommentMatcher(Token_UnixComment)) 125 | l.AddMatcher(golexer.NewCStyleCommentMatcher(Token_CStyleComment)) 126 | 127 | l.AddMatcher(golexer.NewSignMatcher(Token_CurlyBraceL, "{")) 128 | l.AddMatcher(golexer.NewSignMatcher(Token_CurlyBraceR, "}")) 129 | l.AddMatcher(golexer.NewSignMatcher(Token_ParenL, "(")) 130 | l.AddMatcher(golexer.NewSignMatcher(Token_ParenR, ")")) 131 | l.AddMatcher(golexer.NewSignMatcher(Token_BracketL, "[")) 132 | l.AddMatcher(golexer.NewSignMatcher(Token_BracketR, "]")) 133 | l.AddMatcher(golexer.NewSignMatcher(Token_Star, "*")) 134 | l.AddMatcher(golexer.NewSignMatcher(Token_Dot, ".")) 135 | l.AddMatcher(golexer.NewSignMatcher(Token_Assign, "=")) 136 | l.AddMatcher(golexer.NewSignMatcher(Token_Colon, ":")) 137 | l.AddMatcher(golexer.NewKeywordMatcher(Token_Enum, "enum")) 138 | l.AddMatcher(golexer.NewKeywordMatcher(Token_Message, "message")) 139 | l.AddMatcher(golexer.NewKeywordMatcher(Token_FileTag, "filetag")) 140 | 141 | l.AddMatcher(golexer.NewIdentifierMatcher(Token_Identifier)) 142 | 143 | l.AddMatcher(golexer.NewUnknownMatcher(Token_Unknown)) 144 | 145 | return &sprotoParser{ 146 | Parser: golexer.NewParser(l, srcName), 147 | commentsByLine: make(map[int]string), 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gosproto 2 | 基于[云风的sproto](https://github.com/cloudwu/sproto)二进制标准上的描述文件及代码生成工具 3 | 4 | 5 | 6 | English Doc see below: 7 | https://github.com/davyxu/gosproto/blob/master/README_en.md 8 | 9 | # 代码基于 10 | 11 | https://github.com/xjdrew/gosproto 12 | 13 | 14 | # 有Google Protobuf, 为什么还要选择sproto? 15 | Google Protobuf是一个用途非常广泛的数据传输格式及标准,几乎支持所有地球上的编程语言. 16 | Protobuf 3.0版本的很多设计也是非常优秀的.但是和C++多年发展类似的,Protobuf承载了太多的历史包袱. 17 | 除了格式复杂,到描述文件,插件体系的设计都不能满足一些性能领域或者特殊领域的特别要求. 18 | 19 | 使用Unity配合Lua制作游戏的团队中,很多也使用Protobuf.但是Protobuf的二进制复杂格式在Lua层上造成了很大的GC和性能问题. 20 | 云风自己的pbc也不能完全与官方Protobuf二进制兼容. 21 | 22 | 同为云风出品的sproto格式设计精巧.以简单性能为第一需求,天生就为lua做特殊优化,无需担心性能问题. 23 | 本库(gosproto)以及配套的代码生成工具(sprotogen)也正是基于云风的sproto二进制层基础上构建的一整套工具链. 24 | 25 | 26 | # 特性 27 | 28 | * 兼容云风版sproto格式,并扩充的格式,比Protobuf更方便, 更快 29 | 30 | * 代码生成输出支持go,c#,lua以及云风原版sproto 31 | 32 | # 描述文件格式(*.sp) 33 | 34 | 下面是sprotogen支持的推荐格式 35 | ``` 36 | 37 | enum Vocation { 38 | Monkey 39 | Monk 40 | Pig 41 | } 42 | 43 | message PhoneNumber { 44 | 45 | number string 46 | 47 | type int32 48 | } 49 | 50 | 51 | message Person { 52 | 53 | name string 54 | 55 | id int32 56 | 57 | email string 58 | 59 | phone PhoneNumber 60 | 61 | voc Vocation 62 | } 63 | 64 | message AddressBook { 65 | 66 | person []Person 67 | } 68 | 69 | 70 | ``` 71 | ## 特性 72 | 73 | * 自动生成tag序列号(base0,sproto规范),也可以手动指定 74 | 75 | * 自动生成枚举序号(base0),也可以手动指定 76 | 77 | * 类go结构体字段命名方式 78 | 79 | * 比Protobuf更方便的导出注释内容做协议扩充 80 | 81 | ## 兼容云风的sproto描述格式(*.sproto) 82 | 83 | 解析器可以同时兼容两种格式书写,可以在任何地方混合书写. 更推荐使用sp格式,有更强的扩展和兼容性 84 | 85 | ``` 86 | .PhoneNumber { 87 | 88 | number 0 : string 89 | 90 | type 1 : integer 91 | 92 | } 93 | 94 | .Person { 95 | 96 | name 0 : string 97 | 98 | id 1 : integer 99 | 100 | email 2 : string 101 | 102 | phone 3 : *PhoneNumber 103 | 104 | } 105 | 106 | .AddressBook { 107 | 108 | person 0 : *Person 109 | 110 | } 111 | ``` 112 | 113 | 114 | # Go版的使用方法 115 | ```golang 116 | input := &AddressBook{ 117 | Person: []*Person{ 118 | &Person{ 119 | Name: "Alice", 120 | Id: 10000, 121 | Phone: []*PhoneNumber{ 122 | &PhoneNumber{ 123 | Number: "123456789", 124 | Type: 1, 125 | }, 126 | &PhoneNumber{ 127 | Number: "87654321", 128 | Type: 2, 129 | }, 130 | }, 131 | }, 132 | &Person{ 133 | Name: "Bob", 134 | Id: 20000, 135 | Phone: []*PhoneNumber{ 136 | &PhoneNumber{ 137 | Number: "01234567890", 138 | Type: 3, 139 | }, 140 | }, 141 | }, 142 | }, 143 | } 144 | 145 | data, err := sproto.Encode(input) 146 | 147 | if err != nil { 148 | t.Log(err) 149 | } 150 | 151 | var sample AddressBook 152 | _, err = sproto.Decode(data, &sample) 153 | 154 | if err != nil { 155 | t.Log(err) 156 | } 157 | 158 | ``` 159 | 160 | # 支持类型 161 | 162 | * int32: 32位整形 163 | * int64: 64位整形 164 | * uint32: 无符号32位整形 165 | * uint64: 无符号64位整形 166 | * string: 字符串 167 | * float32: 单精度浮点数(默认精度0.001) 168 | * float64: 双精度浮点数(默认精度0.001) 169 | * bytes: 二进制数据 170 | * enum: int32封装 171 | * bool: 布尔 172 | * message 结构体 173 | 174 | 所有类型前添加[]表示数组 175 | 176 | # 编译 177 | 178 | ``` 179 | go get -u -v github.com/davyxu/gosproto/sprotogen 180 | ``` 181 | 182 | # 下载 183 | 184 | https://github.com/davyxu/gosproto/releases 185 | 186 | # sprotogen命令行参数 187 | 188 | * go_out 189 | 输出Go源码 190 | 191 | * lua_out 192 | 输出lua源码,兼容云风版本 193 | 194 | * cs_out 195 | 输出C#源码, 配套基类库请参考https://github.com/davyxu/sproto-Csharp 196 | 197 | * sproto_out 198 | 输出云风版sproto的描述文件 199 | 200 | * package 201 | 包或者命名空间名 202 | 203 | * cellnet_reg 204 | 在Go的生成代码中添加cellnet自动注册入口 205 | 206 | # 使用方法 207 | 208 | ``` 209 | # 输出go源码 210 | sprotogen --go_out=addressbook.go --package=example addressbook.sp 211 | 212 | # 生成C#源码 213 | sprotogen --cs_out=addressbook.cs --package=example addressbook.sp 214 | 215 | ``` 216 | 217 | # 例子 218 | 219 | https://github.com/davyxu/gosproto/tree/master/example 220 | 221 | 222 | 223 | # 可选工具:Protobuf描述格式转sproto描述格式 224 | 225 | https://github.com/davyxu/gosproto/tree/master/pb2sproto 226 | 227 | ## 特性 228 | 保留所有protobuf的注释 229 | 230 | ## 安装方法 231 | 232 | ``` 233 | go get -u -v github.com/davyxu/gosproto/pb2sproto 234 | ``` 235 | 第三方库依赖: github.com/davyxu/pbmeta 236 | 237 | ## 使用方法 238 | 239 | ``` 240 | # 使用Protobuf编译器:protoc配合插件github.com/davyxu/pbmeta/protoc-gen-meta 生成pb的上下文信息 241 | # 参见: github.com/davyxu/gosproto/pb2sproto/Make.bat 242 | 243 | # 根据上下文信息导出sproto 244 | pb2sproto --pbmeta=meta.pb --outdir=. 245 | 246 | ``` 247 | 248 | # 备注 249 | 250 | 感觉不错请star, 谢谢! 251 | 252 | 知乎: [http://www.zhihu.com/people/sunicdavy](http://www.zhihu.com/people/sunicdavy) 253 | 254 | 提交bug及特性: https://github.com/davyxu/gosproto/issues 255 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # gosproto 2 | [sproto](https://github.com/cloudwu/sproto) golang implement 3 | 4 | # Based on code 5 | 6 | https://github.com/xjdrew/gosproto 7 | 8 | # Features 9 | Similar to [golang protobuf](https://github.com/golang/protobuf) version 3 10 | 11 | No need use sproto.Int32( ), sproto.String() wrapper to generate point field in order to implement optional field 12 | 13 | Optional will be auto processed by comparing incoming value and type default value 14 | 15 | bool default is false 16 | 17 | integer default is 0 18 | 19 | string default is "" 20 | 21 | Fields equals to its default value will be skipped encoding and decoding automatically' 22 | 23 | USE SPROTO LIKE NORMAL STRUCT! 24 | 25 | # Type map 26 | sproto type | golang type 27 | ---------------- | ------------------------------------------------- 28 | string | \string, []byte 29 | integer | \int8, \uint8, \int16, \uint16, \int32, \uint32, \int64, \uint64, \int, \uint 30 | boolean | \bool 31 | object | \struct 32 | *string | []string 33 | *integer | []int8, []uint8, []int16, []uint16, []int32, []uint32, []int64, []uint64, []int, []uint 34 | *boolean | []bool 35 | *struct | []\*struct 36 | *struct(index) | []\*struct 37 | 38 | # Schema parser and meta info 39 | 40 | https://github.com/davyxu/gosproto/tree/master/meta 41 | 42 | # Code generator 43 | 44 | https://github.com/davyxu/gosproto/tree/master/sprotogen 45 | 46 | ## Install 47 | 48 | ``` 49 | go get -u -v github.com/davyxu/gosproto/sprotogen 50 | ``` 51 | 52 | ## Usage 53 | 54 | ``` 55 | # Generate go source file by sproto 56 | sprotogen --type=go --out=addressbook.go --gopackage=example addressbook.sp 57 | 58 | # Generate c# source file by sproto 59 | sprotogen --type=cs --out=addressbook.cs --gopackage=example addressbook.sp 60 | 61 | # Convert to standard sproto file 62 | sprotogen --type=sproto --out=addressbook.sproto addressbook.sp 63 | 64 | ``` 65 | 66 | # Protobuf Schema --> sproto Schema convertor 67 | 68 | https://github.com/davyxu/gosproto/tree/master/pb2sproto 69 | 70 | ## Features 71 | Keep all message leading comments and field trailing comments 72 | 73 | ## Install 74 | 75 | ``` 76 | go get -u -v github.com/davyxu/gosproto/pb2sproto 77 | ``` 78 | Requires: github.com/davyxu/pbmeta 79 | 80 | ## Usage 81 | 82 | ``` 83 | # Use protoc and github.com/davyxu/pbmeta/protoc-gen-meta to generate protobuf meta info file(contains comments) 84 | # see github.com/davyxu/gosproto/pb2sproto/Make.bat 85 | 86 | # Use meta info to generate sproto file 87 | pb2sproto --pbmeta=meta.pb --outdir=. 88 | 89 | ``` 90 | 91 | ## Remark 92 | Due to sproto not support float field type, all float double format will convert to int32 type 93 | 94 | # Example 95 | 96 | https://github.com/davyxu/gosproto/tree/master/example 97 | 98 | ```golang 99 | input := &AddressBook{ 100 | Person: []*Person{ 101 | &Person{ 102 | Name: "Alice", 103 | Id: 10000, 104 | Phone: []*PhoneNumber{ 105 | &PhoneNumber{ 106 | Number: "123456789", 107 | Type: 1, 108 | }, 109 | &PhoneNumber{ 110 | Number: "87654321", 111 | Type: 2, 112 | }, 113 | }, 114 | }, 115 | &Person{ 116 | Name: "Bob", 117 | Id: 20000, 118 | Phone: []*PhoneNumber{ 119 | &PhoneNumber{ 120 | Number: "01234567890", 121 | Type: 3, 122 | }, 123 | }, 124 | }, 125 | }, 126 | } 127 | 128 | data, err := sproto.Encode(input) 129 | 130 | if err != nil { 131 | t.Log(err) 132 | } 133 | 134 | var sample AddressBook 135 | _, err = sproto.Decode(data, &sample) 136 | 137 | if err != nil { 138 | t.Log(err) 139 | } 140 | 141 | ``` 142 | 143 | 144 | # Test 145 | 146 | ```golang 147 | go test github.com/davyxu/gosproto 148 | ``` 149 | 150 | 151 | # Benchmark 152 | 153 | 154 | ``` 155 | $ go test -bench . github.com/davyxu/gosproto 156 | BenchmarkEncode-8 500000 2498 ns/op 157 | BenchmarkDecode-8 500000 3134 ns/op 158 | BenchmarkEncodePacked-8 500000 2894 ns/op 159 | BenchmarkDecodePacked-8 500000 3480 ns/op 160 | PASS 161 | ok github.com/davyxu/gosproto 6.162s 162 | ``` 163 | 164 | * xjdrew/gosproto Version 165 | 166 | ``` 167 | $ go test -bench . github.com/xjdrew/gosproto 168 | BenchmarkEncode-8 1000000 1931 ns/op 169 | BenchmarkDecode-8 500000 3498 ns/op 170 | BenchmarkEncodePacked-8 500000 2476 ns/op 171 | BenchmarkDecodePacked-8 500000 3896 ns/op 172 | PASS 173 | ok github.com/xjdrew/gosproto 7.024s 174 | ``` 175 | 176 | 177 | # Feedback 178 | 179 | Star me if you like or use, thanks 180 | 181 | blog(Chinese): [http://www.cppblog.com/sunicdavy](http://www.cppblog.com/sunicdavy) 182 | 183 | zhihu(Chinese): [http://www.zhihu.com/people/sunicdavy](http://www.zhihu.com/people/sunicdavy) 184 | 185 | -------------------------------------------------------------------------------- /pb2sproto/gen_proto.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path" 10 | "strings" 11 | "text/template" 12 | 13 | "github.com/davyxu/pbmeta" 14 | pbprotos "github.com/davyxu/pbmeta/proto" 15 | ) 16 | 17 | const codeTemplate = `# Generated by github.com/davyxu/gosproto/pb2sproto 18 | # Source: {{.FileName}} 19 | 20 | {{range .Enums}} 21 | {{.Comment}} 22 | enum {{.Name}} { 23 | {{range .Fields}} 24 | {{.Name}} {{.Tag}} 25 | {{end}} 26 | } 27 | {{end}} 28 | 29 | {{range .Structs}} 30 | {{.Comment}} 31 | .{{.Name}} { 32 | {{range .Fields}} 33 | {{.Name}} {{.Tag}} : {{.TypeString}} {{.Comment}} 34 | {{end}} 35 | } 36 | {{end}} 37 | ` 38 | 39 | type spEnumFieldModel struct { 40 | *pbmeta.EnumValueDescriptor 41 | } 42 | 43 | func (self *spEnumFieldModel) Tag() int32 { 44 | return self.Define.GetNumber() 45 | } 46 | 47 | type spFieldModel struct { 48 | *pbmeta.FieldDescriptor 49 | } 50 | 51 | func (self *spFieldModel) Tag() int32 { 52 | return self.Define.GetNumber() 53 | } 54 | 55 | func (self *spFieldModel) TypeString() (ret string) { 56 | 57 | if self.IsRepeated() { 58 | ret = "*" 59 | } 60 | 61 | switch self.Type() { 62 | case pbprotos.FieldDescriptorProto_TYPE_INT64: 63 | ret += "int64" 64 | case pbprotos.FieldDescriptorProto_TYPE_UINT64: 65 | ret += "uint64" 66 | case pbprotos.FieldDescriptorProto_TYPE_INT32, 67 | pbprotos.FieldDescriptorProto_TYPE_FLOAT, // 浮点数默认转为int32 68 | pbprotos.FieldDescriptorProto_TYPE_DOUBLE: 69 | ret += "int32" 70 | case pbprotos.FieldDescriptorProto_TYPE_UINT32: 71 | ret += "uint32" 72 | case pbprotos.FieldDescriptorProto_TYPE_BOOL: 73 | ret += "boolean" 74 | case pbprotos.FieldDescriptorProto_TYPE_STRING: 75 | ret += "string" 76 | case pbprotos.FieldDescriptorProto_TYPE_MESSAGE: 77 | ret += self.MessageDesc().Name() 78 | case pbprotos.FieldDescriptorProto_TYPE_ENUM: 79 | ret += self.EnumDesc().Name() 80 | } 81 | 82 | return 83 | } 84 | 85 | func addCommentSignAtEachLine(sign, comment string) string { 86 | 87 | if comment == "" { 88 | return "" 89 | } 90 | var out bytes.Buffer 91 | 92 | scanner := bufio.NewScanner(strings.NewReader(comment)) 93 | 94 | var index int 95 | for scanner.Scan() { 96 | 97 | if index > 0 { 98 | out.WriteString("\n") 99 | } 100 | 101 | out.WriteString(sign) 102 | out.WriteString(" ") 103 | out.WriteString(scanner.Text()) 104 | 105 | index++ 106 | } 107 | 108 | return out.String() 109 | } 110 | 111 | func (self *spFieldModel) Comment() string { 112 | 113 | return addCommentSignAtEachLine("#", self.CommentMeta.TrailingComment()) 114 | 115 | } 116 | 117 | type spStructModel struct { 118 | *pbmeta.Descriptor 119 | 120 | Fields []spFieldModel 121 | } 122 | 123 | func (self *spStructModel) Comment() string { 124 | 125 | return addCommentSignAtEachLine("#", self.CommentMeta.LeadingComment()) 126 | } 127 | 128 | type spEnumModel struct { 129 | *pbmeta.EnumDescriptor 130 | 131 | Fields []spEnumFieldModel 132 | } 133 | 134 | func (self *spEnumModel) Comment() string { 135 | 136 | return addCommentSignAtEachLine("#", self.CommentMeta.LeadingComment()) 137 | } 138 | 139 | type spFileModel struct { 140 | *pbmeta.FileDescriptor 141 | 142 | Structs []*spStructModel 143 | 144 | Enums []*spEnumModel 145 | } 146 | 147 | func gen_proto(fileD *pbmeta.FileDescriptor, outputDir string) { 148 | 149 | tpl, err := template.New("pb->sp").Parse(codeTemplate) 150 | if err != nil { 151 | fmt.Println("template error ", err.Error()) 152 | os.Exit(1) 153 | } 154 | 155 | fm := &spFileModel{ 156 | FileDescriptor: fileD, 157 | } 158 | 159 | for structIndex := 0; structIndex < fileD.MessageCount(); structIndex++ { 160 | st := fileD.Message(structIndex) 161 | 162 | stModel := &spStructModel{ 163 | Descriptor: st, 164 | } 165 | 166 | for fieldIndex := 0; fieldIndex < st.FieldCount(); fieldIndex++ { 167 | fd := st.Field(fieldIndex) 168 | 169 | fdModel := spFieldModel{ 170 | FieldDescriptor: fd, 171 | } 172 | 173 | stModel.Fields = append(stModel.Fields, fdModel) 174 | } 175 | 176 | fm.Structs = append(fm.Structs, stModel) 177 | } 178 | 179 | for enumIndex := 0; enumIndex < fileD.EnumCount(); enumIndex++ { 180 | st := fileD.Enum(enumIndex) 181 | 182 | stModel := &spEnumModel{ 183 | EnumDescriptor: st, 184 | } 185 | 186 | for fieldIndex := 0; fieldIndex < st.ValueCount(); fieldIndex++ { 187 | fd := st.Value(fieldIndex) 188 | 189 | fdModel := spEnumFieldModel{ 190 | EnumValueDescriptor: fd, 191 | } 192 | 193 | stModel.Fields = append(stModel.Fields, fdModel) 194 | } 195 | 196 | fm.Enums = append(fm.Enums, stModel) 197 | } 198 | 199 | var bf bytes.Buffer 200 | 201 | err = tpl.Execute(&bf, &fm) 202 | if err != nil { 203 | fmt.Println("template error ", err.Error()) 204 | os.Exit(1) 205 | } 206 | 207 | if err != nil { 208 | fmt.Println("format error ", err.Error()) 209 | } 210 | 211 | final := path.Join(outputDir, changeExt(fileD.FileName(), ".sp")) 212 | 213 | if fileErr := ioutil.WriteFile(final, bf.Bytes(), 666); fileErr != nil { 214 | fmt.Println("write file error ", fileErr.Error()) 215 | os.Exit(1) 216 | } 217 | } 218 | 219 | // newExt = .xxx 220 | func changeExt(name, newExt string) string { 221 | ext := path.Ext(name) 222 | name = name[0 : len(name)-len(ext)] 223 | return name + newExt 224 | } 225 | -------------------------------------------------------------------------------- /sprotogen/gen_go.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/token" 7 | 8 | "github.com/davyxu/gosproto/meta" 9 | ) 10 | 11 | const goCodeTemplate = `// Generated by github.com/davyxu/gosproto/sprotogen 12 | // DO NOT EDIT! 13 | 14 | package {{.PackageName}} 15 | 16 | import ( 17 | "reflect" 18 | {{if gt (.Enums|len) 0}}"github.com/davyxu/gosproto"{{end}} 19 | {{if .CellnetReg}}"github.com/davyxu/cellnet/codec/sproto"{{end}} 20 | "fmt" 21 | ) 22 | 23 | {{range $a, $enumobj := .Enums}} 24 | type {{.Name}} int32 25 | const ( {{range .StFields}} 26 | {{$enumobj.Name}}_{{.Name}} {{.GoEnumTypeName}} = {{.TagNumber}} {{end}} 27 | ) 28 | 29 | var ( 30 | {{$enumobj.Name}}MapperValueByName = map[string]int32{ {{range .StFields}} 31 | "{{.Name}}": {{.TagNumber}}, {{end}} 32 | } 33 | 34 | {{$enumobj.Name}}MapperNameByValue = map[int32]string{ {{range .StFields}} 35 | {{.TagNumber}}: "{{.Name}}" , {{end}} 36 | } 37 | ) 38 | 39 | func (self {{$enumobj.Name}}) String() string { 40 | return sproto.EnumName({{$enumobj.Name}}MapperNameByValue, int32(self)) 41 | } 42 | {{end}} 43 | 44 | {{range $a, $stobj := .Structs}} 45 | type {{.Name}} struct{ 46 | {{range .StFields}} 47 | {{.GoFieldName}} {{.GoTypeName}} {{.GoTags}} 48 | {{end}} 49 | } 50 | 51 | func (self *{{.Name}}) String() string { return fmt.Sprintf("%+v",*self) } 52 | 53 | {{range .StFields}}{{if .IsExtendType}} 54 | func (self *{{$stobj.Name}}) {{.GoExtendFieldGetterName}}() {{.GoExtendFieldGetterType}} { 55 | {{.GoExtendFieldGetter}} 56 | } 57 | func (self *{{$stobj.Name}}) Set{{.GoExtendFieldGetterName}}(v {{.GoExtendFieldGetterType}}) { 58 | {{.GoExtendFieldSetter}} 59 | } 60 | {{end}} {{end}} 61 | {{end}} 62 | 63 | var SProtoStructs = []reflect.Type{ 64 | {{range .Structs}} 65 | reflect.TypeOf((*{{.Name}})(nil)).Elem(), // {{.MsgID}} {{end}} 66 | } 67 | 68 | var SProtoEnumValue = map[string]map[int32]string{ {{range .Enums}} 69 | "{{.Name}}": {{.Name}}MapperNameByValue,{{end}} 70 | } 71 | 72 | {{if .EnumValueGroup}} 73 | func ResultToString(result int32) string { 74 | switch( result ) { 75 | case 0: return "OK"; 76 | {{range $a, $enumObj := .Enums}} {{if .IsResultEnum}} {{range .Fields}} {{if ne .TagNumber 0}} 77 | case {{.TagNumber}}: return "{{$enumObj.Name}}.{{.Name}}"; {{end}} {{end}} {{end}} {{end}} 78 | } 79 | 80 | return fmt.Sprintf("result: %d", result) 81 | } 82 | {{end}} 83 | {{if .CellnetReg}} 84 | func init() { 85 | sprotocodec.AutoRegisterMessageMeta(SProtoStructs) 86 | } 87 | {{end}} 88 | 89 | ` 90 | 91 | func (self *fieldModel) GoEnumTypeName() string { 92 | 93 | if self.st.EnumValueIgnoreType { 94 | return "" 95 | } 96 | 97 | return self.st.Name 98 | } 99 | 100 | func (self *fieldModel) GoExtendFieldGetterName() string { 101 | pname := publicFieldName(self.Name) 102 | 103 | if token.Lookup(pname).IsKeyword() { 104 | return pname + "_" 105 | } 106 | 107 | return pname 108 | } 109 | 110 | func (self *fieldModel) GoExtendFieldGetter() string { 111 | 112 | switch self.Type { 113 | case meta.FieldType_Float32, 114 | meta.FieldType_Float64: 115 | return fmt.Sprintf("return %s(self.%s) * %f", self.GoExtendFieldGetterType(), self.GoFieldName(), 1.0/float32(self.ExtendTypePrecision())) 116 | } 117 | 118 | return "unknown extend type:" + self.Type.String() 119 | } 120 | 121 | func (self *fieldModel) GoExtendFieldSetter() string { 122 | 123 | switch self.Type { 124 | case meta.FieldType_Float32: 125 | return fmt.Sprintf("self.%s = int32(v* %d)", self.GoFieldName(), self.ExtendTypePrecision()) 126 | case meta.FieldType_Float64: 127 | return fmt.Sprintf("self.%s = int64(v* %d)", self.GoFieldName(), self.ExtendTypePrecision()) 128 | } 129 | 130 | return "unknown extend type:" + self.Type.String() 131 | } 132 | 133 | func (self *fieldModel) GoExtendFieldGetterType() string { 134 | var b bytes.Buffer 135 | if self.Repeatd { 136 | b.WriteString("[]") 137 | } 138 | 139 | // 字段类型映射go的类型 140 | switch self.Type { 141 | case meta.FieldType_Float32: 142 | b.WriteString("float32") 143 | case meta.FieldType_Float64: 144 | b.WriteString("float64") 145 | default: 146 | b.WriteString("unknown extend type:" + self.Type.String()) 147 | } 148 | 149 | return b.String() 150 | } 151 | 152 | func (self *fieldModel) GoFieldName() string { 153 | 154 | var pname string 155 | 156 | // 扩展类型不能直接访问 157 | if self.IsExtendType() { 158 | pname = "Extend_" + self.Name 159 | } else { 160 | pname = publicFieldName(self.Name) 161 | } 162 | 163 | // 碰到关键字在尾部加_ 164 | if token.Lookup(pname).IsKeyword() { 165 | return pname + "_" 166 | } 167 | 168 | return pname 169 | } 170 | 171 | func (self *fieldModel) GoTypeName() string { 172 | 173 | var b bytes.Buffer 174 | if self.Repeatd { 175 | b.WriteString("[]") 176 | } 177 | 178 | if self.Type == meta.FieldType_Struct { 179 | b.WriteString("*") 180 | } 181 | 182 | // 字段类型映射go的类型 183 | switch self.Type { 184 | case meta.FieldType_Integer: 185 | b.WriteString("int") 186 | case meta.FieldType_Bool: 187 | b.WriteString("bool") 188 | case meta.FieldType_Struct, 189 | meta.FieldType_Enum: 190 | b.WriteString(self.Complex.Name) 191 | case meta.FieldType_Float32: 192 | b.WriteString("int32") 193 | case meta.FieldType_Float64: 194 | b.WriteString("int64") 195 | case meta.FieldType_Bytes: 196 | b.WriteString("[]byte") 197 | default: 198 | b.WriteString(self.Type.String()) 199 | } 200 | 201 | return b.String() 202 | } 203 | 204 | func (self *fieldModel) GoTags() string { 205 | 206 | var b bytes.Buffer 207 | 208 | b.WriteString("`sproto:\"") 209 | 210 | // 整形类型对解码层都视为整形 211 | switch self.Type { 212 | case meta.FieldType_Int32, 213 | meta.FieldType_Int64, 214 | meta.FieldType_UInt32, 215 | meta.FieldType_UInt64, 216 | meta.FieldType_Float32, 217 | meta.FieldType_Float64, 218 | meta.FieldType_Enum: 219 | b.WriteString("integer") 220 | case meta.FieldType_Bytes: 221 | b.WriteString("string") 222 | default: 223 | b.WriteString(self.Kind()) 224 | } 225 | 226 | b.WriteString(",") 227 | 228 | b.WriteString(fmt.Sprintf("%d", self.TagNumber())) 229 | b.WriteString(",") 230 | 231 | if self.Repeatd { 232 | b.WriteString("array,") 233 | } 234 | 235 | b.WriteString(fmt.Sprintf("name=%s", self.GoFieldName())) 236 | 237 | b.WriteString("\"`") 238 | 239 | return b.String() 240 | } 241 | 242 | func gen_go(fm *fileModel, filename string) { 243 | 244 | addData(fm, "go") 245 | 246 | generateCode("sp->go", goCodeTemplate, filename, fm, &generateOption{ 247 | formatGoCode: true, 248 | }) 249 | 250 | } 251 | -------------------------------------------------------------------------------- /sprotogen/gen_csharp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sort" 7 | 8 | "github.com/davyxu/gosproto/meta" 9 | ) 10 | 11 | const csharpCodeTemplate = `// Generated by github.com/davyxu/gosproto/sprotogen 12 | // DO NOT EDIT! 13 | using System; 14 | using Sproto; 15 | using System.Collections.Generic; 16 | 17 | namespace {{.PackageName}} 18 | { 19 | {{range $a, $enumobj := .Enums}} 20 | public enum {{.Name}} { 21 | {{range .StFields}} 22 | {{.Name}} = {{.TagNumber}}, 23 | {{end}} 24 | } 25 | {{end}} 26 | 27 | {{range .Structs}} 28 | {{.CSClassAttr}} 29 | public class {{.Name}} : SprotoTypeBase { 30 | private static int max_field_count = {{.MaxFieldCount}}; 31 | 32 | {{range .StFields}} 33 | [SprotoHasField] 34 | public bool Has{{.UpperName}}{ 35 | get { return base.has_field.has_field({{.FieldIndex}}); } 36 | } 37 | {{.CSFieldAttr}} 38 | private {{.CSTypeString}} _{{.Name}}; // tag {{.TagNumber}} 39 | public {{.CSTypeString}} {{.Name}} { 40 | get{ return _{{.Name}}; } 41 | set{ base.has_field.set_field({{.FieldIndex}},true); _{{.Name}} = value; } 42 | } 43 | {{end}} 44 | 45 | public {{.Name}}() : base(max_field_count) {} 46 | 47 | public {{.Name}}(byte[] buffer) : base(max_field_count, buffer) { 48 | this.decode (); 49 | } 50 | 51 | protected override void decode () { 52 | int tag = -1; 53 | while (-1 != (tag = base.deserialize.read_tag ())) { 54 | switch (tag) { 55 | {{range .StFields}} 56 | case {{.TagNumber}}: 57 | this.{{.Name}} = base.deserialize.{{.CSReadFunc}}{{.CSTemplate}}({{.CSLamdaFunc}}); 58 | break; 59 | {{end}} 60 | default: 61 | base.deserialize.read_unknow_data (); 62 | break; 63 | } 64 | } 65 | } 66 | 67 | public override int encode (SprotoStream stream) { 68 | base.serialize.open (stream); 69 | 70 | {{range .StFields}} 71 | if (base.has_field.has_field ({{.FieldIndex}})) { 72 | base.serialize.{{.CSWriteFunc}}(this.{{.Name}}, {{.TagNumber}}); 73 | } 74 | {{end}} 75 | 76 | return base.serialize.close (); 77 | } 78 | } 79 | {{end}} 80 | 81 | public class RegisterEntry 82 | { 83 | static readonly Type[] _types = new Type[]{ {{range .Structs}} 84 | typeof({{.Name}}), // {{.MsgID}}{{end}} 85 | }; 86 | 87 | public static Type[] GetClassTypes() 88 | { 89 | return _types; 90 | } 91 | {{if .EnumValueGroup}} 92 | public static string ResultToString( int result ) 93 | { 94 | switch( result) 95 | { 96 | case 0: return "OK"; 97 | {{range $a, $enumObj := .Enums}} {{if .IsResultEnum}} {{range .Fields}} {{if ne .TagNumber 0}} 98 | case {{.TagNumber}}: return "{{$enumObj.Name}}.{{.Name}}"; {{end}} {{end}} {{end}} {{end}} 99 | } 100 | 101 | return "Result: " + result.ToString(); 102 | } 103 | {{end}} 104 | } 105 | } 106 | ` 107 | 108 | func (self *fieldModel) CSTemplate() string { 109 | 110 | var buf bytes.Buffer 111 | 112 | var needTemplate bool 113 | 114 | switch self.Type { 115 | case meta.FieldType_Struct, 116 | meta.FieldType_Enum: 117 | needTemplate = true 118 | } 119 | 120 | if needTemplate { 121 | buf.WriteString("<") 122 | } 123 | 124 | if self.MainIndex != nil { 125 | buf.WriteString(csharpTypeName(self.MainIndex)) 126 | buf.WriteString(",") 127 | } 128 | 129 | if needTemplate { 130 | buf.WriteString(self.Complex.Name) 131 | buf.WriteString(">") 132 | } 133 | 134 | return buf.String() 135 | } 136 | 137 | func (self *fieldModel) CSLamdaFunc() string { 138 | if self.MainIndex == nil { 139 | return "" 140 | } 141 | 142 | return fmt.Sprintf("v => v.%s", self.MainIndex.Name) 143 | } 144 | 145 | func (self *fieldModel) CSWriteFunc() string { 146 | 147 | return "write_" + self.serializer() 148 | } 149 | 150 | func (self *fieldModel) CSReadFunc() string { 151 | 152 | funcName := "read_" 153 | 154 | if self.Repeatd { 155 | 156 | if self.MainIndex != nil { 157 | return funcName + "map" 158 | } else { 159 | return funcName + self.serializer() + "_list" 160 | } 161 | 162 | } 163 | 164 | return funcName + self.serializer() 165 | } 166 | 167 | func (self *fieldModel) serializer() string { 168 | 169 | var baseName string 170 | 171 | switch self.Type { 172 | case meta.FieldType_Integer: 173 | baseName = "integer" 174 | case meta.FieldType_Int32: 175 | baseName = "int32" 176 | case meta.FieldType_Int64: 177 | baseName = "int64" 178 | case meta.FieldType_UInt32: 179 | baseName = "uint32" 180 | case meta.FieldType_UInt64: 181 | baseName = "uint64" 182 | case meta.FieldType_Float32: 183 | baseName = "float32" 184 | case meta.FieldType_Float64: 185 | baseName = "double" 186 | case meta.FieldType_String: 187 | baseName = "string" 188 | case meta.FieldType_Bool: 189 | baseName = "boolean" 190 | case meta.FieldType_Struct: 191 | baseName = "obj" 192 | case meta.FieldType_Enum: 193 | baseName = "enum" 194 | case meta.FieldType_Bytes: 195 | baseName = "bytes" 196 | default: 197 | baseName = "unknown" 198 | } 199 | 200 | return baseName 201 | } 202 | 203 | func (self *fieldModel) CSTypeName() string { 204 | // 字段类型映射go的类型 205 | return csharpTypeName(self.FieldDescriptor) 206 | } 207 | 208 | func csharpTypeName(fd *meta.FieldDescriptor) string { 209 | switch fd.Type { 210 | case meta.FieldType_Integer: 211 | return "Int64" 212 | case meta.FieldType_Int32: 213 | return "Int32" 214 | case meta.FieldType_Int64: 215 | return "Int64" 216 | case meta.FieldType_UInt32: 217 | return "UInt32" 218 | case meta.FieldType_UInt64: 219 | return "UInt64" 220 | case meta.FieldType_Float32: 221 | return "float" 222 | case meta.FieldType_Float64: 223 | return "double" 224 | case meta.FieldType_String: 225 | return "string" 226 | case meta.FieldType_Bool: 227 | return "bool" 228 | case meta.FieldType_Bytes: 229 | return "byte[]" 230 | case meta.FieldType_Struct, 231 | meta.FieldType_Enum: 232 | return fd.Complex.Name 233 | } 234 | return "unknown" 235 | } 236 | 237 | func (self *fieldModel) CSTypeString() string { 238 | 239 | var b bytes.Buffer 240 | if self.Repeatd { 241 | 242 | if self.MainIndex != nil { 243 | b.WriteString("Dictionary<") 244 | 245 | b.WriteString(csharpTypeName(self.MainIndex)) 246 | 247 | b.WriteString(",") 248 | 249 | } else { 250 | b.WriteString("List<") 251 | } 252 | 253 | } 254 | 255 | b.WriteString(self.CSTypeName()) 256 | 257 | if self.Repeatd { 258 | b.WriteString(">") 259 | } 260 | 261 | return b.String() 262 | } 263 | 264 | func (self *fieldModel) CSFieldAttr() string { 265 | return self.st.f.CSFieldAttr 266 | } 267 | 268 | func (self *structModel) CSClassAttr() string { 269 | return self.f.CSClassAttr 270 | } 271 | 272 | func gen_csharp(fm *fileModel, filename string) { 273 | 274 | addData(fm, "cs") 275 | 276 | sort.Sort(fm) 277 | 278 | generateCode("sp->cs", csharpCodeTemplate, filename, fm, nil) 279 | 280 | } 281 | -------------------------------------------------------------------------------- /decode.go: -------------------------------------------------------------------------------- 1 | package sproto 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | ) 8 | 9 | type Tag struct { 10 | Tag uint16 11 | Val *uint16 12 | } 13 | 14 | func readChunk(chunk []byte) (int, []byte, error) { 15 | if len(chunk) < 4 { 16 | return 0, nil, ErrDecode 17 | } 18 | sz := int(readUint32(chunk)) 19 | expected := 4 + sz 20 | if len(chunk) < expected { 21 | return 0, nil, ErrDecode 22 | } 23 | return expected, chunk[4:expected], nil 24 | } 25 | 26 | func readUint16(buf []byte) uint16 { 27 | var n uint16 28 | n = uint16(buf[1]) << 8 29 | n |= uint16(buf[0]) 30 | return n 31 | } 32 | 33 | func readUint32(buf []byte) uint32 { 34 | var n uint32 35 | n = uint32(buf[3]) << 24 36 | n |= uint32(buf[2]) << 16 37 | n |= uint32(buf[1]) << 8 38 | n |= uint32(buf[0]) 39 | return n 40 | } 41 | 42 | func readUint64(buf []byte) uint64 { 43 | var n uint64 44 | n = uint64(buf[7]) << 56 45 | n |= uint64(buf[6]) << 48 46 | n |= uint64(buf[5]) << 40 47 | n |= uint64(buf[4]) << 32 48 | n |= uint64(buf[3]) << 24 49 | n |= uint64(buf[2]) << 16 50 | n |= uint64(buf[1]) << 8 51 | n |= uint64(buf[0]) 52 | return n 53 | } 54 | 55 | func decodeBool(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 56 | 57 | if *val == 0 { 58 | v.SetBool(false) 59 | } else { 60 | v.SetBool(true) 61 | } 62 | 63 | return nil 64 | } 65 | 66 | func decodeInt(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 67 | var n uint64 68 | if val != nil { 69 | n = uint64(*val) 70 | } else { 71 | switch len(data) { 72 | case 0: 73 | n = 0 74 | case 4: 75 | n = uint64(readUint32(data)) 76 | case 8: 77 | n = readUint64(data) 78 | default: 79 | return fmt.Errorf("sproto: malformed integer data for field %s", sf.Name) 80 | } 81 | } 82 | e := v.Type() 83 | //v.Set(reflect.New(e).Elem()) 84 | switch e.Kind() { 85 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 86 | v.SetInt(int64(n)) 87 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 88 | v.SetUint(n) 89 | } 90 | return nil 91 | } 92 | 93 | func decodeString(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 94 | str := string(data) 95 | v.SetString(str) 96 | return nil 97 | } 98 | 99 | func decodeBytes(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 100 | buf := make([]byte, len(data)) 101 | copy(buf, data) 102 | v.SetBytes(buf) 103 | return nil 104 | } 105 | 106 | func decodeBoolSlice(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 107 | vals := make([]bool, len(data)) 108 | for i, v := range data { 109 | if v == 0 { 110 | vals[i] = false 111 | } else { 112 | vals[i] = true 113 | } 114 | } 115 | v.Set(reflect.ValueOf(vals)) 116 | return nil 117 | } 118 | 119 | func decodeIntSlice(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 120 | dataLen := len(data) 121 | if dataLen < 1 { 122 | return ErrDecode 123 | } 124 | intLen := int(data[0]) 125 | if (dataLen-1)%intLen != 0 { 126 | return fmt.Errorf("sproto: malformed integer data for field %s", sf.Name) 127 | } 128 | sz := (dataLen - 1) / intLen 129 | vals := reflect.MakeSlice(v.Type(), sz, sz) 130 | data = data[1:] 131 | var n uint64 132 | for i := 0; i < sz; i++ { 133 | if intLen == 4 { 134 | n = uint64(readUint32(data[i*intLen:])) 135 | } else { 136 | n = readUint64(data[i*intLen:]) 137 | } 138 | 139 | val := vals.Index(i) 140 | switch e := v.Type().Elem(); e.Kind() { 141 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 142 | val.SetInt(int64(n)) 143 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 144 | val.SetUint(n) 145 | } 146 | } 147 | v.Set(vals) 148 | return nil 149 | } 150 | 151 | func decodeStringSlice(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 152 | vals := make([]string, 0, 16) 153 | for len(data) > 0 { 154 | expected, val, err := readChunk(data) 155 | if err != nil { 156 | return err 157 | } 158 | vals = append(vals, string(val)) 159 | data = data[expected:] 160 | } 161 | v.Set(reflect.ValueOf(vals)) 162 | return nil 163 | } 164 | 165 | func decodeStruct(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 166 | // v1: pointer to struct 167 | v1 := reflect.New(v.Type().Elem()) 168 | used, err := decodeMessage(data, sf.st, v1) 169 | if err != nil { 170 | return err 171 | } 172 | if used != len(data) { 173 | return fmt.Errorf("sproto: malformed struct data for field %s", sf.Name) 174 | } 175 | v.Addr().Elem().Set(v1) 176 | return nil 177 | } 178 | 179 | func decodeStructSlice(val *uint16, data []byte, sf *SprotoField, v reflect.Value) error { 180 | vals := reflect.MakeSlice(v.Type(), 0, 16) 181 | for len(data) > 0 { 182 | expected, buf, err := readChunk(data) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | // v1: pointer to struct 188 | v1 := reflect.New(v.Type().Elem().Elem()) 189 | used, err := decodeMessage(buf, sf.st, v1) 190 | if err != nil { 191 | return err 192 | } 193 | if used != len(buf) { 194 | return fmt.Errorf("sproto: malformed struct data for field %s", sf.Name) 195 | } 196 | vals = reflect.Append(vals, v1) 197 | data = data[expected:] 198 | } 199 | v.Set(vals) 200 | return nil 201 | } 202 | 203 | func decodeHeader(chunk []byte) (int, []Tag, error) { 204 | if len(chunk) < 2 { 205 | return 0, nil, ErrDecode 206 | } 207 | fn := int(readUint16(chunk)) 208 | expected := 2 + fn*2 209 | if len(chunk) < expected { 210 | return 0, nil, ErrDecode 211 | } 212 | tags := make([]Tag, fn) 213 | n := 0 214 | var tag uint16 = 0 215 | for i := 0; i < fn; i++ { 216 | v := readUint16(chunk[(i+1)*2:]) 217 | if v%2 != 0 { //skip tag 218 | tag += (v + 1) / 2 219 | continue 220 | } 221 | var val *uint16 222 | if v != 0 { // value 223 | v1 := v/2 - 1 224 | val = &v1 225 | } 226 | tags[n] = Tag{ 227 | Tag: tag, 228 | Val: val, 229 | } 230 | tag++ 231 | n++ 232 | } 233 | return expected, tags[:n], nil 234 | } 235 | 236 | func decodeMessage(chunk []byte, st *SprotoType, v reflect.Value) (int, error) { 237 | var total int 238 | var tags []Tag 239 | var err error 240 | if total, tags, err = decodeHeader(chunk); err != nil { 241 | return 0, err 242 | } 243 | 244 | if v.Kind() == reflect.Ptr { 245 | v = v.Elem() 246 | } 247 | 248 | for _, tag := range tags { 249 | var used int 250 | var data []byte 251 | if tag.Val == nil { 252 | if used, data, err = readChunk(chunk[total:]); err != nil { 253 | return 0, err 254 | } 255 | total += used 256 | } 257 | sf := st.FieldByTag(int(tag.Tag)) 258 | if sf == nil { 259 | fmt.Fprintf(os.Stderr, "sproto<%s>: unknown tag %d\n", st.Name, tag.Tag) 260 | continue 261 | } 262 | 263 | v1 := v.FieldByIndex(sf.index) 264 | 265 | if err = sf.dec(tag.Val, data, sf, v1); err != nil { 266 | return 0, err 267 | } 268 | } 269 | return total, nil 270 | } 271 | 272 | func Decode(data []byte, sp interface{}) (int, error) { 273 | t, v, err := getbase(sp) 274 | if err != nil { 275 | return 0, err 276 | } 277 | // clear sp 278 | v.Elem().Set(reflect.Zero(t.Elem())) 279 | st, err := GetSprotoType(t.Elem()) 280 | if err != nil { 281 | return 0, err 282 | } 283 | return decodeMessage(data, st, v) 284 | } 285 | -------------------------------------------------------------------------------- /encode.go: -------------------------------------------------------------------------------- 1 | package sproto 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | const ( 8 | EncodeBufferSize = 4096 9 | MaxEmbeddedInt = 0x7fff - 1 10 | MaxInt32 = 0x7fffffff 11 | MinInt32 = -0x80000000 12 | ) 13 | 14 | // little endian 15 | // put int into []byte 16 | func writeUint16(buf []byte, v uint16) { 17 | buf[0] = uint8(v & 0xff) 18 | buf[1] = uint8(v >> 8) 19 | } 20 | 21 | func writeUint32(buf []byte, v uint32) { 22 | buf[0] = uint8(v & 0xff) 23 | buf[1] = uint8((v >> 8) & 0xff) 24 | buf[2] = uint8((v >> 16) & 0xff) 25 | buf[3] = uint8((v >> 24) & 0xff) 26 | } 27 | 28 | func writeUint64(buf []byte, v uint64) { 29 | buf[0] = uint8(v & 0xff) 30 | buf[1] = uint8((v >> 8) & 0xff) 31 | buf[2] = uint8((v >> 16) & 0xff) 32 | buf[3] = uint8((v >> 24) & 0xff) 33 | buf[4] = uint8((v >> 32) & 0xff) 34 | buf[5] = uint8((v >> 40) & 0xff) 35 | buf[6] = uint8((v >> 48) & 0xff) 36 | buf[7] = uint8((v >> 56) & 0xff) 37 | } 38 | 39 | func headerEncodeDefault(sf *SprotoField, v reflect.Value) (uint16, bool) { 40 | 41 | switch v.Kind() { 42 | case reflect.String: 43 | if v.Interface() == "" { 44 | return 0, true 45 | } 46 | default: 47 | if v.IsNil() { 48 | return 0, true 49 | } 50 | } 51 | 52 | return 0, false 53 | } 54 | 55 | func headerEncodeBool(sf *SprotoField, v reflect.Value) (uint16, bool) { 56 | if v.Interface() == false { 57 | return 0, true 58 | } 59 | 60 | var n uint16 = 0 61 | if v.Bool() { 62 | n = 1 63 | } 64 | return 2 * (n + 1), false 65 | } 66 | 67 | func headerEncodeInt(sf *SprotoField, v reflect.Value) (uint16, bool) { 68 | 69 | var n uint64 70 | switch e := v; e.Kind() { 71 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 72 | n = uint64(e.Int()) 73 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 74 | n = e.Uint() 75 | } 76 | 77 | if n == 0 { 78 | return 0, true 79 | } 80 | 81 | if n <= MaxEmbeddedInt { 82 | return uint16(2 * (n + 1)), false 83 | } 84 | return 0, false 85 | } 86 | 87 | func extractInt(v reflect.Value) (n uint64, sz int) { 88 | switch v.Kind() { 89 | case reflect.Int8, reflect.Int16, reflect.Int32: 90 | n = uint64(v.Int()) 91 | sz = 4 92 | case reflect.Int, reflect.Int64: 93 | n1 := v.Int() 94 | n = uint64(n1) 95 | if n1 >= -0x80000000 && n1 <= 0x7fffffff { 96 | sz = 4 97 | } else { 98 | sz = 8 99 | } 100 | case reflect.Uint8, reflect.Uint16: 101 | n = v.Uint() 102 | sz = 4 103 | default: //case reflect.Uint32, reflect.Uint64, reflect.Uint: 104 | n = v.Uint() 105 | if n <= MaxInt32 { 106 | sz = 4 107 | } else { 108 | sz = 8 109 | } 110 | } 111 | return 112 | } 113 | 114 | func encodeInt(sf *SprotoField, v reflect.Value) []byte { 115 | n, sz := extractInt(v) 116 | if n <= MaxEmbeddedInt { 117 | return nil 118 | } 119 | 120 | buf := make([]byte, sz) 121 | if sz == 4 { 122 | writeUint32(buf, uint32(n)) 123 | } else { 124 | writeUint64(buf, n) 125 | } 126 | return buf 127 | } 128 | 129 | func encodeString(sf *SprotoField, v reflect.Value) []byte { 130 | str := v.String() 131 | buf := make([]byte, len(str)) 132 | copy(buf, str) 133 | return buf 134 | } 135 | 136 | func encodeBytes(sf *SprotoField, v reflect.Value) []byte { 137 | bytes := v.Bytes() 138 | buf := make([]byte, len(bytes)) 139 | copy(buf, bytes) 140 | return buf 141 | } 142 | 143 | func encodeStruct(sf *SprotoField, v reflect.Value) []byte { 144 | return encodeMessage(sf.st, v) 145 | } 146 | 147 | func encodeBoolSlice(sf *SprotoField, v reflect.Value) []byte { 148 | sz := v.Len() 149 | buf := make([]byte, sz) 150 | offset := 0 151 | for i := 0; i < sz; i++ { 152 | if v.Index(i).Bool() { 153 | buf[offset+i] = 1 154 | } else { 155 | buf[offset+i] = 0 156 | } 157 | } 158 | return buf 159 | } 160 | 161 | func encodeStringSlice(sf *SprotoField, v reflect.Value) []byte { 162 | var sz int 163 | for i := 0; i < v.Len(); i++ { 164 | str := v.Index(i).String() 165 | sz += 4 + len(str) 166 | } 167 | buf := make([]byte, sz) 168 | offset := 0 169 | for i := 0; i < v.Len(); i++ { 170 | str := v.Index(i).String() 171 | strLen := len(str) 172 | writeUint32(buf[offset:], uint32(strLen)) 173 | copy(buf[offset+4:], str) 174 | offset += 4 + strLen 175 | } 176 | return buf 177 | } 178 | 179 | func encodeIntSlice(sf *SprotoField, v reflect.Value) []byte { 180 | vals := make([]uint64, v.Len()) 181 | var intLen int = 4 // could be 4 and 8 182 | for i := 0; i < v.Len(); i++ { 183 | n, tmp := extractInt(v.Index(i)) 184 | if tmp > intLen { 185 | intLen = tmp 186 | } 187 | vals[i] = n 188 | } 189 | 190 | buf := make([]byte, 1+intLen*v.Len()) 191 | buf[0] = uint8(intLen) // put intLen 192 | offset := 1 193 | for _, val := range vals { 194 | if intLen == 4 { 195 | writeUint32(buf[offset:], uint32(val)) 196 | } else { 197 | writeUint64(buf[offset:], val) 198 | } 199 | offset += intLen 200 | } 201 | return buf 202 | } 203 | 204 | func encodeStructSlice(sf *SprotoField, v reflect.Value) []byte { 205 | 206 | sz := 0 207 | vals := make([][]byte, v.Len()) 208 | for i := 0; i < v.Len(); i++ { 209 | val := encodeMessage(sf.st, v.Index(i)) 210 | vals[i] = val 211 | sz += len(val) + 4 212 | } 213 | 214 | buf := make([]byte, sz) 215 | offset := 0 216 | for _, val := range vals { 217 | valLen := len(val) 218 | writeUint32(buf[offset:], uint32(valLen)) 219 | copy(buf[offset+4:], val) 220 | offset += valLen + 4 221 | } 222 | return buf 223 | } 224 | 225 | func skipTag(tag, nextTag int) uint16 { 226 | if nextTag > tag+1 { 227 | span := nextTag - tag - 1 228 | return uint16((span-1)*2 + 1) 229 | } 230 | return 0 231 | } 232 | 233 | func encodeHeaders(headers []uint16, reserved int) []byte { 234 | buf := make([]byte, (len(headers)+1)*2+reserved) 235 | i := 0 236 | writeUint16(buf, uint16(len(headers))) 237 | i = i + 2 238 | for _, v := range headers { 239 | writeUint16(buf[i:], v) 240 | i = i + 2 241 | } 242 | return buf[:i] 243 | } 244 | 245 | func encodeMessage(st *SprotoType, v reflect.Value) []byte { 246 | headers := make([]uint16, len(st.Fields)*2) // max header len is fieldNum * 2 247 | buffer := make([]byte, EncodeBufferSize)[0:0] // pre-allocate 4k buffer 248 | 249 | tag, offset := -1, 0 250 | dataLen := make([]byte, 4, 4) 251 | if !v.IsNil() { // struct could be nil in struct array 252 | for _, i := range st.order { 253 | sf := st.Fields[i] 254 | v1 := v.Elem().FieldByIndex(sf.index) 255 | nextTag := sf.Tag 256 | if nextTag < 0 { 257 | continue 258 | } 259 | 260 | if header, isNil := sf.headerEnc(sf, v1); !isNil { 261 | if skip := skipTag(tag, nextTag); skip > 0 { 262 | headers[offset] = skip 263 | offset++ 264 | } 265 | headers[offset] = header 266 | offset++ 267 | tag = nextTag 268 | if sf.enc != nil { 269 | if data := sf.enc(sf, v1); data != nil { 270 | writeUint32(dataLen, uint32(len(data))) 271 | buffer = Append(buffer, dataLen) 272 | buffer = Append(buffer, data) 273 | } 274 | } 275 | } 276 | } 277 | } 278 | return Append(encodeHeaders(headers[:offset], len(buffer)), buffer) 279 | } 280 | 281 | func Encode(sp interface{}) ([]byte, error) { 282 | t, v, err := getbase(sp) 283 | if err != nil { 284 | return nil, err 285 | } 286 | 287 | st, err := GetSprotoType(t.Elem()) 288 | if err != nil { 289 | return nil, err 290 | } 291 | return encodeMessage(st, v), nil 292 | } 293 | -------------------------------------------------------------------------------- /meta.go: -------------------------------------------------------------------------------- 1 | package sproto 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sort" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | ) 11 | 12 | const ( 13 | WireVarintName = "integer" // int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64 14 | WireBooleanName = "boolean" // bool 15 | WireBytesName = "string" // string, []byte 16 | WireStructName = "struct" // struct 17 | ) 18 | 19 | var ( 20 | mutex sync.Mutex 21 | stMap = make(map[reflect.Type]*SprotoType) 22 | ) 23 | 24 | type headerEncoder func(st *SprotoField, v reflect.Value) (uint16, bool) 25 | type encoder func(st *SprotoField, v reflect.Value) []byte 26 | type decoder func(val *uint16, data []byte, st *SprotoField, v reflect.Value) error 27 | 28 | type SprotoField struct { 29 | Name string 30 | OrigName string 31 | Wire string 32 | Tag int 33 | Array bool 34 | 35 | st *SprotoType // for struct types only 36 | 37 | index []int // index sequence for Value.FieldByIndex 38 | headerEnc headerEncoder 39 | enc encoder 40 | dec decoder 41 | } 42 | 43 | // parse filed meta information 44 | func (sf *SprotoField) parse(s string) error { 45 | // children,object,3,array 46 | fields := strings.Split(s, ",") 47 | if len(fields) < 2 { 48 | return fmt.Errorf("sproto: parse(%s) tag must have 2 or more fields", s) 49 | } 50 | sf.Wire = fields[0] 51 | switch sf.Wire { 52 | case WireVarintName, WireBooleanName, WireBytesName, WireStructName: 53 | default: 54 | return fmt.Errorf("sproto: parse(%s) unknown wire type: %s", s, sf.Wire) 55 | } 56 | 57 | var err error 58 | sf.Tag, err = strconv.Atoi(fields[1]) 59 | if err != nil { 60 | return fmt.Errorf("sproto: parse(%s) parse tag failed: %s", s, err) 61 | } 62 | 63 | for i := 2; i < len(fields); i++ { 64 | f := fields[i] 65 | switch { 66 | case f == "array": 67 | sf.Array = true 68 | case strings.HasPrefix(f, "name="): 69 | sf.OrigName = f[len("name="):] 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | func (sf *SprotoField) assertWire(expectedWire string, expectedArray bool) error { 76 | if sf.Wire != expectedWire { 77 | return fmt.Errorf("sproto: field(%s) expect %s but get %s", sf.Name, expectedWire, sf.Wire) 78 | } 79 | if sf.Array != expectedArray { 80 | n := "not" 81 | if expectedArray { 82 | n = "" 83 | } 84 | return fmt.Errorf("sproto: field(%s) should %s be array", sf.Name, n) 85 | } 86 | return nil 87 | } 88 | 89 | func (sf *SprotoField) setEncAndDec(f *reflect.StructField) error { 90 | var stype reflect.Type 91 | var err error 92 | switch t1 := f.Type; t1.Kind() { 93 | 94 | case reflect.Bool: 95 | sf.headerEnc = headerEncodeBool 96 | sf.dec = decodeBool 97 | err = sf.assertWire(WireBooleanName, false) 98 | case reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, 99 | reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64, 100 | reflect.Int, reflect.Uint: 101 | sf.headerEnc = headerEncodeInt 102 | sf.enc = encodeInt 103 | sf.dec = decodeInt 104 | err = sf.assertWire(WireVarintName, false) 105 | case reflect.String: 106 | sf.headerEnc = headerEncodeDefault 107 | sf.enc = encodeString 108 | sf.dec = decodeString 109 | sf.assertWire(WireBytesName, false) 110 | // case reflect.Struct: 111 | // stype = t1.Elem() 112 | // sf.headerEnc = headerEncodeDefault 113 | // sf.enc = encodeStruct 114 | // sf.dec = decodeStruct 115 | // err = sf.assertWire(WireStructName, false) 116 | case reflect.Ptr: 117 | switch t2 := t1.Elem(); t2.Kind() { 118 | case reflect.Struct: 119 | stype = t1.Elem() 120 | sf.headerEnc = headerEncodeDefault 121 | sf.enc = encodeStruct 122 | sf.dec = decodeStruct 123 | err = sf.assertWire(WireStructName, false) 124 | default: 125 | err = fmt.Errorf("sproto: field(%s) no coders for %s -> %s", sf.Name, t1.Kind().String(), t2.Kind().String()) 126 | } 127 | case reflect.Slice: 128 | switch t2 := t1.Elem(); t2.Kind() { 129 | case reflect.Bool: 130 | sf.headerEnc = headerEncodeDefault 131 | sf.enc = encodeBoolSlice 132 | sf.dec = decodeBoolSlice 133 | err = sf.assertWire(WireBooleanName, true) 134 | case reflect.Uint8: 135 | sf.headerEnc = headerEncodeDefault 136 | if sf.Wire == WireBytesName { 137 | sf.enc = encodeBytes 138 | sf.dec = decodeBytes 139 | err = sf.assertWire(WireBytesName, false) 140 | } else { 141 | sf.enc = encodeIntSlice 142 | sf.dec = decodeIntSlice 143 | err = sf.assertWire(WireVarintName, true) 144 | } 145 | case reflect.Int8, reflect.Int16, reflect.Uint16, 146 | reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64, 147 | reflect.Int, reflect.Uint: 148 | sf.headerEnc = headerEncodeDefault 149 | sf.enc = encodeIntSlice 150 | sf.dec = decodeIntSlice 151 | err = sf.assertWire(WireVarintName, true) 152 | case reflect.String: 153 | sf.headerEnc = headerEncodeDefault 154 | sf.enc = encodeStringSlice 155 | sf.dec = decodeStringSlice 156 | sf.assertWire(WireBytesName, true) 157 | case reflect.Ptr: 158 | switch t3 := t2.Elem(); t3.Kind() { 159 | case reflect.Struct: 160 | stype = t2.Elem() 161 | sf.headerEnc = headerEncodeDefault 162 | sf.enc = encodeStructSlice 163 | sf.dec = decodeStructSlice 164 | err = sf.assertWire(WireStructName, true) 165 | default: 166 | err = fmt.Errorf("sproto: field(%s) no coders for %s -> %s -> %s", sf.Name, t1.Kind().String(), t2.Kind().String(), t3.Kind().String()) 167 | } 168 | default: 169 | err = fmt.Errorf("sproto: field(%s) no coders for %s -> %s", sf.Name, t1.Kind().String(), t2.Kind().String()) 170 | } 171 | default: 172 | err = fmt.Errorf("sproto: field(%s) no coders for %s", sf.Name, t1.Kind().String()) 173 | } 174 | 175 | if err != nil { 176 | return err 177 | } 178 | 179 | if stype != nil { 180 | if sf.st, err = getSprotoTypeLocked(stype); err != nil { 181 | return err 182 | } 183 | } 184 | return nil 185 | } 186 | 187 | func (sf *SprotoField) init(f *reflect.StructField) error { 188 | sf.Name = f.Name 189 | sf.OrigName = f.Name 190 | 191 | tagString := f.Tag.Get("sproto") 192 | if tagString == "" { 193 | sf.Tag = -1 194 | return nil 195 | } 196 | 197 | sf.index = f.Index 198 | if err := sf.parse(tagString); err != nil { 199 | return err 200 | } 201 | if err := sf.setEncAndDec(f); err != nil { 202 | return err 203 | } 204 | return nil 205 | } 206 | 207 | type SprotoType struct { 208 | Name string // struct name 209 | Fields []*SprotoField 210 | tagMap map[int]int // tag -> fileds index 211 | order []int // list of struct field numbers in tag order 212 | } 213 | 214 | func (st *SprotoType) Len() int { return len(st.order) } 215 | func (st *SprotoType) Less(i, j int) bool { 216 | return st.Fields[st.order[i]].Tag < st.Fields[st.order[j]].Tag 217 | } 218 | func (st *SprotoType) Swap(i, j int) { 219 | st.order[i], st.order[j] = st.order[j], st.order[i] 220 | } 221 | 222 | func (st *SprotoType) FieldByTag(tag int) *SprotoField { 223 | if index, ok := st.tagMap[tag]; ok { 224 | return st.Fields[index] 225 | } 226 | return nil 227 | } 228 | 229 | func GetSprotoType(t reflect.Type) (*SprotoType, error) { 230 | if t.Kind() != reflect.Struct { 231 | return nil, fmt.Errorf("sproto: type must have kind struct") 232 | } 233 | mutex.Lock() 234 | sp, err := getSprotoTypeLocked(t) 235 | mutex.Unlock() 236 | return sp, err 237 | } 238 | 239 | func getSprotoTypeLocked(t reflect.Type) (*SprotoType, error) { 240 | if st, ok := stMap[t]; ok { 241 | return st, nil 242 | } 243 | 244 | st := new(SprotoType) 245 | stMap[t] = st 246 | 247 | st.Name = t.Name() 248 | numField := t.NumField() 249 | st.Fields = make([]*SprotoField, numField) 250 | st.order = make([]int, numField) 251 | st.tagMap = make(map[int]int) 252 | 253 | for i := 0; i < numField; i++ { 254 | sf := new(SprotoField) 255 | f := t.Field(i) 256 | if err := sf.init(&f); err != nil { 257 | delete(stMap, t) 258 | return nil, err 259 | } 260 | 261 | st.Fields[i] = sf 262 | st.order[i] = i 263 | if sf.Tag >= 0 { 264 | // check repeated tag 265 | if _, ok := st.tagMap[sf.Tag]; ok { 266 | return nil, fmt.Errorf("sproto: field(%s.%s) tag repeated", st.Name, sf.Name) 267 | } 268 | st.tagMap[sf.Tag] = i 269 | } 270 | } 271 | 272 | // Re-order prop.order 273 | sort.Sort(st) 274 | return st, nil 275 | } 276 | 277 | // Get the type and value of a pointer to a struct from interface{} 278 | func getbase(sp interface{}) (t reflect.Type, v reflect.Value, err error) { 279 | if sp == nil { 280 | err = ErrNil 281 | return 282 | } 283 | 284 | t = reflect.TypeOf(sp) 285 | if t.Kind() != reflect.Ptr { 286 | err = ErrNonPtr 287 | return 288 | } 289 | 290 | if t.Elem().Kind() != reflect.Struct { 291 | err = ErrNonStruct 292 | return 293 | } 294 | 295 | v = reflect.ValueOf(sp) 296 | if v.IsNil() { 297 | err = ErrNil 298 | return 299 | } 300 | 301 | return 302 | } 303 | -------------------------------------------------------------------------------- /sproto_test.go: -------------------------------------------------------------------------------- 1 | package sproto_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "bytes" 7 | "reflect" 8 | 9 | "github.com/davyxu/gosproto" 10 | ) 11 | 12 | type PhoneNumber struct { 13 | Number string `sproto:"string,0,name=number"` 14 | Type int `sproto:"integer,1,name=type"` 15 | } 16 | 17 | type Person struct { 18 | Name string `sproto:"string,0,name=name"` 19 | Id int `sproto:"integer,1,name=id"` 20 | Email string `sproto:"string,2,name=email"` 21 | Phone []*PhoneNumber `sproto:"struct,3,array,name=phone"` 22 | } 23 | 24 | type AddressBook struct { 25 | Person []*Person `sproto:"struct,0,array,name=person"` 26 | } 27 | 28 | type Human struct { 29 | Name string `sproto:"string,0,name=name"` 30 | Age int `sproto:"integer,1,name=age"` 31 | Marital bool `sproto:"boolean,2,name=marital"` 32 | Children []*Human `sproto:"struct,3,array,name=children"` 33 | } 34 | 35 | type Data struct { 36 | Numbers []int64 `sproto:"integer,0,array,name=numbers"` 37 | Bools []bool `sproto:"boolean,1,array,name=bools"` 38 | Number int `sproto:"integer,2,name=number"` 39 | BigNumber int64 `sproto:"integer,3,name=bignumber"` 40 | Strings []string `sproto:"string,4,array,name=strings"` 41 | Bytes []byte `sproto:"string,5,name=bytes"` 42 | } 43 | 44 | type TestCase struct { 45 | Name string 46 | Struct interface{} 47 | Data []byte 48 | } 49 | 50 | var abData []byte = []byte{ 51 | 1, 0, 0, 0, 122, 0, 0, 0, 52 | 68, 0, 0, 0, 4, 0, 0, 53 | 0, 34, 78, 1, 0, 0, 0, 54 | 5, 0, 0, 0, 65, 108, 105, 55 | 99, 101, 45, 0, 0, 0, 19, 56 | 0, 0, 0, 2, 0, 0, 0, 57 | 4, 0, 9, 0, 0, 0, 49, 58 | 50, 51, 52, 53, 54, 55, 56, 59 | 57, 18, 0, 0, 0, 2, 0, 60 | 0, 0, 6, 0, 8, 0, 0, 61 | 0, 56, 55, 54, 53, 52, 51, 62 | 50, 49, 46, 0, 0, 0, 4, 63 | 0, 0, 0, 66, 156, 1, 0, 64 | 0, 0, 3, 0, 0, 0, 66, 65 | 111, 98, 25, 0, 0, 0, 21, 66 | 0, 0, 0, 2, 0, 0, 0, 67 | 8, 0, 11, 0, 0, 0, 48, 68 | 49, 50, 51, 52, 53, 54, 55, 69 | 56, 57, 48, 70 | } 71 | 72 | var abDataPacked []byte = []byte{ 73 | 17, 1, 122, 17, 68, 4, 71, 34, 78, 1, 5, 252, 65, 108, 105, 99, 101, 74 | 45, 136, 19, 2, 40, 4, 9, 254, 49, 50, 51, 52, 53, 54, 55, 71, 56, 57, 75 | 18, 2, 20, 6, 8, 255, 0, 56, 55, 54, 53, 52, 51, 50, 49, 17, 46, 4, 71, 76 | 66, 156, 1, 3, 60, 66, 111, 98, 25, 34, 21, 2, 138, 8, 11, 48, 255, 0, 77 | 49, 50, 51, 52, 53, 54, 55, 56, 3, 57, 48, 78 | } 79 | 80 | var ab AddressBook = AddressBook{ 81 | Person: []*Person{ 82 | &Person{ 83 | Name: "Alice", 84 | Id: 10000, 85 | Phone: []*PhoneNumber{ 86 | &PhoneNumber{ 87 | Number: "123456789", 88 | Type: 1, 89 | }, 90 | &PhoneNumber{ 91 | Number: "87654321", 92 | Type: 2, 93 | }, 94 | }, 95 | }, 96 | &Person{ 97 | Name: "Bob", 98 | Id: 20000, 99 | Phone: []*PhoneNumber{ 100 | &PhoneNumber{ 101 | Number: "01234567890", 102 | Type: 3, 103 | }, 104 | }, 105 | }, 106 | }, 107 | } 108 | 109 | var testCases []*TestCase = []*TestCase{ 110 | &TestCase{ 111 | Name: "SimpleStruct", 112 | Struct: &Human{ 113 | Name: "Alice", 114 | Age: 13, 115 | Marital: false, // Marital euquals bool default(false), ignored 116 | }, 117 | Data: []byte{ 118 | 0x02, 0x00, // (fn = 3) 119 | 0x00, 0x00, // (id = 0, value in data part) 120 | 0x1C, 0x00, // (id = 1, value = 13) 121 | 0x05, 0x00, 0x00, 0x00, // (sizeof "Alice") 122 | 0x41, 0x6C, 0x69, 0x63, 0x65, // ("Alice") 123 | }, 124 | }, 125 | &TestCase{ 126 | Name: "StructArray", 127 | Struct: &Human{ 128 | Name: "Bob", 129 | Age: 40, 130 | Children: []*Human{ 131 | &Human{ 132 | Name: "Alice", 133 | Age: 13, 134 | }, 135 | &Human{ 136 | Name: "Carol", 137 | Age: 5, 138 | }, 139 | }, 140 | }, 141 | Data: []byte{ 142 | 0x04, 0x00, // (fn = 4) 143 | 0x00, 0x00, // (id = 0, value in data part) 144 | 0x52, 0x00, // (id = 1, value = 40) 145 | 0x01, 0x00, // (skip id = 2) 146 | 0x00, 0x00, // (id = 3, value in data part) 147 | 0x03, 0x00, 0x00, 0x00, // (sizeof "Bob") 148 | 0x42, 0x6F, 0x62, // ("Bob") 149 | 0x26, 0x00, 0x00, 0x00, // (sizeof children) 150 | 0x0F, 0x00, 0x00, 0x00, // (sizeof child 1) 151 | 0x02, 0x00, //(fn = 2) 152 | 0x00, 0x00, //(id = 0, value in data part) 153 | 0x1C, 0x00, //(id = 1, value = 13) 154 | 0x05, 0x00, 0x00, 0x00, // (sizeof "Alice") 155 | 0x41, 0x6C, 0x69, 0x63, 0x65, //("Alice") 156 | 0x0F, 0x00, 0x00, 0x00, // (sizeof child 2) 157 | 0x02, 0x00, //(fn = 2) 158 | 0x00, 0x00, //(id = 0, value in data part) 159 | 0x0C, 0x00, //(id = 1, value = 5) 160 | 0x05, 0x00, 0x00, 0x00, //(sizeof "Carol") 161 | 0x43, 0x61, 0x72, 0x6F, 0x6C, //("Carol") 162 | }, 163 | }, 164 | &TestCase{ 165 | Name: "NumberArray", 166 | Struct: &Data{ 167 | Numbers: []int64{1, 2, 3, 4, 5}, 168 | }, 169 | Data: []byte{ 170 | 0x01, 0x00, // (fn = 1) 171 | 0x00, 0x00, // (id = 0, value in data part) 172 | 173 | 0x15, 0x00, 0x00, 0x00, // (sizeof numbers) 174 | 0x04, //(sizeof int32) 175 | 0x01, 0x00, 0x00, 0x00, //(1) 176 | 0x02, 0x00, 0x00, 0x00, //(2) 177 | 0x03, 0x00, 0x00, 0x00, //(3) 178 | 0x04, 0x00, 0x00, 0x00, //(4) 179 | 0x05, 0x00, 0x00, 0x00, //(5) 180 | }, 181 | }, 182 | &TestCase{ 183 | Name: "BigNumberArray", 184 | Struct: &Data{ 185 | Numbers: []int64{ 186 | (1 << 32) + 1, 187 | (1 << 32) + 2, 188 | (1 << 32) + 3, 189 | }, 190 | }, 191 | Data: []byte{ 192 | 0x01, 0x00, // (fn = 1) 193 | 0x00, 0x00, // (id = 0, value in data part) 194 | 195 | 0x19, 0x00, 0x00, 0x00, // (sizeof numbers) 196 | 0x08, //(sizeof int32) 197 | 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, //((1<<32) + 1) 198 | 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, //((1<<32) + 2) 199 | 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, //((1<<32) + 3) 200 | }, 201 | }, 202 | &TestCase{ 203 | Name: "BoolArray", 204 | Struct: &Data{ 205 | Bools: []bool{false, true, false}, 206 | }, 207 | Data: []byte{ 208 | 0x02, 0x00, // (fn = 2) 209 | 0x01, 0x00, // (skip id = 0) 210 | 0x00, 0x00, // (id = 1, value in data part) 211 | 212 | 0x03, 0x00, 0x00, 0x00, // (sizeof bools) 213 | 0x00, //(false) 214 | 0x01, //(true) 215 | 0x00, //(false) 216 | }, 217 | }, 218 | &TestCase{ 219 | Name: "Bytes", 220 | Struct: &Data{ 221 | Bytes: []byte{0x28, 0x29, 0x30, 0x31}, 222 | }, 223 | Data: []byte{ 224 | 0x02, 0x00, // (fn = 2) 225 | 0x09, 0x00, // (skip id = 4) 226 | 0x00, 0x00, // (id = 5, value in data part) 227 | 228 | 0x04, 0x00, 0x00, 0x00, // (sizeof bytes) 229 | 0x28, //(0x28) 230 | 0x29, //(0x29) 231 | 0x30, //(0x30) 232 | 0x31, //(0x31) 233 | }, 234 | }, 235 | &TestCase{ 236 | Name: "StringArray", 237 | Struct: &Data{ 238 | Strings: []string{"Bob", "Alice", "Carol"}, 239 | }, 240 | Data: []byte{ 241 | 0x02, 0x00, // (fn = 2) 242 | 0x07, 0x00, // (skip id = 3) 243 | 0x00, 0x00, // (id = 4, value in data part) 244 | 245 | 0x19, 0x00, 0x00, 0x00, // (sizeof []string) 246 | 0x03, 0x00, 0x00, 0x00, // (sizeof "Bob") 247 | 0x42, 0x6F, 0x62, // ("Bob") 248 | 0x05, 0x00, 0x00, 0x00, // (sizeof "Alice") 249 | 0x41, 0x6C, 0x69, 0x63, 0x65, //("Alice") 250 | 0x05, 0x00, 0x00, 0x00, //(sizeof "Carol") 251 | 0x43, 0x61, 0x72, 0x6F, 0x6C, //("Carol") 252 | }, 253 | }, 254 | &TestCase{ 255 | Name: "Number", 256 | Struct: &Data{ 257 | Number: 100000, 258 | BigNumber: -10000000000, 259 | }, 260 | Data: []byte{ 261 | 0x03, 0x00, // (fn = 3) 262 | 0x03, 0x00, // (skip id = 1) 263 | 0x00, 0x00, // (id = 2, value in data part) 264 | 0x00, 0x00, // (id = 3, value in data part) 265 | 266 | 0x04, 0x00, 0x00, 0x00, //(sizeof number, data part) 267 | 0xA0, 0x86, 0x01, 0x00, //(100000, 32bit integer) 268 | 269 | 0x08, 0x00, 0x00, 0x00, //(sizeof bignumber, data part) 270 | 0x00, 0x1C, 0xF4, 0xAB, 0xFD, 0xFF, 0xFF, 0xFF, //(-10000000000, 64bit integer) 271 | }, 272 | }, 273 | &TestCase{ 274 | Name: "AddressBook", 275 | Struct: &ab, 276 | Data: abData, 277 | }, 278 | } 279 | 280 | func TestEncode(t *testing.T) { 281 | for _, tc := range testCases { 282 | output, err := sproto.Encode(tc.Struct) 283 | if err != nil { 284 | t.Fatalf("test case *%s* failed with error:%s", tc.Name, err) 285 | } 286 | if !bytes.Equal(output, tc.Data) { 287 | t.Log("encoded:", output) 288 | t.Log("expected:", tc.Data) 289 | t.Fatalf("test case %s failed", tc.Name) 290 | } 291 | } 292 | } 293 | 294 | func TestDecode(t *testing.T) { 295 | for _, tc := range testCases { 296 | 297 | sp := reflect.New(reflect.TypeOf(tc.Struct).Elem()).Interface() 298 | used, err := sproto.Decode(tc.Data, sp) 299 | if err != nil { 300 | t.Fatalf("test case *%s* failed with error:%s", tc.Name, err) 301 | } 302 | 303 | if used != len(tc.Data) { 304 | t.Fatalf("test case *%s* failed: data length mismatch", tc.Name) 305 | } 306 | 307 | output, err := sproto.Encode(sp) 308 | if err != nil { 309 | t.Fatalf("test case *%s* failed with error:%s", tc.Name, err) 310 | } 311 | if !bytes.Equal(output, tc.Data) { 312 | t.Log("encoded:", output) 313 | t.Log("expected:", tc.Data) 314 | t.Fatalf("test case %s failed", tc.Name) 315 | } 316 | } 317 | } 318 | 319 | func BenchmarkEncode(b *testing.B) { 320 | for i := 0; i < b.N; i++ { 321 | sproto.Encode(&ab) 322 | } 323 | } 324 | 325 | func BenchmarkDecode(b *testing.B) { 326 | var ab AddressBook 327 | for i := 0; i < b.N; i++ { 328 | sproto.Decode(abData, &ab) 329 | } 330 | } 331 | 332 | func BenchmarkEncodePacked(b *testing.B) { 333 | for i := 0; i < b.N; i++ { 334 | _, err := sproto.EncodePacked(&ab) 335 | if err != nil { 336 | b.Fatal(err) 337 | } 338 | } 339 | } 340 | 341 | func BenchmarkDecodePacked(b *testing.B) { 342 | var ab AddressBook 343 | for i := 0; i < b.N; i++ { 344 | if err := sproto.DecodePacked(abDataPacked, &ab); err != nil { 345 | b.Fatal(err) 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /example/addressbook_gen.cs: -------------------------------------------------------------------------------- 1 | // Generated by github.com/davyxu/gosproto/sprotogen 2 | // DO NOT EDIT! 3 | using System; 4 | using Sproto; 5 | using System.Collections.Generic; 6 | 7 | namespace example 8 | { 9 | 10 | public enum MyCar { 11 | 12 | Monkey = 0, 13 | 14 | Monk = 1, 15 | 16 | Pig = 2, 17 | 18 | } 19 | 20 | 21 | 22 | 23 | public class AddressBook : SprotoTypeBase { 24 | private static int max_field_count = 1; 25 | 26 | 27 | [SprotoHasField] 28 | public bool HasPerson{ 29 | get { return base.has_field.has_field(0); } 30 | } 31 | 32 | private List _person; // tag 0 33 | public List person { 34 | get{ return _person; } 35 | set{ base.has_field.set_field(0,true); _person = value; } 36 | } 37 | 38 | 39 | public AddressBook() : base(max_field_count) {} 40 | 41 | public AddressBook(byte[] buffer) : base(max_field_count, buffer) { 42 | this.decode (); 43 | } 44 | 45 | protected override void decode () { 46 | int tag = -1; 47 | while (-1 != (tag = base.deserialize.read_tag ())) { 48 | switch (tag) { 49 | 50 | case 0: 51 | this.person = base.deserialize.read_obj_list(); 52 | break; 53 | 54 | default: 55 | base.deserialize.read_unknow_data (); 56 | break; 57 | } 58 | } 59 | } 60 | 61 | public override int encode (SprotoStream stream) { 62 | base.serialize.open (stream); 63 | 64 | 65 | if (base.has_field.has_field (0)) { 66 | base.serialize.write_obj(this.person, 0); 67 | } 68 | 69 | 70 | return base.serialize.close (); 71 | } 72 | } 73 | 74 | 75 | public class MyData : SprotoTypeBase { 76 | private static int max_field_count = 10; 77 | 78 | 79 | [SprotoHasField] 80 | public bool HasName{ 81 | get { return base.has_field.has_field(0); } 82 | } 83 | 84 | private string _name; // tag 0 85 | public string name { 86 | get{ return _name; } 87 | set{ base.has_field.set_field(0,true); _name = value; } 88 | } 89 | 90 | [SprotoHasField] 91 | public bool HasType{ 92 | get { return base.has_field.has_field(1); } 93 | } 94 | 95 | private MyCar _Type; // tag 1 96 | public MyCar Type { 97 | get{ return _Type; } 98 | set{ base.has_field.set_field(1,true); _Type = value; } 99 | } 100 | 101 | [SprotoHasField] 102 | public bool HasInt32{ 103 | get { return base.has_field.has_field(2); } 104 | } 105 | 106 | private Int32 _Int32; // tag 2 107 | public Int32 Int32 { 108 | get{ return _Int32; } 109 | set{ base.has_field.set_field(2,true); _Int32 = value; } 110 | } 111 | 112 | [SprotoHasField] 113 | public bool HasUint32{ 114 | get { return base.has_field.has_field(3); } 115 | } 116 | 117 | private UInt32 _Uint32; // tag 3 118 | public UInt32 Uint32 { 119 | get{ return _Uint32; } 120 | set{ base.has_field.set_field(3,true); _Uint32 = value; } 121 | } 122 | 123 | [SprotoHasField] 124 | public bool HasInt64{ 125 | get { return base.has_field.has_field(4); } 126 | } 127 | 128 | private Int64 _Int64; // tag 4 129 | public Int64 Int64 { 130 | get{ return _Int64; } 131 | set{ base.has_field.set_field(4,true); _Int64 = value; } 132 | } 133 | 134 | [SprotoHasField] 135 | public bool HasUint64{ 136 | get { return base.has_field.has_field(5); } 137 | } 138 | 139 | private UInt64 _Uint64; // tag 5 140 | public UInt64 Uint64 { 141 | get{ return _Uint64; } 142 | set{ base.has_field.set_field(5,true); _Uint64 = value; } 143 | } 144 | 145 | [SprotoHasField] 146 | public bool HasBool{ 147 | get { return base.has_field.has_field(6); } 148 | } 149 | 150 | private bool _Bool; // tag 6 151 | public bool Bool { 152 | get{ return _Bool; } 153 | set{ base.has_field.set_field(6,true); _Bool = value; } 154 | } 155 | 156 | [SprotoHasField] 157 | public bool HasFloat32{ 158 | get { return base.has_field.has_field(7); } 159 | } 160 | 161 | private float _Float32; // tag 7 162 | public float Float32 { 163 | get{ return _Float32; } 164 | set{ base.has_field.set_field(7,true); _Float32 = value; } 165 | } 166 | 167 | [SprotoHasField] 168 | public bool HasFloat64{ 169 | get { return base.has_field.has_field(8); } 170 | } 171 | 172 | private double _Float64; // tag 8 173 | public double Float64 { 174 | get{ return _Float64; } 175 | set{ base.has_field.set_field(8,true); _Float64 = value; } 176 | } 177 | 178 | [SprotoHasField] 179 | public bool HasStream{ 180 | get { return base.has_field.has_field(9); } 181 | } 182 | 183 | private byte[] _Stream; // tag 9 184 | public byte[] Stream { 185 | get{ return _Stream; } 186 | set{ base.has_field.set_field(9,true); _Stream = value; } 187 | } 188 | 189 | 190 | public MyData() : base(max_field_count) {} 191 | 192 | public MyData(byte[] buffer) : base(max_field_count, buffer) { 193 | this.decode (); 194 | } 195 | 196 | protected override void decode () { 197 | int tag = -1; 198 | while (-1 != (tag = base.deserialize.read_tag ())) { 199 | switch (tag) { 200 | 201 | case 0: 202 | this.name = base.deserialize.read_string(); 203 | break; 204 | 205 | case 1: 206 | this.Type = base.deserialize.read_enum(); 207 | break; 208 | 209 | case 2: 210 | this.Int32 = base.deserialize.read_int32(); 211 | break; 212 | 213 | case 3: 214 | this.Uint32 = base.deserialize.read_uint32(); 215 | break; 216 | 217 | case 4: 218 | this.Int64 = base.deserialize.read_int64(); 219 | break; 220 | 221 | case 5: 222 | this.Uint64 = base.deserialize.read_uint64(); 223 | break; 224 | 225 | case 6: 226 | this.Bool = base.deserialize.read_boolean(); 227 | break; 228 | 229 | case 7: 230 | this.Float32 = base.deserialize.read_float32(); 231 | break; 232 | 233 | case 8: 234 | this.Float64 = base.deserialize.read_double(); 235 | break; 236 | 237 | case 9: 238 | this.Stream = base.deserialize.read_bytes(); 239 | break; 240 | 241 | default: 242 | base.deserialize.read_unknow_data (); 243 | break; 244 | } 245 | } 246 | } 247 | 248 | public override int encode (SprotoStream stream) { 249 | base.serialize.open (stream); 250 | 251 | 252 | if (base.has_field.has_field (0)) { 253 | base.serialize.write_string(this.name, 0); 254 | } 255 | 256 | if (base.has_field.has_field (1)) { 257 | base.serialize.write_enum(this.Type, 1); 258 | } 259 | 260 | if (base.has_field.has_field (2)) { 261 | base.serialize.write_int32(this.Int32, 2); 262 | } 263 | 264 | if (base.has_field.has_field (3)) { 265 | base.serialize.write_uint32(this.Uint32, 3); 266 | } 267 | 268 | if (base.has_field.has_field (4)) { 269 | base.serialize.write_int64(this.Int64, 4); 270 | } 271 | 272 | if (base.has_field.has_field (5)) { 273 | base.serialize.write_uint64(this.Uint64, 5); 274 | } 275 | 276 | if (base.has_field.has_field (6)) { 277 | base.serialize.write_boolean(this.Bool, 6); 278 | } 279 | 280 | if (base.has_field.has_field (7)) { 281 | base.serialize.write_float32(this.Float32, 7); 282 | } 283 | 284 | if (base.has_field.has_field (8)) { 285 | base.serialize.write_double(this.Float64, 8); 286 | } 287 | 288 | if (base.has_field.has_field (9)) { 289 | base.serialize.write_bytes(this.Stream, 9); 290 | } 291 | 292 | 293 | return base.serialize.close (); 294 | } 295 | } 296 | 297 | 298 | public class MyProfile : SprotoTypeBase { 299 | private static int max_field_count = 3; 300 | 301 | 302 | [SprotoHasField] 303 | public bool HasNameField{ 304 | get { return base.has_field.has_field(0); } 305 | } 306 | 307 | private MyData _nameField; // tag 0 308 | public MyData nameField { 309 | get{ return _nameField; } 310 | set{ base.has_field.set_field(0,true); _nameField = value; } 311 | } 312 | 313 | [SprotoHasField] 314 | public bool HasNameArray{ 315 | get { return base.has_field.has_field(1); } 316 | } 317 | 318 | private List _nameArray; // tag 1 319 | public List nameArray { 320 | get{ return _nameArray; } 321 | set{ base.has_field.set_field(1,true); _nameArray = value; } 322 | } 323 | 324 | [SprotoHasField] 325 | public bool HasNameMap{ 326 | get { return base.has_field.has_field(2); } 327 | } 328 | 329 | private Dictionary _nameMap; // tag 2 330 | public Dictionary nameMap { 331 | get{ return _nameMap; } 332 | set{ base.has_field.set_field(2,true); _nameMap = value; } 333 | } 334 | 335 | 336 | public MyProfile() : base(max_field_count) {} 337 | 338 | public MyProfile(byte[] buffer) : base(max_field_count, buffer) { 339 | this.decode (); 340 | } 341 | 342 | protected override void decode () { 343 | int tag = -1; 344 | while (-1 != (tag = base.deserialize.read_tag ())) { 345 | switch (tag) { 346 | 347 | case 0: 348 | this.nameField = base.deserialize.read_obj(); 349 | break; 350 | 351 | case 1: 352 | this.nameArray = base.deserialize.read_obj_list(); 353 | break; 354 | 355 | case 2: 356 | this.nameMap = base.deserialize.read_map(v => v.Type); 357 | break; 358 | 359 | default: 360 | base.deserialize.read_unknow_data (); 361 | break; 362 | } 363 | } 364 | } 365 | 366 | public override int encode (SprotoStream stream) { 367 | base.serialize.open (stream); 368 | 369 | 370 | if (base.has_field.has_field (0)) { 371 | base.serialize.write_obj(this.nameField, 0); 372 | } 373 | 374 | if (base.has_field.has_field (1)) { 375 | base.serialize.write_obj(this.nameArray, 1); 376 | } 377 | 378 | if (base.has_field.has_field (2)) { 379 | base.serialize.write_obj(this.nameMap, 2); 380 | } 381 | 382 | 383 | return base.serialize.close (); 384 | } 385 | } 386 | 387 | 388 | public class Person : SprotoTypeBase { 389 | private static int max_field_count = 4; 390 | 391 | 392 | [SprotoHasField] 393 | public bool HasName{ 394 | get { return base.has_field.has_field(0); } 395 | } 396 | 397 | private string _name; // tag 0 398 | public string name { 399 | get{ return _name; } 400 | set{ base.has_field.set_field(0,true); _name = value; } 401 | } 402 | 403 | [SprotoHasField] 404 | public bool HasId{ 405 | get { return base.has_field.has_field(1); } 406 | } 407 | 408 | private Int32 _id; // tag 1 409 | public Int32 id { 410 | get{ return _id; } 411 | set{ base.has_field.set_field(1,true); _id = value; } 412 | } 413 | 414 | [SprotoHasField] 415 | public bool HasEmail{ 416 | get { return base.has_field.has_field(2); } 417 | } 418 | 419 | private string _email; // tag 2 420 | public string email { 421 | get{ return _email; } 422 | set{ base.has_field.set_field(2,true); _email = value; } 423 | } 424 | 425 | [SprotoHasField] 426 | public bool HasPhone{ 427 | get { return base.has_field.has_field(3); } 428 | } 429 | 430 | private List _phone; // tag 3 431 | public List phone { 432 | get{ return _phone; } 433 | set{ base.has_field.set_field(3,true); _phone = value; } 434 | } 435 | 436 | 437 | public Person() : base(max_field_count) {} 438 | 439 | public Person(byte[] buffer) : base(max_field_count, buffer) { 440 | this.decode (); 441 | } 442 | 443 | protected override void decode () { 444 | int tag = -1; 445 | while (-1 != (tag = base.deserialize.read_tag ())) { 446 | switch (tag) { 447 | 448 | case 0: 449 | this.name = base.deserialize.read_string(); 450 | break; 451 | 452 | case 1: 453 | this.id = base.deserialize.read_int32(); 454 | break; 455 | 456 | case 2: 457 | this.email = base.deserialize.read_string(); 458 | break; 459 | 460 | case 3: 461 | this.phone = base.deserialize.read_obj_list(); 462 | break; 463 | 464 | default: 465 | base.deserialize.read_unknow_data (); 466 | break; 467 | } 468 | } 469 | } 470 | 471 | public override int encode (SprotoStream stream) { 472 | base.serialize.open (stream); 473 | 474 | 475 | if (base.has_field.has_field (0)) { 476 | base.serialize.write_string(this.name, 0); 477 | } 478 | 479 | if (base.has_field.has_field (1)) { 480 | base.serialize.write_int32(this.id, 1); 481 | } 482 | 483 | if (base.has_field.has_field (2)) { 484 | base.serialize.write_string(this.email, 2); 485 | } 486 | 487 | if (base.has_field.has_field (3)) { 488 | base.serialize.write_obj(this.phone, 3); 489 | } 490 | 491 | 492 | return base.serialize.close (); 493 | } 494 | } 495 | 496 | 497 | public class PhoneNumber : SprotoTypeBase { 498 | private static int max_field_count = 2; 499 | 500 | 501 | [SprotoHasField] 502 | public bool HasNumber{ 503 | get { return base.has_field.has_field(0); } 504 | } 505 | 506 | private string _number; // tag 0 507 | public string number { 508 | get{ return _number; } 509 | set{ base.has_field.set_field(0,true); _number = value; } 510 | } 511 | 512 | [SprotoHasField] 513 | public bool HasType{ 514 | get { return base.has_field.has_field(1); } 515 | } 516 | 517 | private Int32 _type; // tag 1 518 | public Int32 type { 519 | get{ return _type; } 520 | set{ base.has_field.set_field(1,true); _type = value; } 521 | } 522 | 523 | 524 | public PhoneNumber() : base(max_field_count) {} 525 | 526 | public PhoneNumber(byte[] buffer) : base(max_field_count, buffer) { 527 | this.decode (); 528 | } 529 | 530 | protected override void decode () { 531 | int tag = -1; 532 | while (-1 != (tag = base.deserialize.read_tag ())) { 533 | switch (tag) { 534 | 535 | case 0: 536 | this.number = base.deserialize.read_string(); 537 | break; 538 | 539 | case 1: 540 | this.type = base.deserialize.read_int32(); 541 | break; 542 | 543 | default: 544 | base.deserialize.read_unknow_data (); 545 | break; 546 | } 547 | } 548 | } 549 | 550 | public override int encode (SprotoStream stream) { 551 | base.serialize.open (stream); 552 | 553 | 554 | if (base.has_field.has_field (0)) { 555 | base.serialize.write_string(this.number, 0); 556 | } 557 | 558 | if (base.has_field.has_field (1)) { 559 | base.serialize.write_int32(this.type, 1); 560 | } 561 | 562 | 563 | return base.serialize.close (); 564 | } 565 | } 566 | 567 | 568 | public class RegisterEntry 569 | { 570 | static readonly Type[] _types = new Type[]{ 571 | typeof(AddressBook), // 2618161298 572 | typeof(MyData), // 2244887298 573 | typeof(MyProfile), // 438153711 574 | typeof(Person), // 1498745430 575 | typeof(PhoneNumber), // 4271979557 576 | }; 577 | 578 | public static Type[] GetClassTypes() 579 | { 580 | return _types; 581 | } 582 | 583 | } 584 | } 585 | --------------------------------------------------------------------------------