diff --git a/ast/ast.go b/ast/ast.go index c8fec79..6e1b5a5 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -210,6 +210,8 @@ func Parse(fullline string) Node { return parseObjCObjectPointerType(line) case "ObjCProtocol": return parseObjCProtocol(line) + case "ObjCProtocolDecl": + return parseObjCProtocolDecl(line) case "ObjCPropertyDecl": return parseObjCPropertyDecl(line) case "ObjCTypeParamDecl": diff --git a/ast/objc_protocol_decl.go b/ast/objc_protocol_decl.go new file mode 100644 index 0000000..9dd957e --- /dev/null +++ b/ast/objc_protocol_decl.go @@ -0,0 +1,55 @@ +package ast + +import ( + "strings" +) + +// ObjCProtocolDecl is node represents an Objective-C property declaration +type ObjCProtocolDecl struct { + Addr Address + Pos Position + Position2 string + Name string + ChildNodes []Node +} + +func parseObjCProtocolDecl(line string) *ObjCProtocolDecl { + groups := groupsFromRegex( + `(?:prev (?P0x[0-9a-f]+) )? + <(?P.*.*?|.*.*?|.*|.*?)> + (?P | col:\d+| line:\d+:\d+)? + (?P.*?)`, + line, + ) + + return &ObjCProtocolDecl{ + 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 *ObjCProtocolDecl) 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 *ObjCProtocolDecl) 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 *ObjCProtocolDecl) Children() []Node { + return n.ChildNodes +} + +// Position returns the position in the original source code. +func (n *ObjCProtocolDecl) Position() Position { + return n.Pos +} diff --git a/cmd/nswrap/main.go b/cmd/nswrap/main.go index 9bc6e77..b6e8f80 100644 --- a/cmd/nswrap/main.go +++ b/cmd/nswrap/main.go @@ -2,13 +2,14 @@ package main import ( "fmt" + "io/ioutil" "os" "os/exec" "regexp" "runtime" "strings" - "github.com/BurntSushi/toml" + "gopkg.in/yaml.v2" "gitlab.wow.st/gmp/nswrap/ast" "gitlab.wow.st/gmp/nswrap/types" "gitlab.wow.st/gmp/nswrap/wrap" @@ -18,15 +19,16 @@ var Debug = false type conf struct { Package string - InputFiles []string + Inputfiles []string Classes []string Functions []string Enums []string + Delegates map[string][]string Frameworks []string Imports []string - SysImports []string + Sysimports []string Pragma []string - VaArgs int + Vaargs int } var Config conf @@ -149,7 +151,7 @@ func matches(x string, rs []string) bool { // Start begins transpiling an input file. func Start() (err error) { - for _, in := range Config.InputFiles { + for _, in := range Config.Inputfiles { _, err := os.Stat(in) if err != nil { return fmt.Errorf("Input file %s is not found", in) @@ -161,7 +163,7 @@ func Start() (err error) { // 3. Generate AST cargs := []string{"-xobjective-c", "-Xclang", "-ast-dump", "-fsyntax-only","-fno-color-diagnostics"} - cargs = append(cargs,Config.InputFiles...) + cargs = append(cargs,Config.Inputfiles...) fmt.Printf("Generating AST\n") astPP, err := exec.Command("clang",cargs...).Output() if err != nil { @@ -192,12 +194,12 @@ func Start() (err error) { w.Package = Config.Package w.Frameworks(Config.Frameworks) w.Import(Config.Imports) - w.SysImport(Config.SysImports) + w.SysImport(Config.Sysimports) w.Pragma(Config.Pragma) - if Config.VaArgs == 0 { - Config.VaArgs = 16 + if Config.Vaargs == 0 { + Config.Vaargs = 16 } - w.VaArgs = Config.VaArgs + w.Vaargs = Config.Vaargs for _, u := range tree { fmt.Printf("--processing translation unit\n") for _, n := range(u.Children()) { @@ -212,6 +214,15 @@ func Start() (err error) { if matches(x.Name,Config.Functions) { w.AddFunction(x) } + case *ast.ObjCProtocolDecl: + DELEGATES: + for _,ps := range Config.Delegates { + _ = ps + //if matches(x.Name,ps) { + // w.AddProtocol(x) + break DELEGATES + //} + } case *ast.EnumDecl: w.AddEnum(x,Config.Enums) } @@ -222,8 +233,13 @@ func Start() (err error) { } func main() { - if _, err := toml.DecodeFile("nswrap.toml",&Config); err != nil { - fmt.Printf("Cannot open config file nswrap.toml.\n") + confbytes, err := ioutil.ReadFile("nswrap.yaml") + if err != nil { + fmt.Printf("Cannot open config file nswrap.yaml. %s\n",err) + os.Exit(-1) + } + if err = yaml.Unmarshal(confbytes,&Config); err != nil { + fmt.Printf("Cannot decode config file nswrap.yaml. %s\n",err) os.Exit(-1) } if err := Start(); err != nil { diff --git a/examples/app/nswrap.toml b/examples/app/nswrap.toml deleted file mode 100644 index 3d6cf94..0000000 --- a/examples/app/nswrap.toml +++ /dev/null @@ -1,45 +0,0 @@ -InputFiles = [ - "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h", - "/System/Library/Frameworks/AppKit.framework/Headers/AppKit.h", -] -Classes = [ - "NSArray", - "NSMutableArray", - "NSDictionary", - "NSEnumerator", - "NSSet", - "NSDate", - "NSTimeZone", - "NSCalendar", - "NSLocale", - "NSCharacterSet", - "NSString", - "NSScanner", - "NSFileManager", - "NSApplication", - "NSBundle", - "NSApp", - "NSMenu", - "NSMenuItem", - "NSWindow", - "NSView", - "NSScreen", - "NSButton", - "NSEvent", - "NSResponder", - "NSRunLoop", -] -Functions = [ - "NSMake.*", -] -Enums = [ - "CF.*", - "NSApplication.*", - "NSBackingStore.*", - "NSWindowStyleMask.*", - "NSWindowButton", - "NSWindowOrderingMode", -] -Frameworks = [ "Foundation", "AppKit", "CoreGraphics" ] -Pragma = [ 'clang diagnostic ignored "-Wformat-security"' ] -VaArgs = 32 diff --git a/examples/app/nswrap.yaml b/examples/app/nswrap.yaml new file mode 100644 index 0000000..68504b9 --- /dev/null +++ b/examples/app/nswrap.yaml @@ -0,0 +1,49 @@ +inputfiles: + - /System/Library/Frameworks/Foundation.framework/Headers/Foundation.h + - /System/Library/Frameworks/AppKit.framework/Headers/AppKit.h + +classes: + - NSArray" + - NSMutableArray + - NSDictionary + - NSEnumerator + - NSSet + - NSDate + - NSTimeZone + - NSCalendar + - NSLocale + - NSCharacterSet + - NSString + - NSScanner + - NSFileManager + - NSApplication + - NSBundle + - NSApp + - NSMenu + - NSMenuItem + - NSWindow + - NSView + - NSScreen + - NSButton + - NSEvent + - NSResponder + - NSRunLoop + +functions: [NSMake.*] + +enums: + - CF.* + - NSApplication.* + - NSBackingStore.* + - NSWindowStyleMask.* + - NSWindowButton + - NSWindowOrderingMode + +delegates: + AppDelegate: [NSApplicationDelegate] + +frameworks: [ Foundation, AppKit, CoreGraphics ] +pragma: [ clang diagnostic ignored "-Wformat-security" ] +vaargs: 32 + + diff --git a/examples/bluetooth/nswrap.toml b/examples/bluetooth/nswrap.toml deleted file mode 100644 index 0fe78a0..0000000 --- a/examples/bluetooth/nswrap.toml +++ /dev/null @@ -1,44 +0,0 @@ -Package = "ble" -InputFiles = [ - "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h", - "/System/Library/Frameworks/CoreBluetooth.framework/Headers/CoreBluetooth.h", - "ble/ble_delegate.h", -] -Classes = [ - "ble_delegate", - "CBCentralManager", - "CBPeripheralManager", - "CBPeripheral", - "CBCentral", - "CBService", - "CBCharacteristic", - "CBDescriptor", - "CBError", - "CBUUID", - "CBAdvertisementData", - "CBATTRequest", - "NSArray", - "NSMutableArray", - "NSDictionary", - "NSEnumerator", - "NSSet", - "NSDate", - "NSTimeZone", - "NSString", -] -Functions = [ - "NSMakeRange", -] -Enums = [ - "CB.*", -] -Frameworks = [ - "Foundation", - "CoreBluetooth", -] -Imports = [ - "ble_delegate.h", -] - -Pragma = [ 'clang diagnostic ignored "-Wformat-security"' ] -VaArgs = 32 diff --git a/examples/bluetooth/nswrap.yaml b/examples/bluetooth/nswrap.yaml new file mode 100644 index 0000000..f89ac15 --- /dev/null +++ b/examples/bluetooth/nswrap.yaml @@ -0,0 +1,35 @@ +package: ble +inputfiles: + - /System/Library/Frameworks/Foundation.framework/Headers/Foundation.h + - /System/Library/Frameworks/CoreBluetooth.framework/Headers/CoreBluetooth.h + - ble/ble_delegate.h + +classes: + - ble_delegate + - CBCentralManager + - CBPeripheralManager + - CBPeripheral + - CBCentral + - CBService + - CBCharacteristic + - CBDescriptor + - CBError + - CBUUID + - CBAdvertisementData + - CBATTRequest + - NSArray + - NSMutableArray + - NSDictionary + - NSEnumerator + - NSSet + - NSDate + - NSTimeZone + - NSString + +functions: [ NSMakeRange ] +enums: [ CB.* ] +frameworks: [ Foundation, CoreBluetooth ] +imports: [ ble_delegate.h ] + +pragma: [ clang diagnostic ignored "-Wformat-security" ] +vaargs: 32 diff --git a/examples/foundation/nswrap.toml b/examples/foundation/nswrap.toml deleted file mode 100644 index 35b7c01..0000000 --- a/examples/foundation/nswrap.toml +++ /dev/null @@ -1,28 +0,0 @@ -InputFiles = [ - "/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h", -] -Classes = [ - "NSArray", - "NSMutableArray", - "NSDictionary", - "NSEnumerator", - "NSSet", - "NSDate", - "NSTimeZone", - "NSCalendar", - "NSLocale", - "NSCharacterSet", - "NSString", - "NSScanner", - "NSFileManager", -] -Functions = [ - "NSMakeRange", -] -Enums = [ - "P_ALL", - "CF.*", -] -Frameworks = [ "Foundation" ] -Pragma = [ 'clang diagnostic ignored "-Wformat-security"' ] -VaArgs = 32 diff --git a/examples/foundation/nswrap.yaml b/examples/foundation/nswrap.yaml new file mode 100644 index 0000000..7f4b802 --- /dev/null +++ b/examples/foundation/nswrap.yaml @@ -0,0 +1,21 @@ +inputfiles: + - /System/Library/Frameworks/Foundation.framework/Headers/Foundation.h +classes: + - NSArray + - NSMutableArray + - NSDictionary + - NSEnumerator + - NSSet + - NSDate + - NSTimeZone + - NSCalendar + - NSLocale + - NSCharacterSet + - NSString + - NSScanner + - NSFileManager +functions: [ NSMakeRange ] +enums: [ P_ALL, CF.* ] +frameworks: [ Foundation ] +pragma: [ clang diagnostic ignored "-Wformat-security" ] +vaargs: 32 diff --git a/examples/simple/nswrap.toml b/examples/simple/nswrap.toml deleted file mode 100644 index fa84eca..0000000 --- a/examples/simple/nswrap.toml +++ /dev/null @@ -1,5 +0,0 @@ -Package = "ClassOne" -InputFiles = [ "ClassOne/simple.h" ] -Classes = [ "ClassOne","ClassTwo" ] -Imports = [ "simple.h" ] -Frameworks = [ "Foundation" ] diff --git a/examples/simple/nswrap.yaml b/examples/simple/nswrap.yaml new file mode 100644 index 0000000..d5fa645 --- /dev/null +++ b/examples/simple/nswrap.yaml @@ -0,0 +1,7 @@ +package: ClassOne +inputfiles: [ ClassOne/simple.h ] +classes: + - ClassOne + - ClassTwo +imports: [ simple.h ] +frameworks: [ Foundation ] diff --git a/wrap/main.go b/wrap/main.go index dd41e1d..c33ed22 100644 --- a/wrap/main.go +++ b/wrap/main.go @@ -22,15 +22,17 @@ type Wrapper struct { Functions map[string]*Method NamedEnums map[string]*Enum AnonEnums []*Enum + Protocols []*Protocol cgoFlags strings.Builder // put cGo directives here cCode strings.Builder // put cGo code here goTypes strings.Builder // put Go type declarations here goConst strings.Builder // put Go constants (from C enums) here goCode strings.Builder // put Go code here + goExports strings.Builder // put exported Go functions here goHelpers strings.Builder // put Go helper functions here Processed map[string]bool - VaArgs int + Vaargs int } func NewWrapper(debug bool) *Wrapper { @@ -41,8 +43,9 @@ func NewWrapper(debug bool) *Wrapper { Functions: map[string]*Method{}, NamedEnums: map[string]*Enum{}, AnonEnums: []*Enum{}, + Protocols: []*Protocol{}, Processed: map[string]bool{}, - VaArgs: 16, + Vaargs: 16, } ret.cgoFlags.WriteString(fmt.Sprintf(`/* #cgo CFLAGS: -x objective-c @@ -105,6 +108,12 @@ type Enum struct { Constants []string } +type Protocol struct { + Name, GoName string + Methods []*Method + Polymorphic map[string]bool +} + //isVoid() returns true if the method has no return value. func (m Method) isVoid() bool { return m.Type.CType() == "void" @@ -152,7 +161,7 @@ func (w Wrapper) objcparamlist(m *Method) string { } else { if p.Type.Variadic { str := []string{m.Name + ":arr[0]"} - for i := 1; i < w.VaArgs; i++ { + for i := 1; i < w.Vaargs; i++ { str = append(str,"arr["+strconv.Itoa(i)+"]") } str = append(str,"nil") @@ -268,6 +277,40 @@ func (w *Wrapper) AddFunction(n *ast.FunctionDecl) { w.Functions[n.Name] = m } +func (w *Wrapper) AddProtocol(n *ast.ObjCProtocolDecl) { + p := &Protocol{ + Name: n.Name, + GoName: types.NewTypeFromString(n.Name,n.Name).GoType(), + Methods: []*Method{}, + Polymorphic: map[string]bool{}, + } + seen := make(map[string]bool) + fmt.Printf("Adding protocol %s\n",n.Name) + for _,c := range n.Children() { + switch x := c.(type) { + case *ast.ObjCMethodDecl: + m := &Method{ + Name: x.Name, + Type: types.NewTypeFromString(x.Type,p.Name), + Class: p.Name, + GoClass: p.GoName, + ClassMethod: x.ClassMethod, + } + var avail bool + m.Parameters, avail = w.GetParms(x,p.Name) + if avail { + fmt.Printf(" method %s\n",m.Name) + p.Methods = append(p.Methods,m) + if seen[m.Name] { + p.Polymorphic[m.Name] = true + } + seen[m.Name] = true + } + } + } + w.Protocols = append(w.Protocols,p) +} + //FIXME: copied from nswrap/main.go, should put this in a utils package func matches(x string, rs []string) bool { for _,r := range rs { @@ -680,7 +723,7 @@ func %s%s(%s) %s { for i,o := range %ss { %s[i] = o.Ptr() } -`,vn,w.VaArgs,vn,vn)) +`,vn,w.Vaargs,vn,vn)) } w.goCode.WriteString(` ` + types.GoToC(cname,ns,m.Type,tps) + "\n}\n") @@ -781,6 +824,57 @@ const %s %s= C.%s } } +//FIXME: need to disambiguate polymorphic method names. Something like: +//func Disambiguate([]*Method) []*Method {} ... +//Can allow user to decide on a disambiguation strategy +func (w *Wrapper) ProcessProtocol(p *Protocol) { + fmt.Printf("Processing protocol (%s)\n",p.Name) + //To create (per protocol): + //1. ObjC protocol interface + //2. ObjC protocol implementation + //3. Go type + //4. Go constructor + //5. Go dispatch database for callbacks + //To create (per method): + //1. ObjC function prototypes for go exports + //2. ObjC constructor function + //3. Go exported callback function wrappers + + var ccode, gocode, goexports strings.Builder + //1. ObjC protocol interface + ccode.WriteString(fmt.Sprintf(` +@interface %s : %s +`,p.Name,p.Name)) + //2. ObjC protocol implementation + ccode.WriteString(fmt.Sprintf(` +`)) + //3. Go type + gocode.WriteString(fmt.Sprintf(` +`)) + //4. Go constructor + gocode.WriteString(fmt.Sprintf(` +`)) + //5. Go dispatch database for callbacks + gocode.WriteString(fmt.Sprintf(` +`)) + //6. Go callback registration function + gocode.WriteString(fmt.Sprintf(` +`)) + //To create (per method): + for _,m := range p.Methods { + _ = m + //1. ObjC function prototypes for go exports + ccode.WriteString(fmt.Sprintf(` +`)) + //2. ObjC constructor function + ccode.WriteString(fmt.Sprintf(` +`)) + //3. Go exported callback function wrappers + goexports.WriteString(fmt.Sprintf(` +`)) + } +} + func (w *Wrapper) Wrap(toproc []string) { if w.Package == "" { w.Package = "ns" } err := os.MkdirAll(w.Package,0755) @@ -851,6 +945,9 @@ void* for _,e := range w.AnonEnums { w.ProcessEnum(e) } + for _,p := range w.Protocols { + w.ProcessProtocol(p) + } fmt.Printf("%d functions\n", len(w.Functions)) fmt.Printf("%d enums\n", len(w.NamedEnums) + len(w.AnonEnums)) of.WriteString("package " + w.Package + "\n\n")