From 2fbd9134a032972bfd3c9bf7a6dfe4f6f0a0746c Mon Sep 17 00:00:00 2001 From: Greg Date: Wed, 10 Apr 2019 14:00:48 -0400 Subject: [PATCH] Generate go and objc wrapper functions. Translate classes into wrapped unsafe.Pointer() instances. Embed super-class to simulate inheritance and hold pointers only at the base class. --- .gitignore | 1 + ast/ast.go | 4 +- ast/objc_implementation.go | 44 ++++++ ast/objc_interface.go | 8 ++ main.go | 2 +- wrap/main.go | 284 ++++++++++++++++++++++++++++++++----- 6 files changed, 309 insertions(+), 34 deletions(-) create mode 100644 ast/objc_implementation.go diff --git a/.gitignore b/.gitignore index 26a15e3..3bf9d09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ clast ast.txt *.ast +simple diff --git a/ast/ast.go b/ast/ast.go index 374bf4e..245262d 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -188,12 +188,14 @@ func Parse(fullline string) Node { return parseNotTailCalledAttr(line) case "ObjCCategoryDecl": return parseObjCCategoryDecl(line) + case "ObjCImplementation": + return parseObjCImplementation(line) case "ObjCInterface": return parseObjCInterface(line) case "ObjCInterfaceType": return parseObjCInterfaceType(line) case "super ObjCInterface": - return parseObjCInterface(line) + return parseSuperObjCInterface(line) case "ObjCInterfaceDecl": return parseObjCInterfaceDecl(line) case "getter ObjCMethod": diff --git a/ast/objc_implementation.go b/ast/objc_implementation.go new file mode 100644 index 0000000..b05d760 --- /dev/null +++ b/ast/objc_implementation.go @@ -0,0 +1,44 @@ +package ast + +// ObjCImplementation is an Objective-C implementation +type ObjCImplementation struct { + Addr Address + Name string + ChildNodes []Node +} + +func parseObjCImplementation(line string) *ObjCImplementation { + groups := groupsFromRegex( + "'(?P.*)'", + line, + ) + + return &ObjCImplementation{ + Addr: ParseAddress(groups["address"]), + Name: groups["name"], + ChildNodes: []Node{}, + } +} + +// AddChild adds a new child node. Child nodes can then be accessed with the +// Children attribute. +func (n *ObjCImplementation) 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 *ObjCImplementation) 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 *ObjCImplementation) Children() []Node { + return n.ChildNodes +} + +// Position returns the position in the original source code. +func (n *ObjCImplementation) Position() Position { + return Position{} +} diff --git a/ast/objc_interface.go b/ast/objc_interface.go index 3611b3d..b410c61 100644 --- a/ast/objc_interface.go +++ b/ast/objc_interface.go @@ -4,9 +4,16 @@ package ast type ObjCInterface struct { Addr Address Name string + Super bool ChildNodes []Node } +func parseSuperObjCInterface(line string) *ObjCInterface { + ret := parseObjCInterface(line) + ret.Super = true + return ret +} + func parseObjCInterface(line string) *ObjCInterface { groups := groupsFromRegex( "'(?P.*)'", @@ -16,6 +23,7 @@ func parseObjCInterface(line string) *ObjCInterface { return &ObjCInterface{ Addr: ParseAddress(groups["address"]), Name: groups["name"], + Super: false, ChildNodes: []Node{}, } } diff --git a/main.go b/main.go index b651443..0cba3d2 100644 --- a/main.go +++ b/main.go @@ -212,7 +212,7 @@ func Start(args ProgramArgs) (err error) { w.AddCategory(x) } } - w.Wrap() + w.Wrap("ClassOne") return nil } diff --git a/wrap/main.go b/wrap/main.go index db0c23a..53ec15e 100644 --- a/wrap/main.go +++ b/wrap/main.go @@ -8,12 +8,45 @@ import ( "gitlab.wow.st/gmp/clast/ast" ) +var ( + Debug = false +) + type Wrapper struct { Interfaces map[string]Interface Types map[string]string + cCode strings.Builder // put cGo code here + goTypes strings.Builder // put Go type declarations here + goCode strings.Builder // put Go code here + Processed map[string]bool } // translate C builtin types to CGo +var builtinTypes map[string]string = map[string]string{ + "char": "byte", + "signed char": "byte", + "unsigned char": "byte", + "short": "int", + "unsigned short": "int", + "int": "int", + "unsigned int": "int", + "long": "int", + "unsigned long": "int", + "long long": "int", + "unsigned long long": "int", + "float": "float", + "double": "float", + "complex float": "C.complexfloat", + "complex double": "C.complexdouble", +} + +var gobuiltinTypes map[string]bool = map[string]bool{ + "byte": true, + "int": true, + "float": true, +} + +/* var builtinTypes map[string]string = map[string]string{ "char": "C.char", "signed char": "C.schar", @@ -30,28 +63,42 @@ var builtinTypes map[string]string = map[string]string{ "double": "C.double", "complex float": "C.complexfloat", "complex double": "C.complexdouble", -} +}*/ func (w *Wrapper) AddType(t string) { - // only for objects and pointers? + if _,ok := builtinTypes[t]; ok { + return + } + nt, err := goType(t) + if err != nil { + return + } + w.Types[nt] = t +} + +func goType(t string) (string, error) { // 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 + nt = strings.ReplaceAll(nt,"<","__") + nt = strings.ReplaceAll(nt,">","__") + nt = strings.ReplaceAll(nt,",","_") + if x,ok := builtinTypes[nt]; ok { // do not add builtin types + return x, nil } if len(nt) > 5 && nt[0:5] == "enum " { // FIXME: deal with enums? - return + return "", fmt.Errorf("goType(): enum") } nt = strings.ReplaceAll(nt," ","") if nt == "void" { - return + return "", fmt.Errorf("goType(): void") } - w.Types[nt] = t + if nt[len(nt)-1] == ')' { // skip function pointers + return "", fmt.Errorf("goType(): function pointer") + } + return nt, nil } func NewWrapper() *Wrapper { @@ -74,12 +121,64 @@ type Method struct { Parameters map[string]Parameter } +func (m Method) isVoid() bool { + return typeOrType2(m.Type,m.Type2) == "void" +} + +func (m Method) cparamlist() string { + ret := []string{"void* obj"} + for k,v := range m.Parameters { + ret = append(ret,fmt.Sprintf("%s %s",typeOrType2(v.Type,v.Type2),k)) + } + return strings.Join(ret,", ") +} + +func (m Method) objcparamlist() string { + if len(m.Parameters) == 0 { + return m.Name + } + ret := []string{fmt.Sprintf("%s:",m.Name)} + for k,_ := range m.Parameters { + ret = append(ret, fmt.Sprintf("%s:%s",k,k)) + } + return strings.Join(ret," ") +} + +func (m Method) goparamlist() (string,[]string) { + ret := []string{} + tps := []string{} + for k,v := range m.Parameters { + tp,err := goType(typeOrType2(v.Type,v.Type2)) + if err != nil { + return "UNSUPPORTED TYPE", []string{} + } + tps = append(tps,tp) + ret = append(ret,fmt.Sprintf("%s %s",k,tp)) + } + return strings.Join(ret,", "),tps +} + +func (m Method) goparamnames() string { + ret := []string{"o.ptr"} + for k,_ := range m.Parameters { + ret = append(ret,k) + } + return strings.Join(ret, ", ") +} + type Interface struct { - Name string + Name, Super string Properties map[string]Property Methods map[string]Method } +func (i Interface) IsRoot() bool { + if i.Super == "" || i.Super == i.Name { + return true + } + return false +} + func (w *Wrapper) AddInterface(n *ast.ObjCInterfaceDecl) { //fmt.Printf("ast.ObjCInterfaceDecl: %s\n",n.Name) w.add(n.Name, n.Children()) @@ -104,6 +203,7 @@ func (w *Wrapper) add(name string, ns []ast.Node) { Properties: map[string]Property{}, Methods: map[string]Method{}, } + w.AddType(name) } for _,c := range ns { switch x := c.(type) { @@ -116,7 +216,7 @@ func (w *Wrapper) add(name string, ns []ast.Node) { w.AddType(typeOrType2(x.Type,x.Type2)) i.Properties[p.Name] = p case *ast.ObjCMethodDecl: - fmt.Printf("ObjcMethodDecl: %s\n",x.Name) + //fmt.Printf("ObjCMethodDecl: %s\n",x.Name) m := Method{ Name: x.Name, Type: x.Type, @@ -128,7 +228,10 @@ func (w *Wrapper) add(name string, ns []ast.Node) { case *ast.ObjCProtocol: //fmt.Printf("ast.ObjCProtocol: %s\n",x.Name) case *ast.ObjCInterface: - //fmt.Printf("ast.ObjCInterface: %s\n",x.Name) + if x.Super { + //fmt.Printf("ast.ObjCInterface: %s inherits from %s\n",name,x.Name) + i.Super = x.Name + } case *ast.Unknown: //fmt.Printf("(*ast.Unkonwn %s: %s)\n",x.Name,x.Content) default: @@ -192,29 +295,146 @@ func typeOrType2(t1, t2 string) string { 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) +func (w *Wrapper) ProcessType(gotype string) { + if gotype == "" { + return } - 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)) + if _,ok := gobuiltinTypes[gotype]; ok { + return + } + ctype := w.Types[gotype] + if _,ok := builtinTypes[ctype]; ok { + return + } + if Debug { + fmt.Printf("Processing %s (%s)\n",gotype,ctype) + } + if w.Processed[gotype] { + return + } + if i,ok := w.Interfaces[gotype]; ok { + if Debug { + fmt.Printf("Have interface for %s. super = %s\n",gotype,i.Super) } - 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)) + if i.Name != i.Super { + w.ProcessType(i.Super) + } + } + var fields string +// if there is an Interface known for this type, decide if it is the +// root object and if so give it a pointer element: + if i,ok := w.Interfaces[gotype]; ok && i.IsRoot() { + fields = "ptr unsafe.Pointer" + } else { + fields = i.Super // embed superclass + } + w.goTypes.WriteString(fmt.Sprintf(` +// %s +type %s struct { %s } +`,ctype, gotype, fields)) + w.Processed[gotype] = true +} + +func (w *Wrapper) ProcessTypes(tps []string) { + for _,tp := range tps { + w.ProcessType(tp) + } +} + +func (w *Wrapper) Wrap(toproc string) { + + w.Processed = make(map[string]bool) + + w.cCode.WriteString(`/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework CoreBluetooth +`) + + pInterfaces := map[string]Interface {toproc: w.Interfaces[toproc]} + for k,v := range pInterfaces { + if Debug { + fmt.Printf("Interface %s: %d properties, %d methods\n", + k, len(v.Properties), len(v.Methods)) + } + + w.goCode.WriteString(fmt.Sprintf(` +func New%s() *%s { + ret := &%s{} + ret.ptr = unsafe.Pointer(C.New%s()) + return ret +} +`,v.Name,v.Name,v.Name,v.Name)) + + w.cCode.WriteString(fmt.Sprintf(` +%s* +New%s() { + return [[%s alloc] init]; +} +`, v.Name, v.Name, v.Name)) + + for _,y := range v.Properties { + if Debug { + fmt.Printf(" property: %s (%s)\n", y.Name, typeOrType2(y.Type,y.Type2)) } } + for _,y := range v.Methods { + if Debug { + fmt.Printf(" method: %s (%s)\n", y.Name, typeOrType2(y.Type,y.Type2)) + } + gname := strings.Title(y.Name) + + grtype := "" + grptr := "" + cmtype := typeOrType2(y.Type,y.Type2) + if !y.isVoid() { + var err error + grtype,err = goType(cmtype) + if err != nil { + grtype = fmt.Sprintf("// goType(%s): NOT IMPLEMENTED\n",cmtype) + } + } + gcast := "" + gcast2 := "" + if grtype != "" { + if _,ok := w.Interfaces[grtype]; ok { + grptr = "*" + gcast = "ret := &" + grtype + "{}\n ret.ptr = unsafe.Pointer(" + gcast2 = ")\n return ret" + } else { + gcast = fmt.Sprintf("return (%s)(",grtype) + gcast2 = ")" + } + } + w.ProcessType(grtype) + gplist, gptypes := y.goparamlist() + w.ProcessTypes(gptypes) + w.goCode.WriteString(fmt.Sprintf(` +func (o *%s) %s(%s) %s%s { + %sC.%s_%s(%s)%s +}`,v.Name, gname, gplist, grptr, grtype, gcast, v.Name, y.Name, y.goparamnames(),gcast2)) + + cret := "" + if !y.isVoid() { + cret = "return " + } + w.cCode.WriteString(fmt.Sprintf(` +%s +%s_%s(%s) { + %s[(id)obj %s]; +}`, cmtype, v.Name, y.Name, y.cparamlist(), cret, y.objcparamlist())) + } } - fmt.Println(goCode.String()) + fmt.Println(`package main +`) + fmt.Println(w.cCode.String()) + fmt.Println(` +*/ +import "C" + +import ( + "unsafe" +) +`) + fmt.Println(w.goTypes.String()) + fmt.Println(w.goCode.String()) }