// Copyright (C) 2016 Nexedi SA and Contributors. // Kirill Smelkov <kirr@nexedi.com> // // This program is free software: you can Use, Study, Modify and Redistribute // it under the terms of the GNU General Public License version 2, or (at your // option) any later version, as published by the Free Software Foundation. // // This program is distributed WITHOUT ANY WARRANTY; without even the implied // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // // See COPYING file for full licensing terms. // NEO. Protocol definition. Code generator // TODO text what it does (generates code for proto.go) // +build ignore package main import ( "fmt" "go/ast" "go/parser" "go/token" "go/types" "log" ) // information about one packet type type PacketType struct { name string msgCode uint16 // message code for this packet type - derived from type order number in source } var fset = token.NewFileSet() var info = &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Uses: make(map[*ast.Ident]types.Object), Defs: make(map[*ast.Ident]types.Object), } // complete position of a node func pos(n ast.Node) { return fset.Position(n.Pos()) } func main() { typeMap := map[string]*PacketType{} // XXX needed ? // go through proto.go and collect packets type definitions var mode parser.Mode = 0 // parser.Trace f, err := parser.ParseFile(fset, "proto.go", nil, mode) if err != nil { log.Fatal(err) // parse error } conf := types.Config{} pkg, err := conf.Check("proto", fset, []*ast.File{f}, info) if err != nil { log.Fatal(err) // typecheck error } ncode := 0 //ast.Print(fset, f) //return for _, decl := range f.Decls { // we look for types (which can be only under GenDecl) gendecl, ok := decl.(*ast.GenDecl) if !ok || gendecl.Tok != token.TYPE { continue } for _, spec := range gendecl.Specs { typespec := spec.(*ast.TypeSpec) // must be because tok = TYPE typename := typespec.Name.Name switch t := typespec.Type.(type) { default: // we are only interested in struct types continue case *ast.StructType: //fmt.Printf("\n%s:\n", typename) //fmt.Println(t) //ast.Print(fset, t) PacketType{name: typename, msgCode: ncode} // if ncode != 0 { // fmt.Println() // } for _, fieldv := range t.Fields.List { // we only support simple types like uint16 ftype, ok := fieldv.Type.(*ast.Ident) if !ok { // TODO log // TODO proper error message panic(fmt.Sprintf("%#v not supported", fieldv.Type)) } if len(fieldv.Names) != 0 { for _, field := range fieldv.Names { fmt.Printf("%s(%d).%s\t%s\n", typename, ncode, field.Name, ftype) } } else { // no names means embedding fmt.Printf("%s(%d).<%s>\n", typename, ncode, ftype) } } ncode++ } } //fmt.Println(gdecl) //ast.Print(fset, gdecl) } } // wiresize returns wire size of a type // type must be of fixed size (e.g. not a slice or map) // XXX ast.Expr -> ? func wiresize(*ast.Expr) int { // TODO } func gendecode(typespec *ast.TypeSpec) string { buf := butes.Buffer{} typename := typespec.Name.Name t := typespec.Type.(*ast.StructType) // must be fmt.Fprintf(&buf, "func (p *%s) NEODecode(data []byte) int {\n", typename) n := 0 // current decode pos in data for _, fieldv := t.Fields.List { // type B struct { ... } // // type A struct { // x, y int <- fieldv // B <- fieldv // embedding: change `B` -> `B B` (field type must be Ident) fieldnamev := fieldv.Names if fieldnamev == nil { fieldnamev = []*ast.Ident{fieldv.Type.(*ast.Ident)} } for fieldname := range fieldnamev { switch fieldtype := fieldv.Type.(type) { // we are processing: <fieldname> <fieldtype> // simple types like uint16 case *ast.Ident: // TODO // array or slice case *ast.ArrayType: if fieldtype.Len != nil { log.Fatalf("%s: TODO arrays not suported", pos(fieldtype)) } eltsize := wiresize(fieldtype.Elt) // TODO // len u32 // [len] items emit("length = Uint32(data[%s:])", n) n += 4 emit("for ; length != 0; length-- {") emit("}") // map case *ast.MapType: // len u32 // [len] key, value emit("length = Uint32(data[%s:])", n) n += 4 keysize := wiresize(fieldtype.Key) valsize := wiresize(fieldtype.Value) // XXX *ast.StructType ? default: panic() // TODO } } } fmt.Fprintf(&buf, "}\n") // TODO format.Source(buf.Bytes()) (XXX -> better at top-level for whole file) return buf.String() }