From 758ea406790a19d4fc8f1c517e0fc4a0e89121f9 Mon Sep 17 00:00:00 2001 From: Greg Date: Thu, 2 May 2019 14:14:29 -0400 Subject: [PATCH] Add constants for C enum types. Add NSEnumerator helper. Fix bugs in Go class names. Add "Frameworks" option to nswrap.toml. Improve handling of ast.EnumDecl and ast.EnumConstantDecl. --- ast/enum_constant_decl.go | 20 ++- ast/enum_decl.go | 16 ++- examples/foundation/main.go | 4 + examples/foundation/nswrap.toml | 3 + main.go | 6 +- types/convert.go | 5 +- wrap/main.go | 217 +++++++++++++++++++++++++------- 7 files changed, 219 insertions(+), 52 deletions(-) diff --git a/ast/enum_constant_decl.go b/ast/enum_constant_decl.go index 772e7e3..03cf79d 100644 --- a/ast/enum_constant_decl.go +++ b/ast/enum_constant_decl.go @@ -1,5 +1,9 @@ package ast +import ( + "strings" +) + // EnumConstantDecl is node represents a enum constant declaration. type EnumConstantDecl struct { Addr Address @@ -8,6 +12,7 @@ type EnumConstantDecl struct { Referenced bool Name string Type string + Type2 string ChildNodes []Node } @@ -16,18 +21,25 @@ func parseEnumConstantDecl(line string) *EnumConstantDecl { `<(?P.*)> ( (?P[^ ]+))? ( (?Preferenced))? - (?P.+) - '(?P.+?)'`, + (?P \w+) + (?P '.*?')? + (?P:'.*?')?`, line, ) + type2 := groups["type2"] + if type2 != "" { + type2 = type2[2 : len(type2)-1] + } + return &EnumConstantDecl{ Addr: ParseAddress(groups["address"]), Pos: NewPositionFromString(groups["position"]), Position2: groups["position2"], Referenced: len(groups["referenced"]) > 0, - Name: groups["name"], - Type: groups["type"], + Name: strings.TrimSpace(groups["name"]), + Type: removeQuotes(groups["type"]), + Type2: type2, ChildNodes: []Node{}, } } diff --git a/ast/enum_decl.go b/ast/enum_decl.go index de85098..ff8b21e 100644 --- a/ast/enum_decl.go +++ b/ast/enum_decl.go @@ -10,20 +10,34 @@ type EnumDecl struct { Pos Position Position2 string Name string + Type string + Type2 string ChildNodes []Node } func parseEnumDecl(line string) *EnumDecl { groups := groupsFromRegex( - `(?:prev (?P0x[0-9a-f]+) )?<(?P.*)>(?P .+:\d+)?(?P.*)`, + `(?:prev (?P0x[0-9a-f]+) )? + <(?P.*)> + (?P .+:\d+)? + (?P \w+)? + (?P '.*?')? + (?P:'.*')?`, line, ) + type2 := groups["type2"] + if type2 != "" { + type2 = type2[2 : len(type2)-1] + } + return &EnumDecl{ Addr: ParseAddress(groups["address"]), Pos: NewPositionFromString(groups["position"]), Position2: groups["position2"], Name: strings.TrimSpace(groups["name"]), + Type: removeQuotes(groups["type"]), + Type2: type2, ChildNodes: []Node{}, } } diff --git a/examples/foundation/main.go b/examples/foundation/main.go index 2d7b745..b33368e 100644 --- a/examples/foundation/main.go +++ b/examples/foundation/main.go @@ -27,4 +27,8 @@ func main() { fmt.Println("Length(a2) = ",a2.Count()) i1 := a.ObjectAtIndex(1).NSString() fmt.Println(i1.UTF8String()) + a.ObjectEnumerator().ForIn(func(o *ns.Id) bool { + fmt.Println(o.NSString().UTF8String()) + return true + }) } diff --git a/examples/foundation/nswrap.toml b/examples/foundation/nswrap.toml index 2681f82..5dd9082 100644 --- a/examples/foundation/nswrap.toml +++ b/examples/foundation/nswrap.toml @@ -19,6 +19,9 @@ Classes = [ Functions = [ "NSMakeRange", ] +Enums = [ + "CF.*", +] SysImports = [ "Foundation/Foundation.h" ] Pragma = [ 'clang diagnostic ignored "-Wformat-security"' ] VaArgs = 32 diff --git a/main.go b/main.go index 67bd50c..51fbcfc 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,8 @@ type conf struct { InputFiles []string Classes []string Functions []string + Enums []string + Frameworks []string Imports []string SysImports []string Pragma []string @@ -177,9 +179,9 @@ func Start() (err error) { // build tree tree := buildTree(nodes, 0) - //unit := tree[0] w := wrap.NewWrapper(Debug) w.Package = Config.Package + w.Frameworks(Config.Frameworks) w.Import(Config.Imports) w.SysImport(Config.SysImports) w.Pragma(Config.Pragma) @@ -200,6 +202,8 @@ func Start() (err error) { if matches(x.Name,Config.Functions) { w.AddFunction(x) } + case *ast.EnumDecl: + w.AddEnum(x,Config.Enums) } } } diff --git a/types/convert.go b/types/convert.go index 2bdfc23..a772df3 100644 --- a/types/convert.go +++ b/types/convert.go @@ -157,7 +157,6 @@ func (t *Type) PointsTo() *Type { } func Wrap(s string) { - // it is the pointers to this type that get wrapped wrapped[s] = true } @@ -270,11 +269,13 @@ type %s %s } func (t *Type) GoInterfaceDecl() string { + ct := t.CType() gt := t.GoType() if gt[0] == '*' { gt = gt[1:] // dereference wrapped types + ct = ct[:len(ct)-1] } - super := Super(gt) + super := Super(ct) if super == "" { goInterfaces[gt] = true return fmt.Sprintf(` diff --git a/wrap/main.go b/wrap/main.go index 068309f..62e7323 100644 --- a/wrap/main.go +++ b/wrap/main.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path" + "regexp" "strconv" "strings" @@ -19,9 +20,13 @@ type Wrapper struct { Package string Interfaces map[string]*Interface Functions map[string]*Method + NamedEnums map[string]*Enum + AnonEnums []*Enum + 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 goHelpers strings.Builder // put Go helper functions here Processed map[string]bool @@ -34,13 +39,14 @@ func NewWrapper(debug bool) *Wrapper { ret := &Wrapper{ Interfaces: map[string]*Interface{}, Functions: map[string]*Method{}, + NamedEnums: map[string]*Enum{}, + AnonEnums: []*Enum{}, Processed: map[string]bool{}, VaArgs: 16, } - ret.cCode.WriteString(`/* + ret.cgoFlags.WriteString(fmt.Sprintf(`/* #cgo CFLAGS: -x objective-c -#cgo LDFLAGS: -framework Foundation -`) +`)) ret.goTypes.WriteString(` type Id struct { } func (o *Id) Ptr() unsafe.Pointer { return unsafe.Pointer(o) } @@ -48,11 +54,19 @@ func (o *Id) Ptr() unsafe.Pointer { return unsafe.Pointer(o) } return ret } +func (w *Wrapper) Frameworks(ss []string) { + if len(ss) == 0 { + return + } + for _,s := range ss { + w.cCode.WriteString(fmt.Sprintf("#import <%s/%s.h>\n",s,s)) + } + w.cgoFlags.WriteString("#cgo LDFLAGS: -framework " + strings.Join(ss," -framework ")) +} + func (w *Wrapper) Import(ss []string) { for _,s := range ss { - w.cCode.WriteString(` -#import "` + s + `" -`) + w.cCode.WriteString("\n#import \"" + s + "\"\n") } } @@ -64,7 +78,7 @@ func (w *Wrapper) SysImport(ss []string) { func (w *Wrapper) Pragma(ss []string) { for _,s := range ss { - w.cCode.WriteString("\n#pragma " + s + "\n") + w.cgoFlags.WriteString("\n#pragma " + s + "\n") } } @@ -79,12 +93,18 @@ type Parameter struct { } type Method struct { - Name, Class string + Name, Class, GoClass string Type *types.Type ClassMethod bool Parameters []*Parameter } +type Enum struct { + Name string + Type *types.Type + Constants []string +} + //isVoid() returns true if the method has no return value. func (m Method) isVoid() bool { return m.Type.CType() == "void" @@ -149,6 +169,7 @@ func (w Wrapper) objcparamlist(m *Method) string { //also a C/Objective-C reserved word. var goreserved map[string]bool = map[string]bool{ "range": true, + "type": true, } func (w *Wrapper) gpntp(m *Method) ([]string,[]*types.Type,string) { @@ -187,7 +208,7 @@ func (w *Wrapper) gpntp(m *Method) ([]string,[]*types.Type,string) { type Interface struct { - Name string + Name, GoName string Properties map[string]*Property Methods map[string]*Method } @@ -247,18 +268,69 @@ func (w *Wrapper) AddFunction(n *ast.FunctionDecl) { w.Functions[n.Name] = m } +//FIXME: copied from nswrap/main.go, should put this in a utils package +func matches(x string, rs []string) bool { + for _,r := range rs { + if m,_ := regexp.MatchString("^" + r + "$",x); m { + return true + } + } + return false +} + +func (w *Wrapper) AddEnum(n *ast.EnumDecl,rs []string) { + if n.Name != "" && !matches(n.Name,rs) { + return + } + //fmt.Printf("Adding enum: (%s) %s\n",n.Type,n.Name) + var tp *types.Type + a := (*Avail)(&[]AvailAttr{}) + if n.Type == "" { + tp = nil + } else { + tp = types.NewTypeFromString(n.Type,"") + //fmt.Printf(" type: %s\n",tp.CType()) + } + e := &Enum{ + Name: n.Name, // NOTE: may be empty string + Type: tp, + Constants: []string{}, + } + for _,c := range n.Children() { + switch x := c.(type) { + case *ast.AvailabilityAttr, *ast.UnavailableAttr: + a.Add(x) + case *ast.EnumConstantDecl: + //fmt.Printf("*ast.EnumConstantDecl: (%s) '%s': %s\n",n.Type,n.Name,x.Name) + if n.Name == "" && !matches(x.Name,rs) { + continue + } + e.Constants = append(e.Constants,x.Name) + } + } + if a.Available() && len(e.Constants) > 0 { + if e.Name == "" { + w.AnonEnums = append(w.AnonEnums,e) + } else { + w.NamedEnums[e.Name] = e + } + //fmt.Printf(" added\n") + } +} + func (w *Wrapper) add(name string, ns []ast.Node) { var i *Interface var ok bool + goname := types.NewTypeFromString(name,name).GoType() + types.Wrap(goname) if i,ok = w.Interfaces[name]; !ok { i = &Interface{ Name: name, + GoName: goname, Properties: map[string]*Property{}, Methods: map[string]*Method{}, } } - tp := types.NewTypeFromString(name,name) - types.Wrap(tp.GoType()) var avail bool for _,c := range ns { switch x := c.(type) { @@ -278,6 +350,7 @@ func (w *Wrapper) add(name string, ns []ast.Node) { Name: x.Name, Type: types.NewTypeFromString(x.Type,name), Class: name, + GoClass: goname, ClassMethod: x.ClassMethod, } //fmt.Println(m.Type.Node.String()) @@ -314,6 +387,7 @@ func (w *Wrapper) add(name string, ns []ast.Node) { m2 := &Method{ Name: m.Name, Class: i.Name, + GoClass: i.GoName, Type: m.Type.CloneToClass(i.Name), ClassMethod: true, Parameters: []*Parameter{}, @@ -341,12 +415,40 @@ type AvailAttr struct { Deprecated bool } +type Avail []AvailAttr + +func (a *Avail) Add(n ast.Node) { + switch x := n.(type) { + case *ast.AvailabilityAttr: + *a = append(*a, AvailAttr{ + OS: x.OS, + Version: x.Version, + Deprecated: (x.Unknown1 != "0") || x.IsUnavailable, + }) + case *ast.UnavailableAttr: + *a = append(*a, AvailAttr{ + OS: "macos", Deprecated: true }) + } +} + +func (a *Avail) Available() bool { + if len(*a) == 0 { + return true + } + for _,x := range *a { + if x.OS == "macos" && x.Deprecated == false { + return true + } + } + return false +} + //GetParms returns the parameters of a method declaration and a bool //indicating whether the given method is available on MacOS and not //deprecated. func (w *Wrapper) GetParms(n ast.Node,class string) ([]*Parameter,bool) { ret := make([]*Parameter,0) - avail := make([]AvailAttr,0) + avail := (*Avail)(&[]AvailAttr{}) var parms []string switch x := n.(type) { case *ast.ObjCMethodDecl: @@ -370,34 +472,14 @@ func (w *Wrapper) GetParms(n ast.Node,class string) ([]*Parameter,bool) { j++ case *ast.Variadic: ret[j-1].Type.Variadic = true - case *ast.AvailabilityAttr: - avail = append(avail, - AvailAttr{ - OS: x.OS, - Version: x.Version, - Deprecated: x.Unknown1 != "0", - }) - //fmt.Println("AvailAttr ",avail,x) - case *ast.UnavailableAttr: - avail = append(avail, - AvailAttr{ OS: "macos", Deprecated: true }) + case *ast.AvailabilityAttr, *ast.UnavailableAttr: + avail.Add(x) case *ast.Unknown: if Debug { fmt.Printf("GetParms(): ast.Unknown: %s\n",x.Name) } } } // check that the method is available for this OS and not deprecated - a := func() bool { - if len(avail) == 0 { - return true - } - for _,x := range avail { - if x.OS == "macos" && x.Deprecated == false { - return true - } - } - return false - }() - if !a { + if !avail.Available() { return nil, false } // check that we found the right number of parameters @@ -436,13 +518,17 @@ func (w *Wrapper) processType(tp *types.Type) { if gt == "Char" { w.CharHelpers() } + if gt == "NSEnumerator" { + w.EnumeratorHelpers() + } if bt.IsFunction() { return } super := types.Super(gt) if super != "" { - types.Wrap(super) - w.processType(types.NewTypeFromString(super,"")) + tp := types.NewTypeFromString(super,"") + types.Wrap(tp.GoType()) + w.processType(tp) } w.goTypes.WriteString(bt.GoTypeDecl()) } @@ -463,6 +549,15 @@ func (c *Char) String() string { `) } +func (w *Wrapper) EnumeratorHelpers() { + w.goHelpers.WriteString(` +func (e *NSEnumerator) ForIn(f func(*Id) bool) { + for o := e.NextObject(); o != nil; o = e.NextObject() { + if !f(o) { break } + } +}`) +} + func (w *Wrapper) ProcessMethod(m *Method) { w._processMethod(m,false) } @@ -481,9 +576,9 @@ func (w *Wrapper) _processMethod(m *Method,fun bool) { gname := strings.Title(m.Name) switch { case !m.ClassMethod: - gname = "(o *" + m.Class + ") " + gname - case m.Type.GoType() != "*" + m.Class: - gname = m.Class + gname + gname = "(o *" + m.GoClass + ") " + gname + case m.Type.GoType() != "*" + m.GoClass: + gname = m.GoClass + gname default: lens1 := len(m.Class) i := 0 @@ -492,9 +587,9 @@ func (w *Wrapper) _processMethod(m *Method,fun bool) { if m.Class[i:] == gname[:lens1 - i] { break } } if lens1 - i >= len(gname) { - gname = m.Class + gname + gname = m.GoClass + gname } else { - gname = m.Class + gname[lens1-i:] + gname = m.GoClass + gname[lens1-i:] } } cname := m.Name @@ -511,6 +606,9 @@ func (w *Wrapper) _processMethod(m *Method,fun bool) { if types.IsGoInterface(grtype) { grtype = "*Id" } + if gname == grtype { // avoid name conflicts between methods and types + gname = "Get" + gname + } w.goCode.WriteString(fmt.Sprintf(` //%s func %s(%s) %s { @@ -538,7 +636,7 @@ func %s(%s) %s { if m.ClassMethod { cobj = m.Class } else { - cobj = "(id)o" + cobj = "(" + m.Class + "*)o" } cns,cntps := w.cparamlist(m) _ = cns @@ -563,6 +661,25 @@ func %s(%s) %s { } } +func (w *Wrapper) ProcessEnum(e *Enum) { + gtp := "" + if e.Type != nil { + gtp = e.Type.GoType() + if !w.Processed[gtp] { + w.goTypes.WriteString(fmt.Sprintf(` +type %s C.%s +`,gtp,e.Type.CType())) + w.Processed[gtp] = true + } + } + gtp = gtp + " " + for _,c := range e.Constants { + w.goConst.WriteString(fmt.Sprintf(` +const %s %s= C.%s +`,c,gtp,c)) + } +} + func (w *Wrapper) Wrap(toproc []string) { if w.Package == "" { w.Package = "ns" } err := os.MkdirAll(w.Package,0755) @@ -582,6 +699,9 @@ func (w *Wrapper) Wrap(toproc []string) { } //FIXME: sort pInterfaces for _,i := range pInterfaces { + if i == nil { + continue + } fmt.Printf("Interface %s: %d properties, %d methods\n", i.Name, len(i.Properties), len(i.Methods)) @@ -589,7 +709,7 @@ func (w *Wrapper) Wrap(toproc []string) { func New%s() *%s { return (*%s)(unsafe.Pointer(C.New%s())) } -`,i.Name,i.Name,i.Name,i.Name)) +`,i.GoName,i.GoName,i.GoName,i.Name)) w.cCode.WriteString(fmt.Sprintf(` %s* @@ -614,8 +734,16 @@ New%s() { //fmt.Printf("Processing function %s %s\n",m.Type.CType(),m.Name) w.ProcessFunction(m) } + for _,e := range w.NamedEnums { + w.ProcessEnum(e) + } + for _,e := range w.AnonEnums { + w.ProcessEnum(e) + } 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") + of.WriteString(w.cgoFlags.String() + "\n") of.WriteString(w.cCode.String()) of.WriteString(` */ @@ -626,6 +754,7 @@ import ( ) `) of.WriteString(w.goTypes.String()) + of.WriteString(w.goConst.String()) of.WriteString(w.goHelpers.String()) of.WriteString(w.goCode.String()) of.Close()