diff --git a/.gitignore b/.gitignore index de2abf3..50f89a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ clast ast.txt *.ast -simple -complex program +examples/foundation/foundation +examples/foundation/ns +examples/simple/simple +examples/simple/simple/ClassOne diff --git a/ast/ast.go b/ast/ast.go index 233d2a5..19a6dfc 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -212,6 +212,8 @@ func Parse(fullline string) Node { return parseObjCProtocol(line) case "ObjCPropertyDecl": return parseObjCPropertyDecl(line) + case "ObjCTypeParamDecl": + return parseObjCTypeParamDecl(line) case "OffsetOfExpr": return parseOffsetOfExpr(line) case "PackedAttr": diff --git a/ast/objc_type_param_decl.go b/ast/objc_type_param_decl.go new file mode 100644 index 0000000..7dd3111 --- /dev/null +++ b/ast/objc_type_param_decl.go @@ -0,0 +1,80 @@ +package ast + +import ( + "strings" +) + +// ObjCTypeParamDecl is node represents a parameter of variable declaration. +type ObjCTypeParamDecl struct { + Addr Address + Pos Position + Position2 string + Name string + Type string + Type2 string + IsReferenced bool + IsCovariant bool + IsBounded bool + ChildNodes []Node +} + +func parseObjCTypeParamDecl(line string) *ObjCTypeParamDecl { + groups := groupsFromRegex( + `<(?P.*)> + (?P [^ ]+:[\d:]+)? + (?P referenced)? + (?P \w+)? + (?P covariant)? + (?P bounded)? + '(?P.*?)' + (?P:'.*?')? + `, + line, + ) + + type2 := groups["type2"] + if type2 != "" { + type2 = type2[2 : len(type2)-1] + } + + if strings.Index(groups["position"], "") > -1 { + groups["position"] = "" + groups["position2"] = "" + } + + return &ObjCTypeParamDecl{ + Addr: ParseAddress(groups["address"]), + Pos: NewPositionFromString(groups["position"]), + Position2: strings.TrimSpace(groups["position2"]), + Name: strings.TrimSpace(groups["name"]), + Type: groups["type"], + Type2: type2, + IsReferenced: len(groups["referenced"]) > 0, + IsCovariant: len(groups["covariant"]) > 0, + IsBounded : len(groups["bounded"]) > 0, + ChildNodes: []Node{}, + } +} + +// AddChild adds a new child node. Child nodes can then be accessed with the +// Children attribute. +func (n *ObjCTypeParamDecl) 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 *ObjCTypeParamDecl) 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 *ObjCTypeParamDecl) Children() []Node { + return n.ChildNodes +} + +// Position returns the position in the original source code. +func (n *ObjCTypeParamDecl) Position() Position { + return n.Pos +} diff --git a/examples/foundation/main.go b/examples/foundation/main.go new file mode 100644 index 0000000..e847263 --- /dev/null +++ b/examples/foundation/main.go @@ -0,0 +1,18 @@ +package main + +import "C" + +import ( + "fmt" + "gitlab.wow.st/gmp/nswrap/examples/foundation/ns" +) + +func main() { + fmt.Println("Started") + n := ns.StringWithUTF8String(ns.CharFromString("hi there")) + c := n.CapitalizedString() + gstring := c.UTF8String().String() + fmt.Println(gstring) + fmt.Println("ok") +} + diff --git a/examples/foundation/nswrap.toml b/examples/foundation/nswrap.toml new file mode 100644 index 0000000..0aaf73d --- /dev/null +++ b/examples/foundation/nswrap.toml @@ -0,0 +1,10 @@ +InputFiles = [ + "/System/Library/Frameworks/Foundation.framework/Headers/NSDictionary.h", + "/System/Library/Frameworks/Foundation.framework/Headers/NSString.h", +] +Classes = [ + "NSString", + "NSDictionary", +] +SysImports = [ "Foundation/Foundation.h" ] +Pragma = [ 'clang diagnostic ignored "-Wformat-security"' ] diff --git a/examples/simple/ClassOne/main.go b/examples/simple/ClassOne/main.go new file mode 100644 index 0000000..0017ffe --- /dev/null +++ b/examples/simple/ClassOne/main.go @@ -0,0 +1,103 @@ +package ClassOne + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation + +#import "simple.h" + +ClassOne* +NewClassOne() { + return [ClassOne alloc]; +} + +int +ClassOne_geti1(void* obj) { + return [(id)obj geti1]; +} +int* +ClassOne_getp1(void* obj) { + return [(id)obj getp1]; +} +int +ClassOne_hi1(void* obj, struct stru in) { + return [(id)obj hi1:in]; +} +int +ClassOne_hi2(void* obj, void* in) { + return [(id)obj hi2:in]; +} +struct stru +ClassOne_nstru1(void* obj) { + return [(id)obj nstru1]; +} +struct stru* +ClassOne_nstru2(void* obj) { + return [(id)obj nstru2]; +} +ClassOne* +ClassOne_init(void* obj) { + return [(id)obj init]; +} +*/ +import "C" + +import ( + "unsafe" +) + +//ClassOne* +type ClassOne struct { NSObject } + +//NSObject* +type NSObject struct { ptr unsafe.Pointer } + +//int +type Int C.int + +//struct stru +type Stru C.struct_stru + +func NewClassOne() *ClassOne { + ret := &ClassOne{} + ret.ptr = unsafe.Pointer(C.NewClassOne()) + //ret = ret.Init() + return ret +} + +func (o *ClassOne) Geti1() Int { + return (Int)(C.ClassOne_geti1(o.ptr)) +} + + +func (o *ClassOne) Getp1() *Int { + return (*Int)(unsafe.Pointer(C.ClassOne_getp1(o.ptr))) +} + + +func (o *ClassOne) Hi1(in Stru) Int { + return (Int)(C.ClassOne_hi1(o.ptr, (C.struct_stru)(in))) +} + + +func (o *ClassOne) Hi2(in *Stru) Int { + return (Int)(C.ClassOne_hi2(o.ptr, unsafe.Pointer(in))) +} + + +func (o *ClassOne) Nstru1() Stru { + return (Stru)(C.ClassOne_nstru1(o.ptr)) +} + + +func (o *ClassOne) Nstru2() *Stru { + return (*Stru)(unsafe.Pointer(C.ClassOne_nstru2(o.ptr))) +} + + +func (o *ClassOne) Init() *ClassOne { + ret := &ClassOne{} + ret.ptr = unsafe.Pointer(C.ClassOne_init(o.ptr)) + return ret +} + diff --git a/examples/simple/ClassOne/simple.h b/examples/simple/ClassOne/simple.h new file mode 100644 index 0000000..52a0acb --- /dev/null +++ b/examples/simple/ClassOne/simple.h @@ -0,0 +1,22 @@ +#import + +struct stru {int a,b;}; + +@interface ClassOne : NSObject +{ + int i1; + int *p1; + int a1[2]; + int (*f)(); +} + +- (ClassOne*) init; +- (int) geti1; +- (int *) getp1; +- (int (*)()) getf1; +- (int) hi1:(struct stru)in; +- (int) hi2:(struct stru*)in; +- (struct stru) nstru1; +- (struct stru*) nstru2; +@end + diff --git a/examples/simple/ClassOne/simple.m b/examples/simple/ClassOne/simple.m new file mode 100644 index 0000000..6273f92 --- /dev/null +++ b/examples/simple/ClassOne/simple.m @@ -0,0 +1,59 @@ +#import "simple.h" + +@implementation ClassOne + +- (ClassOne*) init +{ + ClassOne *ret; + ret = [ClassOne alloc]; + ret->i1 = 12; + ret->p1 = malloc(sizeof(int)); + *ret->p1 = 16; + ret->a1[0] = 4; + ret ->a1[1] = 5; + return ret; +} + +- (int) geti1 +{ + return i1; +} + +- (int *) getp1 +{ + return p1; +} + +- (int (*)()) getf1 +{ + return f; +} + +- (int) hi1:(struct stru)in +{ + return in.a; +} + +- (int) hi2:(struct stru*)in +{ + return in->a; +} + +- (struct stru) nstru1 +{ + struct stru ret; + ret.a = 7; + ret.b = 8; + return ret; +} +- (struct stru*) nstru2 +{ + struct stru* ret; + ret = malloc(sizeof(struct stru)); + ret->a = 9; + ret->b = 10; + return ret; +} + +@end + diff --git a/examples/simple/main.go b/examples/simple/main.go new file mode 100644 index 0000000..c41d3fd --- /dev/null +++ b/examples/simple/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "gitlab.wow.st/gmp/nswrap/examples/simple/ClassOne" +) + +func main() { + o := ClassOne.NewClassOne().Init() + fmt.Println("i1 = ",o.Geti1()) + fmt.Println("p1 = ",o.Getp1()) + p1 := o.Getp1() + fmt.Println("*p1 = ", *p1) + *p1 = 17 + fmt.Println("*p1 = ", *o.Getp1()) + ns := o.Nstru1() + np := o.Nstru2() + fmt.Println(o.Hi1(ns)) + fmt.Println(o.Hi2(np)) +} + diff --git a/examples/simple/nswrap.toml b/examples/simple/nswrap.toml new file mode 100644 index 0000000..dde6dcb --- /dev/null +++ b/examples/simple/nswrap.toml @@ -0,0 +1,4 @@ +Package = "ClassOne" +InputFiles = [ "ClassOne/simple.h" ] +Classes = [ "ClassOne" ] +Imports = [ "simple.h" ] diff --git a/main.go b/main.go index 246f8e9..e3eb108 100644 --- a/main.go +++ b/main.go @@ -9,12 +9,14 @@ import ( "github.com/BurntSushi/toml" "gitlab.wow.st/gmp/nswrap/ast" + "gitlab.wow.st/gmp/nswrap/types" "gitlab.wow.st/gmp/nswrap/wrap" ) var Debug = false type conf struct { + Package string InputFiles []string Classes []string Imports []string @@ -163,17 +165,22 @@ func Start() (err error) { // build tree tree := buildTree(nodes, 0) - unit := tree[0] + //unit := tree[0] w := wrap.NewWrapper(Debug) + w.Package = Config.Package w.Import(Config.Imports) w.SysImport(Config.SysImports) w.Pragma(Config.Pragma) - for _, n := range(unit.Children()) { - switch x := n.(type) { - case *ast.ObjCInterfaceDecl: - w.AddInterface(x) - case *ast.ObjCCategoryDecl: - w.AddCategory(x) + for _, u := range tree { + for _, n := range(u.Children()) { + switch x := n.(type) { + case *ast.ObjCInterfaceDecl: + w.AddInterface(x) + case *ast.ObjCCategoryDecl: + w.AddCategory(x) + case *ast.TypedefDecl: + types.AddTypedef(x.Name,x.Type) + } } } w.Wrap(Config.Classes) @@ -181,8 +188,8 @@ func Start() (err error) { } func main() { - if _, err := toml.DecodeFile("conf.toml",&Config); err != nil { - fmt.Printf("Cannot open config file conf.toml.\n") + if _, err := toml.DecodeFile("nswrap.toml",&Config); err != nil { + fmt.Printf("Cannot open config file nswrap.toml.\n") os.Exit(-1) } if err := Start(); err != nil { diff --git a/types/combinators.go b/types/combinators.go index b932c89..90350f7 100644 --- a/types/combinators.go +++ b/types/combinators.go @@ -102,11 +102,6 @@ func NodeNamed(k string, p Parser) Parser { // Combinators -//Id is the identity parser -func Id(s string, n *Node) (string, *Node) { - return s,n -} - //Opt optionally runs a Parser, returning the input node if it fails func Opt(p Parser) Parser { return func(s string, n *Node) (string, *Node) { diff --git a/types/convert.go b/types/convert.go index 172384a..e91a82b 100644 --- a/types/convert.go +++ b/types/convert.go @@ -12,6 +12,12 @@ var super map[string]string //go struct. var wrapped map[string]bool +//TypeParameters maps, for each class, a TypedefName to a type, representing +//the Objective-C type parameters for that class +var TypeParameters map[string]map[string]string + +var Typedefs map[string]*Type + func Super(c string) string { if super == nil { super = make(map[string]string) @@ -26,6 +32,23 @@ func SetSuper(c, p string) { super[c] = p } +func SetTypeParam(c, n, t string) { + if TypeParameters == nil { + TypeParameters = make(map[string]map[string]string) + } + if TypeParameters[c] == nil { + TypeParameters[c] = make(map[string]string) + } + TypeParameters[c][n] = t +} + +func AddTypedef(n,t string) { + if Typedefs == nil { + Typedefs = make(map[string]*Type) + } + Typedefs[n] = NewTypeFromString(t,"") +} + type Type struct { Node *Node Class string @@ -42,12 +65,22 @@ func NewType(n *Node, c string) *Type { func NewTypeFromString(t,c string) *Type { n,err := Parse(t) - if n.CtypeSimplified() == "id" { + if n.IsId() { + //if n.CtypeSimplified() == "id" { n,err = Parse("NSObject*") } if err != nil { return &Type{} } + if TypeParameters[c] != nil { + recur := false + for k,v := range TypeParameters[c] { + recur = n.renameTypedefs(k,v) + } + if recur { + return NewTypeFromString(n.Ctype(),c) + } + } return &Type{ Node: n, Class: c, @@ -117,19 +150,21 @@ func (t *Type) CTypeAttrib() string { } func (t *Type) _CType(attrib bool) string { + //if !attrib && c.ctype != "" ... FIXME? if t.ctype != "" { // cache return t.ctype } var ct string if attrib { - ct = t.Node.Ctype() + ignore := map[string]bool { "GenericList": true } + ct = t.Node._Ctype(ignore) } else { ct = t.Node.CtypeSimplified() } ct = strings.ReplaceAll(ct,"instancename",t.Class) ct = strings.ReplaceAll(ct,"instancetype",t.Class + " *") if len(ct) > 1 && ct[:2] == "id" { - ct = "NSObject *" + ct[2:] + ct = "NSObject*" + ct[2:] } if len(ct) > 11 { if ct[:12] == "instancename" { ct = t.Class + ct[12:] } @@ -148,14 +183,18 @@ func (t *Type) GoTypeDecl() string { return t.GoInterfaceDecl() } tp := t.BaseType() + if tp.Node.IsId() { + return "" + } gt := tp.GoType() switch gt { case "", "Void": return "" default: return fmt.Sprintf(` +//%s type %s %s -`,gt,tp.CGoType()) +`,t.Node.CtypeSimplified(),gt,tp.CGoType()) } } @@ -170,12 +209,27 @@ func (t *Type) GoInterfaceDecl() string { super = "ptr unsafe.Pointer" } return fmt.Sprintf(` +//%s %stype %s struct { %s } -`,x,gt,super) +`,t.CTypeAttrib(),x,gt,super) +} + +func (t *Type) IsFunction() bool { + if td,ok := Typedefs[t.BaseType().CType()]; ok { + return td.IsFunction() + } + return t.Node.IsFunction() +} + +func (t *Type) IsPointer() bool { + if td,ok := Typedefs[t.BaseType().CType()]; ok { + return td.IsPointer() + } + return t.Node.IsPointer() } func (t *Type) CToGo(cval string) string { // cast C value to CGo - if t.Node.IsPointer() { + if t.IsPointer() { cval = "unsafe.Pointer(" + cval + ")" } return fmt.Sprintf("(%s)(%s)",t.GoType(),cval) diff --git a/types/cparser.go b/types/cparser.go index d9ea9bd..25eea8a 100644 --- a/types/cparser.go +++ b/types/cparser.go @@ -30,6 +30,7 @@ func AbstractDeclarator(s string, n *Node) (string, *Node) { Opt(Pointer), OneOrMore(DirectAbstractDeclarator)), Pointer, + Id, Block, )(s,n) } @@ -136,6 +137,14 @@ func NullableAnnotation(s string, n *Node) (string, *Node) { ))(s,n) } +func Id(s string, n *Node) (string, *Node) { + return Seq( + NodeNamed("Id",Lit("id")), + Opt(TypeQualifierList), + Opt(NullableAnnotation), + )(s,n) +} + func Pointer(s string, n *Node) (string, *Node) { return Seq( NodeNamed("Pointer",Lit("*")), diff --git a/types/main.go b/types/main.go index 33e5a0c..5053102 100644 --- a/types/main.go +++ b/types/main.go @@ -96,7 +96,7 @@ func (n *Node) PointsTo() *Node { //IsPointer returns true if the node is a pointer func (n *Node) IsPointer() bool { - return n.PointsTo() != nil + return n.IsId() || n.PointsTo() != nil } //ArrayOf, when called on an array node returns a node describing the type @@ -126,6 +126,13 @@ func (n *Node) IsFunction() bool { return n.Children[len(n.Children)-1].Kind == "Function" } +func (n *Node) IsId() bool { + if n == nil || len(n.Children) < 1 { + return false + } + return n.Children[0].Kind == "TypedefName" && n.Children[0].Content == "id" +} + //BaseType strips off all layers of pointer indirection func (n *Node) BaseType() *Node { if n == nil { diff --git a/types/node.go b/types/node.go index f6f56f1..f6ce307 100644 --- a/types/node.go +++ b/types/node.go @@ -67,6 +67,24 @@ func (n *Node) AddChild(c *Node) *Node { return n } +//returns true if anything gets renamed +func (n *Node) renameTypedefs(a,b string) (ret bool) { + ret = false + if n == nil { return } + for i,c := range n.Children { + if c.Kind == "TypedefName" && c.Content == a { + ret = true + n.Children[i] = NewNode("TypedefName", b) + n.Children[i].Children = c.Children + } + if len(c.Children) > 0 { + ret2 := c.renameTypedefs(a,b) + ret = ret || ret2 + } + } + return +} + func (n *Node) CtypeSimplified() string { ignore := map[string]bool{ "NullableAnnotation": true, diff --git a/wrap/main.go b/wrap/main.go index 25ecbe4..074c874 100644 --- a/wrap/main.go +++ b/wrap/main.go @@ -2,6 +2,8 @@ package wrap import ( "fmt" + "os" + "path" "strings" "gitlab.wow.st/gmp/nswrap/ast" @@ -13,6 +15,7 @@ var ( ) type Wrapper struct { + Package string Interfaces map[string]Interface cCode strings.Builder // put cGo code here @@ -81,7 +84,7 @@ func (m Method) isVoid() bool { //hasFunctionParam() returns true if a method has a function as a parameter. func (m Method) hasFunctionParam() bool { for _,p := range m.Parameters { - if p.Type.Node.IsFunction() { + if p.Type.IsFunction() { return true } } @@ -224,6 +227,9 @@ func (w *Wrapper) add(name string, ns []ast.Node) { //fmt.Printf("ast.ObjCInterface: %s inherits from %s\n",name,x.Name) types.SetSuper(name,x.Name) } + case *ast.ObjCTypeParamDecl: + //fmt.Printf("ObjCTypeParamDecl: %s = %s\n",x.Name,x.Type) + types.SetTypeParam(name,x.Name,x.Type) case *ast.Unknown: //fmt.Printf("(*ast.Unkonwn %s: %s)\n",x.Name,x.Content) default: @@ -312,7 +318,7 @@ func (w *Wrapper) processType(tp *types.Type) { bt := tp.BaseType() if w.Processed[bt.GoType()] { return } w.Processed[bt.GoType()] = true - if bt.Node.IsFunction() { + if bt.IsFunction() { return } w.goTypes.WriteString(tp.GoTypeDecl()) @@ -345,17 +351,26 @@ func (c *Char) String() string { func (w *Wrapper) Wrap(toproc []string) { - + if w.Package == "" { w.Package = "ns" } + err := os.MkdirAll(w.Package,0755) + if err != nil { + fmt.Printf("Error creating directory '%s'\n%s\n",w.Package,err) + os.Exit(-1) + } + of,err := os.Create(path.Join(w.Package,"main.go")) + if err != nil { + fmt.Printf("Error opening file %s\n%s\n",path.Join(w.Package,"main.go"),err) + os.Exit(-1) + } + fmt.Printf("Writing output to %s\n",path.Join(w.Package,"main.go")) pInterfaces := map[string]Interface{} for _,iface := range toproc { pInterfaces[iface] = w.Interfaces[iface] } //FIXME: sort pInterfaces - for iname,i := range pInterfaces { - if Debug { - fmt.Printf("Interface %s: %d properties, %d methods\n", - iname, len(i.Properties), len(i.Methods)) - } + for _,i := range pInterfaces { + fmt.Printf("Interface %s: %d properties, %d methods\n", + i.Name, len(i.Properties), len(i.Methods)) w.goCode.WriteString(fmt.Sprintf(` func New%s() *%s { @@ -384,16 +399,14 @@ New%s() { if Debug { fmt.Printf(" method: %s (%s)\n", m.Name, m.Type) } - if m.Type.Node.IsFunction() { + if m.Type.IsFunction() { continue } if m.hasFunctionParam() { continue } gname := strings.Title(m.Name) - if m.ClassMethod { - gname = i.Name + gname - } else { + if !m.ClassMethod { gname = "(o *" + i.Name + ") " + gname } cname := i.Name + "_" + m.Name @@ -429,10 +442,9 @@ func %s(%s) %s { }`, cmtype, cname, w.cparamlist(m), cret, cobj, w.objcparamlist(m))) } } - fmt.Println(`package main -`) - fmt.Println(w.cCode.String()) - fmt.Println(` + of.WriteString("package " + w.Package + "\n\n") + of.WriteString(w.cCode.String()) + of.WriteString(` */ import "C" @@ -440,7 +452,8 @@ import ( "unsafe" ) `) - fmt.Println(w.goTypes.String()) - fmt.Println(w.goHelpers.String()) - fmt.Println(w.goCode.String()) + of.WriteString(w.goTypes.String()) + of.WriteString(w.goHelpers.String()) + of.WriteString(w.goCode.String()) + of.Close() }