From bcb4a0680de510ba4e39f4338d73ee85c503e2d5 Mon Sep 17 00:00:00 2001 From: Greg Date: Tue, 9 Apr 2019 23:19:49 -0400 Subject: [PATCH] Identify methods from interfaces and categories, determine their parameter and return types. --- .gitignore | 2 +- ast/ast.go | 4 + ast/objc_category_decl.go | 56 ++++++++++ ast/objc_interface.go | 6 +- ast/objc_method_decl.go | 22 ++-- ast/objc_property_decl.go | 64 +++++++++++ ast/objc_protocol.go | 8 +- ast/parm_var_decl.go | 2 +- main.go | 43 ++++---- wrap/main.go | 221 +++++++++++++++++++++++++++++++++++--- 10 files changed, 377 insertions(+), 51 deletions(-) create mode 100644 ast/objc_category_decl.go create mode 100644 ast/objc_property_decl.go diff --git a/.gitignore b/.gitignore index 6bb4b82..26a15e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ clast ast.txt - +*.ast diff --git a/ast/ast.go b/ast/ast.go index b955d7a..374bf4e 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -186,6 +186,8 @@ func Parse(fullline string) Node { return parseNonNullAttr(line) case "NotTailCalledAttr": return parseNotTailCalledAttr(line) + case "ObjCCategoryDecl": + return parseObjCCategoryDecl(line) case "ObjCInterface": return parseObjCInterface(line) case "ObjCInterfaceType": @@ -206,6 +208,8 @@ func Parse(fullline string) Node { return parseObjCObjectPointerType(line) case "ObjCProtocol": return parseObjCProtocol(line) + case "ObjCPropertyDecl": + return parseObjCPropertyDecl(line) case "OffsetOfExpr": return parseOffsetOfExpr(line) case "PackedAttr": diff --git a/ast/objc_category_decl.go b/ast/objc_category_decl.go new file mode 100644 index 0000000..336a48b --- /dev/null +++ b/ast/objc_category_decl.go @@ -0,0 +1,56 @@ +package ast + +import ( +// "fmt" + "strings" +) + +// ObjCCategoryDecl is node represents a category declaration. +type ObjCCategoryDecl struct { + Addr Address + Pos Position + Position2 string + Name string + ChildNodes []Node +} + +func parseObjCCategoryDecl(line string) *ObjCCategoryDecl { + groups := groupsFromRegex( + `(?:prev (?P0x[0-9a-f]+) )? + <(?P|.*)> + (?P | col:\d+| line:\d+:\d+) + (?P \w+)?`, + line, + ) + + return &ObjCCategoryDecl{ + Addr: ParseAddress(groups["address"]), + Pos: NewPositionFromString(groups["position"]), + Position2: strings.TrimSpace(groups["position2"]), + Name: strings.TrimSpace(groups["name"]), + ChildNodes: []Node{}, + } +} + +// AddChild adds a new child node. Child nodes can then be accessed with the +// Children attribute. +func (n *ObjCCategoryDecl) AddChild(node Node) { + n.ChildNodes = append(n.ChildNodes, node) +} + +// Address returns the numeric address of the node. See the documentation for +// the Address type for more information. +func (n *ObjCCategoryDecl) Address() Address { + return n.Addr +} + +// Children returns the child nodes. If this node does not have any children or +// this node does not support children it will always return an empty slice. +func (n *ObjCCategoryDecl) Children() []Node { + return n.ChildNodes +} + +// Position returns the position in the original source code. +func (n *ObjCCategoryDecl) Position() Position { + return n.Pos +} diff --git a/ast/objc_interface.go b/ast/objc_interface.go index 7463f8f..3611b3d 100644 --- a/ast/objc_interface.go +++ b/ast/objc_interface.go @@ -3,19 +3,19 @@ package ast // ObjCInterface is an Objective-C interface type ObjCInterface struct { Addr Address - Type string + Name string ChildNodes []Node } func parseObjCInterface(line string) *ObjCInterface { groups := groupsFromRegex( - "'(?P.*)'", + "'(?P.*)'", line, ) return &ObjCInterface{ Addr: ParseAddress(groups["address"]), - Type: groups["type"], + Name: groups["name"], ChildNodes: []Node{}, } } diff --git a/ast/objc_method_decl.go b/ast/objc_method_decl.go index f9b05fa..9d2cf9e 100644 --- a/ast/objc_method_decl.go +++ b/ast/objc_method_decl.go @@ -12,7 +12,9 @@ type ObjCMethodDecl struct { Implicit bool ClassMethod bool Name string + Parameters []string Type string + Type2 string Attr string ChildNodes []Node } @@ -25,13 +27,17 @@ func parseObjCMethodDecl(line string) *ObjCMethodDecl { (?P implicit)? (?P \+| \-) (?P.*?) - (?P'.*') + '(?P[^']*?)' + (:'(?P.*?)')? (?P .*)?`, line, ) -/* - (?P | col:\d+| line:\d+:\d+)? -*/ + names := strings.TrimSpace(groups["names"]) + parts := strings.Split(strings.TrimSpace(groups["names"]),":") + params := []string{} + if names[len(names)-1] == ':' { + params = parts[:len(parts)-1] + } return &ObjCMethodDecl{ Addr: ParseAddress(groups["address"]), @@ -39,9 +45,11 @@ func parseObjCMethodDecl(line string) *ObjCMethodDecl { Position2: strings.TrimSpace(groups["position2"]), Implicit: len(groups["implicit"])>0, ClassMethod: groups["methodtype"] == " +", - Name: groups["names"], - Type: groups["type"], - Attr: groups["attr"], + Name: parts[0], + Parameters: params, + Type: strings.TrimSpace(groups["type"]), + Type2: strings.TrimSpace(groups["type2"]), + Attr: strings.TrimSpace(groups["attr"]), ChildNodes: []Node{}, } } diff --git a/ast/objc_property_decl.go b/ast/objc_property_decl.go new file mode 100644 index 0000000..646016e --- /dev/null +++ b/ast/objc_property_decl.go @@ -0,0 +1,64 @@ +package ast + +import ( + "strings" +) + +// ObjCPropertyDecl is node represents an Objective-C property declaration +type ObjCPropertyDecl struct { + Addr Address + Pos Position + Position2 string + Name string + Type string + Type2 string + Attr string + ChildNodes []Node +} + +func parseObjCPropertyDecl(line string) *ObjCPropertyDecl { + groups := groupsFromRegex( + `(?:prev (?P0x[0-9a-f]+) )? + <(?P.*.*?|.*.*?|.*|.*?)> + (?P | col:\d+| line:\d+:\d+)? + (?P.*?) + '(?P[^']*?)' + (:'(?P.*?)')? + (?P .*)?`, + line, + ) + + return &ObjCPropertyDecl{ + Addr: ParseAddress(groups["address"]), + Pos: NewPositionFromString(groups["position"]), + Position2: strings.TrimSpace(groups["position2"]), + Name: strings.TrimSpace(groups["name"]), + Type: strings.TrimSpace(groups["type"]), + Type2: strings.TrimSpace(groups["type2"]), + Attr: strings.TrimSpace(groups["attr"]), + ChildNodes: []Node{}, + } +} + +// AddChild adds a new child node. Child nodes can then be accessed with the +// Children attribute. +func (n *ObjCPropertyDecl) AddChild(node Node) { + n.ChildNodes = append(n.ChildNodes, node) +} + +// Address returns the numeric address of the node. See the documentation for +// the Address type for more information. +func (n *ObjCPropertyDecl) Address() Address { + return n.Addr +} + +// Children returns the child nodes. If this node does not have any children or +// this node does not support children it will always return an empty slice. +func (n *ObjCPropertyDecl) Children() []Node { + return n.ChildNodes +} + +// Position returns the position in the original source code. +func (n *ObjCPropertyDecl) Position() Position { + return n.Pos +} diff --git a/ast/objc_protocol.go b/ast/objc_protocol.go index 975ce74..941df85 100644 --- a/ast/objc_protocol.go +++ b/ast/objc_protocol.go @@ -3,19 +3,21 @@ package ast // ObjCProtocol is an Objective-C protocol type ObjCProtocol struct { Addr Address - Type string + Name string + Content string ChildNodes []Node } func parseObjCProtocol(line string) *ObjCProtocol { groups := groupsFromRegex( - "'(?P.*)'", + "'(?P.*)'", line, ) return &ObjCProtocol{ Addr: ParseAddress(groups["address"]), - Type: groups["type"], + Name: groups["name"], + Content: groups["content"], ChildNodes: []Node{}, } } diff --git a/ast/parm_var_decl.go b/ast/parm_var_decl.go index 8fe8a6b..3b423ef 100644 --- a/ast/parm_var_decl.go +++ b/ast/parm_var_decl.go @@ -44,7 +44,7 @@ func parseParmVarDecl(line string) *ParmVarDecl { return &ParmVarDecl{ Addr: ParseAddress(groups["address"]), - Pos: NewPositionFromString(groups["position"]), + Pos: NewPositionFromString(groups["position"]), Position2: strings.TrimSpace(groups["position2"]), Name: strings.TrimSpace(groups["name"]), Type: groups["type"], diff --git a/main.go b/main.go index 8d8d2ad..b651443 100644 --- a/main.go +++ b/main.go @@ -207,11 +207,12 @@ func Start(args ProgramArgs) (err error) { for _, n := range(unit.Children()) { switch x := n.(type) { case *ast.ObjCInterfaceDecl: - if x.Name == "CBPeripheral" { - w.Wrap(x) - } + w.AddInterface(x) + case *ast.ObjCCategoryDecl: + w.AddCategory(x) } } + w.Wrap() return nil } @@ -232,19 +233,19 @@ func (i *inputDataFlags) Set(value string) error { var clangFlags inputDataFlags func init() { - transpileCommand.Var(&clangFlags, "clang-flag", "Pass arguments to clang. You may provide multiple -clang-flag items.") + wrapCommand.Var(&clangFlags, "clang-flag", "Pass arguments to clang. You may provide multiple -clang-flag items.") astCommand.Var(&clangFlags, "clang-flag", "Pass arguments to clang. You may provide multiple -clang-flag items.") } var ( - versionFlag = flag.Bool("v", false, "print the version and exit") - transpileCommand = flag.NewFlagSet("transpile", flag.ContinueOnError) - verboseFlag = transpileCommand.Bool("V", false, "print progress as comments") - outputFlag = transpileCommand.String("o", "", "output Go generated code to the specified file") - packageFlag = transpileCommand.String("p", "main", "set the name of the generated package") - transpileHelpFlag = transpileCommand.Bool("h", false, "print help information") - astCommand = flag.NewFlagSet("ast", flag.ContinueOnError) - astHelpFlag = astCommand.Bool("h", false, "print help information") + versionFlag = flag.Bool("v", false, "print the version and exit") + wrapCommand = flag.NewFlagSet("wrap", flag.ContinueOnError) + verboseFlag = wrapCommand.Bool("V", false, "print progress as comments") + outputFlag = wrapCommand.String("o", "", "output Go generated code to the specified file") + packageFlag = wrapCommand.String("p", "main", "set the name of the generated package") + wrapHelpFlag = wrapCommand.Bool("h", false, "print help information") + astCommand = flag.NewFlagSet("ast", flag.ContinueOnError) + astHelpFlag = astCommand.Bool("h", false, "print help information") ) func main() { @@ -259,8 +260,8 @@ func runCommand() int { flag.Usage = func() { usage := "Usage: %s [-v] [] [] file1.c ...\n\n" usage += "Commands:\n" - usage += " transpile\ttranspile an input C source file or files to Go\n" - usage += " ast\t\tprint AST before translated Go code\n\n" + usage += " wrap\twrap an Objective-C interface for Go\n" + usage += " ast\t\tprint AST\n\n" usage += "Flags:\n" fmt.Printf(usage, os.Args[0]) @@ -299,20 +300,20 @@ func runCommand() int { args.ast = true args.inputFiles = astCommand.Args() args.clangFlags = clangFlags - case "transpile": - err := transpileCommand.Parse(os.Args[2:]) + case "wrap": + err := wrapCommand.Parse(os.Args[2:]) if err != nil { - fmt.Printf("transpile command cannot parse: %v", err) + fmt.Printf("wrap command cannot parse: %v", err) return 1 } - if *transpileHelpFlag || transpileCommand.NArg() == 0 { - fmt.Printf("Usage: %s transpile [-V] [-o file.go] [-p package] file1.c ...\n", os.Args[0]) - transpileCommand.PrintDefaults() + if *wrapHelpFlag || wrapCommand.NArg() == 0 { + fmt.Printf("Usage: %s wrap [-V] [-o file.go] [-p package] file1.c ...\n", os.Args[0]) + wrapCommand.PrintDefaults() return 1 } - args.inputFiles = transpileCommand.Args() + args.inputFiles = wrapCommand.Args() args.outputFile = *outputFlag args.packageName = *packageFlag args.verbose = *verboseFlag diff --git a/wrap/main.go b/wrap/main.go index 5297d21..db0c23a 100644 --- a/wrap/main.go +++ b/wrap/main.go @@ -2,28 +2,219 @@ package wrap import ( "fmt" - "reflect" + //"reflect" + "strings" "gitlab.wow.st/gmp/clast/ast" ) type Wrapper struct { + Interfaces map[string]Interface + Types map[string]string +} + +// translate C builtin types to CGo +var builtinTypes map[string]string = map[string]string{ + "char": "C.char", + "signed char": "C.schar", + "unsigned char": "C.uchar", + "short": "C.short", + "unsigned short": "C.ushort", + "int": "C.int", + "unsigned int": "C.uint", + "long": "C.long", + "unsigned long": "C.ulong", + "long long": "C.longlong", + "unsigned long long": "C.ulonglong", + "float": "C.float", + "double": "C.double", + "complex float": "C.complexfloat", + "complex double": "C.complexdouble", +} + +func (w *Wrapper) AddType(t string) { + // only for objects and pointers? + // strip off pointers, < > and blank space + nt := strings.ReplaceAll(t,"*","") + if len(nt) > 3 && nt[0:3] == "id<" { + nt = t[3:len(t)-1] + } + nt = strings.ReplaceAll(nt,"<","_") + nt = strings.ReplaceAll(nt,">","_") + if _,ok := builtinTypes[nt]; ok { // do not add builtin types + return + } + if len(nt) > 5 && nt[0:5] == "enum " { // FIXME: deal with enums? + return + } + nt = strings.ReplaceAll(nt," ","") + if nt == "void" { + return + } + w.Types[nt] = t } func NewWrapper() *Wrapper { - return &Wrapper{} -} - -func (w *Wrapper) Wrap(n *ast.ObjCInterfaceDecl) { - fmt.Println("//generated by gitlab.wow.st/gmp/clast") - for _,c := range n.Children() { - switch x := c.(type) { - case *ast.Unknown: - fmt.Printf("%s: %s\n",x.Name,x.Content) - case *ast.ObjCMethodDecl: - fmt.Printf("*ast.ObjCMethodDecl: %s (%s %s)\n",x.Name,x.Type, x.Attr) - default: - fmt.Println(reflect.TypeOf(x)) - } + return &Wrapper{ + Interfaces: map[string]Interface{}, + Types: map[string]string{}, } } + +type Property struct { + Name, Type, Type2, Attr string +} + +type Parameter struct { + Name, Type, Type2 string +} + +type Method struct { + Name, Type, Type2 string + Parameters map[string]Parameter +} + +type Interface struct { + Name string + Properties map[string]Property + Methods map[string]Method +} + +func (w *Wrapper) AddInterface(n *ast.ObjCInterfaceDecl) { + //fmt.Printf("ast.ObjCInterfaceDecl: %s\n",n.Name) + w.add(n.Name, n.Children()) +} + +func (w *Wrapper) AddCategory(n *ast.ObjCCategoryDecl) { + ns := n.Children() + switch x := ns[0].(type) { + case *ast.ObjCInterface: + w.add(x.Name, ns[1:]) + default: + fmt.Printf("Not adding methods for %s: interface name not found in first child node of category defclaration\n",n.Name) + } +} + +func (w *Wrapper) add(name string, ns []ast.Node) { + var i Interface + var ok bool + if i,ok = w.Interfaces[name]; !ok { + i = Interface{ + Name: name, + Properties: map[string]Property{}, + Methods: map[string]Method{}, + } + } + for _,c := range ns { + switch x := c.(type) { + case *ast.ObjCPropertyDecl: + p := Property{ + Name: x.Name, + Type: x.Type, + Type2: x.Type2, + } + w.AddType(typeOrType2(x.Type,x.Type2)) + i.Properties[p.Name] = p + case *ast.ObjCMethodDecl: + fmt.Printf("ObjcMethodDecl: %s\n",x.Name) + m := Method{ + Name: x.Name, + Type: x.Type, + Type2: x.Type2, + } + w.AddType(typeOrType2(x.Type,x.Type2)) + m.Parameters = w.GetParms(x) + i.Methods[m.Name] = m + case *ast.ObjCProtocol: + //fmt.Printf("ast.ObjCProtocol: %s\n",x.Name) + case *ast.ObjCInterface: + //fmt.Printf("ast.ObjCInterface: %s\n",x.Name) + case *ast.Unknown: + //fmt.Printf("(*ast.Unkonwn %s: %s)\n",x.Name,x.Content) + default: + //fmt.Println(reflect.TypeOf(x)) + } + } + w.Interfaces[i.Name] = i +} + +func (w *Wrapper) GetParms(n *ast.ObjCMethodDecl) map[string]Parameter { + ps := make([]Parameter,0) + avail := make([][]string,len(n.Children())) + var c ast.Node + i := -1 + for i,c = range n.Children() { + switch x := c.(type) { + case *ast.ParmVarDecl: + i++ + p := Parameter{ + Type: x.Type, + Type2: x.Type2, + } + ps = append(ps,p) + avail = append(avail,[]string{}) + case *ast.AvailabilityAttr: + avail[i] = append(avail[i],x.OS) + } + } + isAvail := func(l []string) bool { + if len(l) == 0 { + return true + } + for _,x := range l { + if x == "macos" { + return true + } + } + return false + } + ret := make(map[string]Parameter) + j := 0 + for i,p := range ps { + if isAvail(avail[i]) { + ret[n.Parameters[j]] = p + j++ + } + } + for _,p := range ret { + w.AddType(typeOrType2(p.Type,p.Type2)) + } + if j != len(n.Parameters) { + fmt.Printf("Error in method declaration %s: Wrong number of ParmVarDecl children: %d parameters but %d ParmVarDecl children\n",n.Name,len(n.Parameters),i) + } + return ret +} + +func typeOrType2(t1, t2 string) string { + if t2 != "" { + return t2 + } + return t1 +} + +func (w *Wrapper) Wrap() { + //var cCode strings.Builder + var goCode strings.Builder + + for k,t := range w.Types { // FIXME: SORT FIRST + w := fmt.Sprintf(` +// %s +type %s struct { ptr unsafe.Pointer } +`,t, k) + goCode.WriteString(w) + } + for k,v := range w.Interfaces { + fmt.Printf("Interface %s: %d properties, %d methods\n", + k, len(v.Properties), len(v.Methods)) + for _,y := range v.Properties { + fmt.Printf(" property: %s (%s)\n", y.Name, typeOrType2(y.Type,y.Type2)) + } + for _,y := range v.Methods { + fmt.Printf(" method: %s (%s)\n", y.Name, typeOrType2(y.Type,y.Type2)) + for _,z := range y.Parameters { + fmt.Printf(" %s:%s\n", z.Name, typeOrType2(z.Type,z.Type2)) + } + } + } + fmt.Println(goCode.String()) +}