nswrap/wrap/main.go

2269 lines
56 KiB
Go

package wrap
import (
"fmt"
"os"
"path"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"git.wow.st/gmp/nswrap/ast"
"git.wow.st/gmp/nswrap/types"
)
var (
Debug = false
// Arc flag is for debugging only, your builds will break if you turn it on
Arc = false
Autorelease = false
Gogc = false
)
type Wrapper struct {
Package string
Interfaces map[string]*Interface
Functions map[string]*Method
NamedEnums map[string]*Enum
AnonEnums []*Enum
Delegates map[string]map[string][]string
Subclasses map[string]*Subclass
Protocols map[string]*Protocol
Frameworks []string
Frameworkdirs []string
Pragmas []string
cgoFlags strings.Builder // put cGo directives here
cImports strings.Builder // put C imports and sysimports here
goImports map[string]bool // put required go imports 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
ProcessedTypes map[string]bool
ProcessedClassMethods map[string]bool
Vaargs int
}
func NewWrapper(debug bool) *Wrapper {
Debug = debug
types.Debug = Debug
if Debug {
fmt.Println("// Debug mode")
}
ret := &Wrapper{
Interfaces: map[string]*Interface{},
Functions: map[string]*Method{},
NamedEnums: map[string]*Enum{},
AnonEnums: []*Enum{},
Protocols: map[string]*Protocol{},
Subclasses: map[string]*Subclass{},
ProcessedTypes: map[string]bool{},
ProcessedClassMethods: map[string]bool{},
Vaargs: 16,
goImports: map[string]bool{},
}
ret.goImports["unsafe"] = true
if Gogc {
types.Gogc = true
}
ret.goTypes.WriteString(`
type Id struct {
ptr unsafe.Pointer
}
func (o *Id) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
`)
return ret
}
func (w *Wrapper) Import(ss []string) {
for _, s := range ss {
w.cImports.WriteString("\n#import \"" + s + "\"\n")
}
}
func (w *Wrapper) SysImport(ss []string) {
for _, s := range ss {
w.cImports.WriteString("\n#import <" + s + ">\n")
}
}
func (w *Wrapper) Delegate(ds map[string]map[string][]string) {
w.Delegates = ds
}
func (w *Wrapper) Subclass(ds map[string]map[string][]string) {
for k, v := range ds {
sc := &Subclass{
Overrides: []string{},
NewMethods: []string{},
}
if len(ds) == 0 {
fmt.Printf("No superclass specified for subclass %s\n", k)
os.Exit(-1)
}
if len(ds) > 1 {
fmt.Printf("Multiple inheritance not permitted for subclass %s\n", k)
os.Exit(-1)
}
sc.Name = k
for x, y := range v {
sc.Super = x
for _, m := range y {
switch m[0] {
case '-', '+':
sc.NewMethods = append(sc.NewMethods, m)
default:
sc.Overrides = append(sc.Overrides, m)
}
}
}
w.Subclasses[sc.Name] = sc
}
}
type Subclass struct {
Name, Super string
Overrides []string
NewMethods []string
}
type Property struct {
Name, Attr string
Type *types.Type
retained, notretained bool
}
type Parameter struct {
Pname, Vname string
Type *types.Type
}
type Method struct {
Name, GoName, Class, GoClass string
Type *types.Type
ClassMethod bool
Parameters []*Parameter
Unavailable bool
}
// ShouldFinalize returns true on a method that returns an object that should
// have a GC finalizer.
func (m *Method) ShouldFinalize() bool {
grtype := m.Type.GoType()
return Gogc && grtype != "NSAutoreleasePool" &&
(types.PtrShouldWrap(grtype) || grtype == "*Id") &&
(!m.ClassMethod || IsRetained(m.Name))
}
// IsRetained returns true if a given instance method returns a retained object.
// NSWrap will not send a 'retain' message to these objects before returning
// them to Go.
func IsRetained(name string) bool {
return (
(len(name) >= 3 && name[:3] == "new") ||
(len(name) >= 4 && name[:4] == "init") ||
(len(name) >= 4 && name[:4] == "copy") ||
(len(name) >= 5 && name[:5] == "alloc") ||
(len(name) >= 11 && name[:11] == "mutableCopy"))
}
// IsRetainedProperty returns true if the name matches a retained property for
// the given interface.
func (i *Interface) IsRetainedProperty(name string) bool {
if p, ok := i.Properties[name]; ok {
if p.retained {
return true
}
if p.notretained {
return false
}
attrs := strings.Split(p.Attr," ")
for _, a := range attrs {
if a == "retain" {
p.retained = true // cache this result
return true
}
}
p.notretained = true // cache this result
}
return false
}
//Fully disambiguated method name (m.GoName + all parameter names)
func (m *Method) LongName() string {
ret := m.GoName
for _, p := range m.Parameters {
ret = ret + p.Pname
}
return ret
}
func (m *Method) HasUnsupportedType() bool {
return m.Type.IsFunction() ||
m.Type.IsFunctionPtr() ||
m.hasUnsupportedParam()
}
type Enum struct {
Name string
Type *types.Type
Constants []string
}
type Protocol struct {
InstanceMethods, ClassMethods *MethodCollection
}
type MethodCollection struct {
Class, GoClass string
Methods []*Method
}
func NewMethodCollection(class string) *MethodCollection {
ret := &MethodCollection{
Class: class,
GoClass: strings.Title(class),
Methods: []*Method{},
}
return ret
}
type ByParams []*Method
func (a ByParams) Len() int { return len(a) }
func (a ByParams) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByParams) Less(i, j int) bool { return len(a[i].Parameters) < len(a[j].Parameters) }
//Disambiguate overloaded method names
func Disambiguate(mc *MethodCollection) {
lst := map[string][]*Method{}
for _, m := range mc.Methods {
lst2 := lst[m.Name]
if lst2 == nil {
lst2 = []*Method{m}
} else {
lst2 = append(lst2, m)
}
lst[m.Name] = lst2
}
mc.Methods = []*Method{}
used := map[string]bool{}
for _, v := range lst {
sort.Sort(ByParams(v))
for _, m := range v {
if len(v) < 2 || len(m.Parameters) < 2 {
if !used[m.Name] {
mc.Methods = append(mc.Methods, m)
used[m.Name] = true
}
continue
}
i := 2
pname := m.Name + strings.Title(m.Parameters[1].Pname)
for ; used[pname] && i < len(m.Parameters); i++ {
pname = pname + strings.Title(m.Parameters[i].Pname)
}
used[pname] = true
m.GoName = strings.Title(pname)
mc.Methods = append(mc.Methods, m)
}
}
}
//isVoid() returns true if the method has no return value.
func (m Method) isVoid() bool {
return m.Type.CType() == "void"
}
//hasUnsupportedParam() returns true if a method has a function as a parameter.
func (m Method) hasUnsupportedParam() bool {
for _, p := range m.Parameters {
if p.Type.IsFunction() || p.Type.IsFunctionPtr() {
return true
}
if pt := p.Type.PointsTo(); pt.IsValist() {
return true
}
}
return false
}
func (w Wrapper) cparamlist(m *Method) (string, string, string) {
ns := make([]string, 0)
ret := make([]string, 0)
tps := []string{"void*"}
if !m.ClassMethod {
ret = append(ret, "void* o")
}
for _, p := range m.Parameters {
var tp string
gt := p.Type.GoType()
wp := types.ShouldWrap(gt)
switch {
case len(gt) > 2 && gt[:1] == "*" && types.PtrShouldWrap(gt[1:]):
tp = "void**"
case wp || p.Type.IsPointer() || p.Type.Variadic:
tp = "void*"
default:
tp = p.Type.CType()
}
ns = append(ns, p.Vname)
ret = append(ret, fmt.Sprintf("%s %s", tp, p.Vname))
tps = append(tps, tp)
}
return strings.Join(ns, ", "), strings.Join(ret, ", "), strings.Join(tps, ", ")
}
func (w Wrapper) objcparamlist(m *Method) string {
if len(m.Parameters) == 0 {
return m.Name
}
first := true
ret := []string{}
pname := ""
for _, p := range m.Parameters {
gt := p.Type.GoType()
if first {
first = false
pname = m.Name
} else {
pname = p.Pname
}
switch {
case len(gt) > 2 && gt[:1] == "*" && types.PtrShouldWrap(gt[1:]):
ret = append(ret, pname+":("+p.Type.Node.CType()+")"+p.Vname)
case !p.Type.Variadic:
ret = append(ret, pname+":"+p.Vname)
first = false
case p.Type.Variadic:
str := []string{p.Pname + ", arr[0]"}
for i := 1; i < w.Vaargs; i++ {
str = append(str, "arr["+strconv.Itoa(i)+"]")
}
str = append(str, "nil")
ret = append(ret, strings.Join(str, ", "))
}
}
return strings.Join(ret, " ")
}
//goreserved is a map telling whether a word is a go reserved word that is not
//also a C/Objective-C reserved word.
var goreserved map[string]bool = map[string]bool{
"range": true,
"type": true,
"len": true,
}
func (w *Wrapper) gpntp(m *Method) ([]string, []string, []string, []*types.Type, string) {
ns := []string{}
pnames := []string{}
tps := []*types.Type{}
if !m.ClassMethod {
ns = append(ns, "o")
pnames = append(pnames, m.Name)
tps = append(tps, types.NewTypeFromString(m.Class+"*", ""))
}
for i, p := range m.Parameters {
gname := p.Vname
if goreserved[gname] {
gname = gname + "_"
}
if gname == "" {
gname = fmt.Sprintf("p%d",i)
}
ns = append(ns, gname)
pnames = append(pnames, p.Pname)
tps = append(tps, p.Type)
}
w.processTypes(tps)
ret := []string{}
snames := make([]string, len(ns))
i := 0
if !m.ClassMethod {
i = 1
}
for ; i < len(ns); i++ {
gt := tps[i].GoType()
if gt == "*Void" {
gt = "unsafe.Pointer"
}
if types.PtrIsGoInterface(gt) {
gt = gt[1:]
}
if tps[i].Variadic {
gt = "..." + gt
ns[i] = ns[i] + "s"
}
if len(gt) > 2 && gt[:1] == "*" && types.PtrShouldWrap(gt[1:]) {
x := gt[1:]
if types.PtrIsGoInterface(x) {
x = "*Id"
}
gt = "*[]" + x
snames[i] = "goSlice" + strconv.Itoa(i)
}
ret = append(ret, ns[i]+" "+gt)
}
return ns, pnames, snames, tps, strings.Join(ret, ", ")
}
type Interface struct {
Name, GoName string
InstanceMethods, ClassMethods *MethodCollection
Properties map[string]*Property
Protocols []string // Protocols impelemented by this Interface
ProcessedInstanceMethods map[string]bool
}
//AddInterface adds an Objective-C interface to a Wrapper.
func (w *Wrapper) AddInterface(n *ast.ObjCInterfaceDecl) {
if Debug {
fmt.Printf("ast.ObjCInterfaceDecl: %s\n", n.Name)
}
//fmt.Printf("AddInterface(%s)\n",n.Name)
w.addIntCat(n.Name, n.Children())
}
//AddCategory adds an Objective-C category to a Wrapper.
//the first child node of an ObjCCategoryDecl is always an ObjCInterface
//indicating which interface is being extended by this category.
func (w *Wrapper) AddCategory(n *ast.ObjCCategoryDecl) {
ns := n.Children()
if len(ns) > 0 {
switch x := ns[0].(type) {
case *ast.ObjCInterface:
//fmt.Printf("AddCategory(%s) -> %s\n",n.Name, x.Name)
w.addIntCat(x.Name, ns[1:])
return
}
}
fmt.Printf("Not adding methods for %s: interface name not found in first child node of category defclaration\n", n.Name)
}
func (w *Wrapper) AddFunction(n *ast.FunctionDecl) {
//treat functions as class methods with no class
tp := types.NewTypeFromString(n.Type, "")
m := &Method{
Name: n.Name,
GoName: strings.Title(n.Name),
Type: tp.ReturnType(),
Class: "",
ClassMethod: true,
Parameters: []*Parameter{},
}
f := tp.Node.Children[len(tp.Node.Children)-1] // Function node
if f.Kind != "Function" {
//fmt.Printf("AddFunction(%s): not a function -- Node type is %s\n%s",n.Name,f.Kind,tp.String())
return
}
if Debug {
fmt.Printf("FunctionDecl: %s (%s) %s\n", n.Type, m.Type.CType(), n.Name)
}
i := 0
for _, c := range n.Children() {
switch x := c.(type) {
case *ast.ParmVarDecl:
p := &Parameter{
Vname: x.Name,
Type: types.NewTypeFromString(x.Type, ""),
}
m.Parameters = append(m.Parameters, p)
i++
if Debug {
fmt.Printf(" %s\n", p.Type.CType())
}
case *ast.Variadic:
p := &Parameter{
Vname: "object",
Type: types.NewTypeFromString("NSObject*", ""),
}
p.Type.Variadic = true
m.Parameters = append(m.Parameters, p)
i++
}
}
w.Functions[n.Name] = m
}
func (w *Wrapper) AddProtocol(n *ast.ObjCProtocolDecl) {
p := w.Protocols[n.Name]
if p == nil {
if Debug {
fmt.Printf("Adding protocol %s\n", n.Name)
}
p = &Protocol{
ClassMethods: NewMethodCollection(n.Name),
InstanceMethods: NewMethodCollection(n.Name),
}
}
if Debug {
fmt.Printf("Protocol %s\n", n.Name)
}
for _, c := range n.Children() {
switch x := c.(type) {
case *ast.ObjCMethodDecl:
if Arc {
switch x.Name {
case "retain", "release", "autorelease":
continue
}
}
if x.ClassMethod {
w.AddMethod(p.ClassMethods, x)
} else {
w.AddMethod(p.InstanceMethods, x)
}
}
}
Disambiguate(p.InstanceMethods)
Disambiguate(p.ClassMethods)
w.Protocols[n.Name] = p
}
func (w *Wrapper) AddMethod(p *MethodCollection, x *ast.ObjCMethodDecl) {
m := &Method{
Name: x.Name,
GoName: strings.Title(x.Name),
Type: types.NewTypeFromString(x.Type, p.Class),
Class: p.Class,
GoClass: strings.Title(p.Class),
ClassMethod: x.ClassMethod,
Unavailable: false,
}
if Debug {
fmt.Printf(" -- Method %s\n", m.Name)
}
var avail bool
m.Parameters, avail, m.Unavailable = w.GetParms(x, p.Class)
if avail {
if Debug {
fmt.Printf("%s: Adding %s (%d)\n", p.Class, m.Name, len(m.Parameters))
}
p.Methods = append(p.Methods, m)
} else {
if Debug {
fmt.Printf("--Method %s is not available\n", m.Name)
}
}
}
//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
}
if Debug {
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, "")
if Debug {
fmt.Printf(" type: %s -> %s\n", n.Type, 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, *ast.DeprecatedAttr:
a.Add(x)
case *ast.EnumConstantDecl:
if Debug {
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
}
if Debug {
fmt.Printf(" added\n")
}
}
}
//Add an Interface or add a Category to an Interface
func (w *Wrapper) addIntCat(name string, ns []ast.Node) {
var i *Interface
var ok bool
goname := strings.Title(name)
types.Wrap(goname)
if i, ok = w.Interfaces[name]; !ok {
i = &Interface{}
i.Name = name
i.GoName = goname
i.InstanceMethods = NewMethodCollection(name)
i.ClassMethods = NewMethodCollection(name)
i.Properties = map[string]*Property{}
i.Protocols = []string{}
i.ProcessedInstanceMethods = map[string]bool{}
}
avail := (*Avail)(&[]AvailAttr{})
mcc := NewMethodCollection(name)
mci := NewMethodCollection(name)
prots := []string{}
for _, c := range ns {
switch x := c.(type) {
case *ast.ObjCPropertyDecl:
if Debug {
fmt.Printf("ObjCPropertyDecl: %s\n", x.Name)
}
p := &Property{
Name: x.Name,
Type: types.NewTypeFromString(x.Type,name),
Attr: x.Attr,
}
i.Properties[x.Name] = p
case *ast.ObjCMethodDecl:
if Debug {
fmt.Printf("ObjCMethodDecl: %s (%s) %s\n", x.Type, name, x.Name)
}
if name == "NSObject" && x.Name == "initialize" {
continue
}
if x.ClassMethod {
w.AddMethod(mcc, x)
} else {
w.AddMethod(mci, x)
}
case *ast.ObjCProtocol:
if Debug {
fmt.Printf("ast.ObjCProtocol: %s\n", x.Name)
}
prots = append(prots, x.Name)
case *ast.ObjCInterface:
if x.Super {
if Debug {
fmt.Printf("ast.ObjCInterface: %s inherits from %s\n", name, x.Name)
}
types.SetSuper(name, x.Name)
}
case *ast.ObjCTypeParamDecl:
if Debug {
fmt.Printf("ObjCTypeParamDecl: %s = %s\n", x.Name, x.Type)
}
types.SetTypeParam(name, x.Name, x.Type)
case *ast.AvailabilityAttr, *ast.UnavailableAttr, *ast.DeprecatedAttr:
avail.Add(x)
case *ast.Unknown:
if Debug {
fmt.Printf("(*ast.Unkonwn %s: %s)\n", x.Name, x.Content)
}
case *ast.ObjCRootClassAttr, *ast.VisibilityAttr,
*ast.ObjCIvarDecl, *ast.ArcWeakrefUnavailableAttr,
*ast.ObjCExceptionAttr:
default:
fmt.Printf("AST parse error: node type is %s\n", reflect.TypeOf(x).String())
}
}
if !avail.Available() {
if Debug {
fmt.Printf("-- %s is not available\n", i.Name)
}
return
}
i.ClassMethods.Methods = append(i.ClassMethods.Methods, mcc.Methods...)
i.InstanceMethods.Methods = append(i.InstanceMethods.Methods, mci.Methods...)
i.Protocols = append(i.Protocols, prots...)
if Debug {
fmt.Println("Add interface ", i.Name)
}
//fmt.Printf("Interface = %s %d class methods, %d instance methods\n", i.Name, len(i.ClassMethods.Methods), len(i.InstanceMethods.Methods))
Disambiguate(i.ClassMethods)
Disambiguate(i.InstanceMethods)
//fmt.Printf("Interface = %s (disambiguated) %d class methods, %d instance methods\n", i.Name, len(i.ClassMethods.Methods), len(i.InstanceMethods.Methods))
w.Interfaces[i.Name] = i
}
type AvailAttr struct {
OS, Version string
Deprecated bool
}
type Avail []AvailAttr
func (a *Avail) Add(n ast.Node) {
switch x := n.(type) {
case *ast.AvailabilityAttr:
if Debug {
fmt.Printf(" AvailabilityAttr: OS: %s, Version: %s, Deprecated: %t\n", x.OS, x.Version, (x.Unknown1 != "0") || x.IsUnavailable)
}
if x.OS != "macos" {
return
}
*a = append(*a, AvailAttr{
OS: x.OS,
Version: x.Version,
Deprecated: (x.Unknown1 != "0") || x.IsUnavailable,
})
case *ast.UnavailableAttr, *ast.DeprecatedAttr:
if Debug {
fmt.Printf(" DeprecatedAttr\n")
}
*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, bool) {
ret := make([]*Parameter, 0)
unavail := false
avail := (*Avail)(&[]AvailAttr{})
var parms []string
switch x := n.(type) {
case *ast.ObjCMethodDecl:
parms = x.Parameters
case *ast.FunctionDecl:
default:
panic("GetParms called with wrong node type")
}
j := 0
for _, c := range n.Children() {
switch x := c.(type) {
case *ast.ParmVarDecl:
p := &Parameter{
Vname: x.Name,
Type: types.NewTypeFromString(x.Type, class),
}
if parms != nil {
p.Pname = parms[j]
}
ret = append(ret, p)
j++
case *ast.Variadic:
//ret[j-1].Type.Variadic = true
p := &Parameter{
Vname: "object",
Type: types.NewTypeFromString("NSObject*", ""),
}
p.Type.Variadic = true
ret = append(ret, p)
j++
case *ast.UnavailableAttr:
unavail = true
case *ast.AvailabilityAttr, *ast.DeprecatedAttr:
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
if !avail.Available() {
return nil, false, unavail
}
return ret, true, unavail
}
func (w *Wrapper) AddTypedef(n, t string) {
tp := types.NewTypeFromString(t, "")
gt := tp.GoType()
if Debug {
fmt.Printf("Typedef %s -> %s\n", n, t)
}
if types.ShouldWrap(gt) {
if Debug {
fmt.Printf(" processing wrapped type for %s (%s)\n", n, gt)
}
types.Wrap(n)
types.SetSuper(n, gt)
w._processType(tp)
} else {
cgt := tp.CGoType()
if Debug {
fmt.Printf(" processing un-wrapped type for %s -> %s\n", n, cgt)
}
types.AddTypedef(n, tp)
}
}
func (w *Wrapper) processTypes(tps []*types.Type) {
for _, tp := range tps {
w.processType(tp)
}
}
func (w *Wrapper) processType(tp *types.Type) {
bt := tp.BaseType()
w._processType(bt)
}
//func (w *Wrapper) _processType(bt *types.Type, gt string) {
func (w *Wrapper) _processType(bt *types.Type) {
gt := bt.GoType()
if Debug {
fmt.Printf("processType: gt = %s bt = %s\n", gt, bt)
}
if gt == "" {
return
}
if gt[0] == '*' {
w.processType(bt.PointsTo())
return
}
if w.ProcessedTypes[gt] {
if Debug { fmt.Printf(" -- already seen\n") }
return
}
if Debug { fmt.Printf(" -- not yet seen\n") }
w.ProcessedTypes[gt] = true
if gt == "Char" {
w.CharHelpers()
}
if gt == "SEL" {
w.SelectorHelpers()
}
if gt == "NSAutoreleasePool" {
w.AutoreleaseHelpers()
}
if bt.IsFunction() || bt.IsFunctionPtr() {
return
}
super := types.Super(gt)
if super != "" {
tp := types.NewTypeFromString(super, "")
types.Wrap(tp.GoType())
w.processType(tp)
}
if Debug {
fmt.Printf("Writing go type for %s -> %s\n",bt.CType(),gt)
}
w.goTypes.WriteString(bt.GoTypeDecl(Gogc))
}
func (w *Wrapper) CharHelpers() {
w.goHelpers.WriteString(`
func CharWithGoString(s string) *Char {
return (*Char)(unsafe.Pointer(C.CString(s)))
}
func CharWithBytes(b []byte) *Char {
return (*Char)(unsafe.Pointer(C.CString(string(b))))
}
func (c *Char) String() string {
return C.GoString((*C.char)(c))
}
func (c *Char) Free() {
C.free(unsafe.Pointer(c))
}
`)
}
func (w *Wrapper) StringHelpers() {
ufree := ""
if Gogc {
ufree = "utf8.Free()\n\t"
}
w.goHelpers.WriteString(fmt.Sprintf(`
func (o *NSString) String() string {
utf8 := o.UTF8String()
ret := utf8.String()
%sreturn ret
}
`,ufree))
}
func (w *Wrapper) EnumeratorHelpers() {
var re1, re2 string
if Gogc && false { // FIXME: don't need this
re1 = "o.Release(); "
re2 = `
o.Release()`
}
w.goHelpers.WriteString(fmt.Sprintf(`
func (e *NSEnumerator) ForIn(f func(*Id) bool) {
for o := e.NextObject(); o.Ptr() != nil; o = e.NextObject() {
if !f(o) { %sbreak }%s
}
}
`, re1, re2))
}
func (w *Wrapper) AutoreleaseHelpers() {
w.goHelpers.WriteString(`
func Autoreleasepool(f func()) {
pool := NSAutoreleasePoolAlloc().Init()
f()
pool.Drain()
}
`)
}
func (w *Wrapper) SelectorHelpers() {
w.cCode.WriteString(`
void*
selectorFromString(char *s) {
return NSSelectorFromString([NSString stringWithUTF8String:s]);
}
`)
w.goHelpers.WriteString(`
func Selector(s string) SEL {
return (SEL)(unsafe.Pointer(C.selectorFromString(C.CString(s))))
}
`)
}
func (w *Wrapper) ProcessMethod(m *Method) {
w._processMethod(m, false)
}
func (w *Wrapper) ProcessMethodForClass(m *Method, class string) {
goclass := strings.Title(class)
m2 := &Method{
Name: m.Name, GoName: m.GoName, Class: class, GoClass: goclass,
Type: m.Type.CloneToClass(class),
ClassMethod: m.ClassMethod,
Parameters: make([]*Parameter, len(m.Parameters)),
Unavailable: m.Unavailable,
}
for i, p := range m.Parameters {
m2.Parameters[i] = &Parameter{
Pname: p.Pname, Vname: p.Vname,
Type: p.Type.CloneToClass(class),
}
}
w._processMethod(m2, false)
}
func (w *Wrapper) ProcessFunction(m *Method) {
if m.Type.Node.IsId() {
//do not wrap functions that return ID because of CGo struct size bug
return
}
w._processMethod(m, true)
}
func (w *Wrapper) _processMethod(m *Method, fun bool) {
if Debug {
fmt.Printf(" method: %s (%s)\n", m.Name, m.Type)
}
if m.HasUnsupportedType() {
return
}
if m.Unavailable {
return
}
w.processType(m.Type)
gname := m.GoName
gname = strings.ReplaceAll(gname, "_", " ")
gname = strings.Title(gname)
gname = strings.ReplaceAll(gname, " ", "")
receiver := ""
constructor := false // this is an autoreleased object constructor
var cname string
if fun {
cname = m.Name
} else {
cname = gname
}
if Debug {
fmt.Printf("Method %s (GoClass %s)\n", cname, m.GoClass)
}
grtype := m.Type.GoType()
if grtype == "Void" {
grtype = ""
}
if types.PtrIsGoInterface(grtype) {
grtype = "*Id"
}
if grtype == "BOOL" { // convert objective-c bools to Go bools
grtype = "bool"
}
switch {
case !m.ClassMethod:
if types.IsGoInterface(m.GoClass) {
receiver = "(o *Id) "
} else {
receiver = "(o *" + m.GoClass + ") "
}
//Disambiguate instance methods with same name as a class method
cname = "inst_" + cname
case m.ClassMethod:
//Shorten class method names
lens1 := len(m.Class)
i := 0
if len(gname) < lens1 {
i = lens1 - len(gname)
}
for ; i < lens1; i++ {
if m.Class[i:] == gname[:lens1-i] {
if Gogc &&
(types.PtrShouldWrap(grtype) || grtype == "*Id") {
constructor = true
}
break
}
}
if lens1-i >= len(gname) {
gname = m.GoClass + gname
} else {
gname = m.GoClass + gname[lens1-i:]
}
}
if m.Class != "" {
cname = m.Class + "_" + cname
}
var cmtype string
if m.Type.IsPointer() || types.ShouldWrap(m.Type.GoType()) {
// work around cgo bugs with struct size calculation
cmtype = "void*"
if x := m.Type.Node.Qualifiers(); x != "" {
cmtype = x + " " + cmtype
}
if x := m.Type.Node.Annotations(); x != "" {
cmtype = cmtype + " " + x
}
} else {
cmtype = m.Type.CTypeAttrib()
}
ns, pnames, snames, tps, gplist := w.gpntp(m)
if gname == grtype { // avoid name conflicts between methods and types
gname = "Get" + gname
}
var inter *Interface
if m.ClassMethod {
if w.ProcessedClassMethods[gname] {
return
}
w.ProcessedClassMethods[gname] = true
} else {
var ok bool
inter,ok = w.Interfaces[m.Class]
if !ok {
fmt.Printf("Can't find interface %s for method %s\n", m.Class, m.Name)
os.Exit(-1)
}
if inter.ProcessedInstanceMethods[gname] {
return
}
inter.ProcessedInstanceMethods[gname] = true
}
w.goCode.WriteString(fmt.Sprintf(`
func %s%s(%s) %s {
`, receiver, gname, gplist, grtype))
lparm := len(tps) - 1
if len(tps) > 0 && tps[lparm].Variadic {
vn := ns[lparm]
vn = vn[:len(vn)-1]
ns[lparm] = vn
w.goCode.WriteString(fmt.Sprintf(
` var %s [%d]unsafe.Pointer
for i,o := range %ss {
%s[i] = o.Ptr()
}
`, vn, w.Vaargs, vn, vn))
}
for i, n := range ns {
if snames[i] == "" {
continue
}
w.goCode.WriteString(fmt.Sprintf(`
%s := make([]unsafe.Pointer,cap(*%s))
for i := 0; i < len(*%s); i++ {
%s[i] = (*%s)[i].Ptr()
}
`, snames[i], n, n, snames[i], n))
}
w.goCode.WriteString(` ` +
types.GoToC(m.Name, cname, ns, snames, m.Type, tps, fun, constructor || m.ShouldFinalize(), m.ClassMethod) + "\n}\n")
cret := ""
if !m.isVoid() {
cret = "return "
}
var cobj string
if m.ClassMethod {
cobj = m.Class
} else {
cobj = "(" + m.Class + "*)o"
}
cns, cntps, _ := w.cparamlist(m)
if fun {
return
}
w.cCode.WriteString(fmt.Sprintf(`
%s
%s(%s) {
`, cmtype, cname, cntps))
if len(tps) > 0 && tps[lparm].Variadic {
w.cCode.WriteString(fmt.Sprintf(
` %s* arr = %s;
`, tps[lparm].CType(), ns[lparm]))
}
switch {
case fun:
w.cCode.WriteString(fmt.Sprintf(` %s%s(%s);
}`, cret, m.Name, cns))
case len(m.Name) >= 5 && m.Name[:5] == "alloc" && m.Class != "NSAutoreleasePool":
if Autorelease {
w.cCode.WriteString(fmt.Sprintf(` %s[[%s %s] autorelease];
}`, cret, cobj, w.objcparamlist(m)))
} else {
w.cCode.WriteString(fmt.Sprintf(` %s[%s %s];
}`, cret, cobj, w.objcparamlist(m)))
}
default:
//if Gogc && !m.isVoid() {
if Gogc {
rtn := ""
if types.PtrShouldWrap(m.Type.GoType()) {
switch {
case m.ClassMethod:
if grtype != "*NSAutoreleasePool" && constructor {
// retain objects returned by class constructor methods
rtn = `
if(ret != nil) { [ret retain]; }`
}
// do not retain new, alloc, init and copy methods
case IsRetained(m.Name):
default:
// by default, for instance methods, retain
// if returning a new object
rtn = `
if (ret != nil && ret != o) { [ret retain]; }`
}
}
rtns := []string{}
// for pointers to pointers, assume length 1 unless there is a
// parameter named "range" or "count".
rlength := "i<1"
for i,n := range pnames {
vn := strings.ReplaceAll(ns[i],"_","")
if n == "range" {
rlength = "i<" + vn + ".length"
}
if n == "count" {
rlength = "i<" + vn
}
}
for i,n := range ns {
if snames[i] == "" {
continue
}
rtns = append(rtns,fmt.Sprintf(`
for(int i=0;%s;i++) {
if(%s[i] == 0) { break; }
[(id)%s[i] retain];
}
`, rlength, n, n))
}
var retdecl, reteq, retretn, dup1, dup2 string
if !m.isVoid() {
retdecl = fmt.Sprintf("%s ret;\n\t", m.Type.CTypeAttrib())
reteq = "ret = "
retretn = "\n\treturn ret;\n"
if m.Type.CType() == "char*" {
dup1 = "strdup("
dup2 = ")"
}
}
w.cCode.WriteString(fmt.Sprintf(
` %s@autoreleasepool {
%s%s[%s %s]%s;%s%s
}%s
}`, retdecl, reteq, dup1, cobj, w.objcparamlist(m), dup2, rtn, strings.Join(rtns,"\n\t"), retretn))
} else {
w.cCode.WriteString(fmt.Sprintf(` %s[%s %s];
}`, cret, cobj, w.objcparamlist(m)))
}
}
// create SetFinalizer methods when we see an alloc function:
if Gogc && m.Name == "alloc" {
cls := m.GoClass
if types.IsGoInterface(cls) {
cls = "Id"
}
dbg := ""
dbg2 := ""
if Debug {
dbg = fmt.Sprintf(`fmt.Printf("Setting GC finalizer (%s): %%p -> %%p\n", o, o.ptr)
`,cls)
dbg2 = fmt.Sprintf(`fmt.Printf("GC finalizer (%s): release %%p -> %%p\n", o, o.ptr)
`, cls)
}
w.goImports["runtime"] = true
w.goCode.WriteString(fmt.Sprintf(`
func (o *%s) GC() {
if o.ptr == nil { return }
%sruntime.SetFinalizer(o, func(o *%s) {
%so.Release()
})
}
`, cls, dbg, cls, dbg2))
}
// create GoString helper method
if ok, _ := regexp.MatchString("WithString$", m.Name); ok {
if Debug {
fmt.Printf("--%s\n", gname)
}
cvts := ""
gname2 := gname[:len(gname)-6] + "GoString"
gps := []string{}
i := 0
if !m.ClassMethod {
i = 1
}
for ; i < len(ns); i++ {
gt := tps[i].GoType()
if Debug {
fmt.Printf(" %s\n", gt)
}
ns2 := ns[i]
if gt == "*NSString" {
gt = "string"
//ns[i] = gStringToNsstring(ns[i])
cvts = gStringToNSString(ns[i])
ns[i] = "NSStringWithUTF8String(" + ns[i] + "_chr)"
}
gps = append(gps, ns2+" "+gt)
}
gplist = strings.Join(gps, ", ")
obj := ""
if !m.ClassMethod {
obj = "o."
ns = ns[1:]
}
w.goCode.WriteString(fmt.Sprintf(`
func %s%s(%s) %s {
%sret := %s%s(%s)
return ret
}
`, receiver, gname2, gplist, grtype, cvts, obj, gname, strings.Join(ns, ", ")))
}
}
func gStringToNSString(s string) string {
return fmt.Sprintf(
`%s_chr := CharWithGoString(%s)
defer %s_chr.Free()
`, s, s, s)
}
func (w *Wrapper) ProcessEnum(e *Enum) {
if Debug {
fmt.Printf("Processing enum (%s)\n", e.Name)
}
w.processType(e.Type)
gtp := ""
ctp := ""
if e.Name != "" {
gtp = e.Name
ctp = "C." + e.Name
} else {
gtp = e.Type.GoType()
ctp = e.Type.CGoType()
}
if e.Type != nil {
if !w.ProcessedTypes[gtp] {
w.goTypes.WriteString(fmt.Sprintf(`
type %s %s
`, gtp, ctp))
w.ProcessedTypes[gtp] = true
}
}
gtp = gtp + " "
if Debug {
fmt.Printf(" gtp = %s; ctp = %s\n", gtp, ctp)
}
for _, c := range e.Constants {
w.goConst.WriteString(fmt.Sprintf(`const %s %s= C.%s
`, c, gtp, c))
}
w.goConst.WriteString("\n")
}
func (w *Wrapper) MethodFromSig(sig, class string) *Method {
ret := &Method{Parameters: []*Parameter{}}
if len(sig) == 0 {
return ret
}
if sig[0] == '+' {
ret.ClassMethod = true
}
sig = sig[1:]
rem, n := types.MethodSignature(sig, types.NewNode("AST"))
if len(rem) > 0 {
fmt.Printf("Failed to parse method signature %s (%s)\n", sig, rem)
os.Exit(-1)
}
i := 0 // count MethodParameters
for _, c := range n.Children {
switch c.Kind {
case "TypeName":
tp := types.NewType(c, class)
ret.Type = tp
case "Identifier":
ret.Name = c.Content
ret.GoName = strings.Title(c.Content)
case "MethodParameter":
p := &Parameter{}
for _, d := range c.Children {
switch d.Kind {
case "TypeName":
tp := types.NewType(d, class)
p.Type = tp
case "Identifier":
if i == 0 || p.Pname != "" {
p.Vname = d.Content
} else {
p.Pname = d.Content
}
}
}
i++
ret.Parameters = append(ret.Parameters, p)
}
}
return ret
}
func (mc *MethodCollection) AddMethod(m *Method) {
m2 := &Method{
Name: m.Name,
GoName: m.GoName,
Class: mc.Class,
GoClass: mc.GoClass,
Type: m.Type.CloneToClass(mc.Class),
ClassMethod: m.ClassMethod,
Parameters: []*Parameter{},
Unavailable: m.Unavailable,
}
for _, p := range m.Parameters {
p2 := &Parameter{
Pname: p.Pname,
Vname: p.Vname,
Type: p.Type.CloneToClass(mc.Class),
}
m2.Parameters = append(m2.Parameters, p2)
}
mc.Methods = append(mc.Methods, m2)
}
func (mc *MethodCollection) AddMethods(smc *MethodCollection) {
for _, m := range smc.Methods {
mc.AddMethod(m)
}
}
func (w *Wrapper) ProcessSubclass(sname string, sc *Subclass) {
i := &Interface{
ProcessedInstanceMethods: map[string]bool{},
Properties: map[string]*Property{},
}
w.Interfaces[sname] = i
gname := strings.Title(sname)
types.Wrap(gname)
types.SetSuper(gname, sc.Super)
ps := map[string][]string{}
ps[sc.Super] = sc.Overrides
nms := make([]*Method, len(sc.NewMethods))
for i, sig := range sc.NewMethods {
nms[i] = w.MethodFromSig(sig, sname)
}
if Debug {
fmt.Printf("ProcessSubclass(%s)\n",sname)
}
w._ProcessDelSub(sname, ps, nms, true)
}
func (w *Wrapper) ProcessDelegate(dname string, ps map[string][]string) {
i := &Interface{
ProcessedInstanceMethods: map[string]bool{},
Properties: map[string]*Property{},
}
w.Interfaces[dname] = i
w._ProcessDelSub(dname, ps, nil, false)
}
//NOTE: The delegate wrapper does not support variadic callback functions.
func (w *Wrapper) _ProcessDelSub(dname string, ps map[string][]string, nms []*Method, sub bool) {
//To create (per delegate):
//1. ObjC interface
//2. ObjC implementation
//3. ObjC constructor function
//4. Go type
//5. Go constructor
//6. Go dispatch database for callbacks
//7. Go superclass dispatch function
//8. Methods inherited from parent class
//To create (per method):
//1. ObjC function prototypes for go exports
//2. Go callback registration functions
//3. Go exported callback function wrappers
//4. Go wrapper functions for superclass methods
//organize output into string builders
var cprotos, ccode, gotypes, gocode, goexports strings.Builder
//set up array of methods for this delegate
methods := []*Method{}
sms := 0 // the number of methods that have super-methods
gnames := []string{} // go names for methods
pnames := make([]string, len(ps))
supmeths := []*Method{}
var supr string
i := 0
for pname, pats := range ps {
pnames[i] = pname
i++
var ms []*Method
if sub {
interf := w.Interfaces[pname]
supr = interf.GoName
if i > 1 {
fmt.Printf("Multiple inheritance is not permitted:\n subclass %s already inherits from %s\n", dname, supr)
os.Exit(-1)
}
if interf == nil {
fmt.Printf("Failed to find interface %s for subclass %s\n", pname, dname)
os.Exit(-1)
}
if Debug {
fmt.Printf(" subclass for %s\n", pname)
}
mc := NewMethodCollection(dname)
var addmeths func(s string)
addmeths = func(s string) {
if sup := types.Super(s); w.Interfaces[sup] != nil {
addmeths(sup)
}
if Debug {
fmt.Printf("Adding methods for interface %s\n", s)
}
for _, m := range w.Interfaces[s].InstanceMethods.Methods {
if Debug {
fmt.Printf(" -> %s\n", m.Name)
}
mc.Methods = append(mc.Methods, m)
}
//mc.Methods = append(mc.Methods,w.Interfaces[s].InstanceMethods...)
for _, p := range w.Interfaces[s].Protocols {
if Debug {
fmt.Printf("Adding methods for protocol %s\n", p)
}
for _, m := range w.Protocols[p].InstanceMethods.Methods {
if Debug {
fmt.Printf(" -> %s\n", m.Name)
}
mc.Methods = append(mc.Methods, m)
}
}
}
//for subclasses, add all superclass methods, depth first
addmeths(interf.Name)
Disambiguate(mc)
ms = mc.Methods
} else { // not a subclass
proto := w.Protocols[pname]
if proto == nil {
fmt.Printf("Failed to find protocol %s for delegate %s\n", pname, dname)
os.Exit(-1)
}
if Debug {
fmt.Printf(" proto %s\n", pname)
}
ms = proto.InstanceMethods.Methods
if Debug {
fmt.Printf("Protocol %s\n", pname)
}
types.SetSuper(dname, "Id")
supr = "Id"
}
for _, m := range ms {
//note:we may have capitalized the first character to make a GoName...
if Debug {
fmt.Printf("--Method: %s\n", m.Name)
}
if !matches(string(m.Name[0])+m.GoName[1:], pats) {
//methods from superclass that we are not overriding
supmeths = append(supmeths,m)
continue
}
if m.HasUnsupportedType() {
continue
}
methods = append(methods, m)
gnames = append(gnames, m.GoName)
if sub {
sms = len(methods)
}
}
}
//add new methods being defined for the subclass
if sub {
for _, m := range nms {
methods = append(methods, m)
gnames = append(gnames, strings.Title(m.Name))
}
}
methprotos := make([]string, len(methods)) // objc method prototypes
smethprotos := make([]string, sms) // super method prototypes
sfunprotos := make([]string, sms) // super method prototypes
gname := strings.Title(dname) // go name for this Delegate
vnames := make([][]string, len(methods)) // objc variable names
vpnames := make([][]string, len(methods)) // objc parameter:variable names
gtypes := make([][]string, len(methods)) // go parameter types for each method
getypes := make([][]string, len(methods)) // parameter types for go export
grtypes := make([]string, len(methods)) // go retrun types for each method
cgtypes := make([]string, len(methods)) // cgo return types
crtypes := make([]string, len(methods)) // c return types for each method
//1. ObjC interface
if sub {
fmt.Printf("Subclass %s <%s>: %d overrides, %d new methods\n", dname, strings.Join(pnames, ", "), sms, len(nms))
} else {
fmt.Printf("Delegate %s <%s>: %d methods\n", dname, strings.Join(pnames, ", "), len(methods))
}
for i, m := range methods {
w.processType(m.Type)
vnames[i] = make([]string, len(m.Parameters)+1)
vpnames[i] = make([]string, len(m.Parameters))
getypes[i] = make([]string, len(m.Parameters)+1)
vnames[i][0] = "self"
getypes[i][0] = "unsafe.Pointer"
gtypes[i] = make([]string, len(m.Parameters)+1)
if m.Name != "dealloc" {
gtypes[i][0] = gname + "Supermethods"
}
if Debug {
fmt.Printf("%s: %s\n", dname, m.Name)
}
var parms string
var cparms string
if len(m.Parameters) == 0 {
parms = ""
cparms = "void* self"
vpnames[i] = []string{m.Name}
} else {
pm := m.Parameters[0]
w.processType(pm.Type)
parms = fmt.Sprintf(":(%s)%s", pm.Type.Node.CType(), pm.Vname)
cparms = fmt.Sprintf("void* self, %s %s", pm.Type.Node.CType(), pm.Vname)
vnames[i][1] = pm.Vname
vpnames[i][0] = pm.Pname + ":" + pm.Vname
gtypes[i][1] = pm.Type.GoType()
if pm.Type.IsPointer() {
getypes[i][1] = "unsafe.Pointer"
} else {
getypes[i][1] = gtypes[i][1]
}
}
for j := 1; j < len(m.Parameters); j++ {
pm := m.Parameters[j]
w.processType(pm.Type)
parms = parms + fmt.Sprintf(" %s:(%s)%s", pm.Pname, pm.Type.Node.CType(), pm.Vname)
cparms = cparms + fmt.Sprintf(", %s %s", pm.Type.Node.CType(), pm.Vname)
vnames[i][j+1] = pm.Vname
vpnames[i][j] = pm.Pname + ":" + pm.Vname
gtypes[i][j+1] = pm.Type.GoType()
var getp string
if pm.Type.IsPointer() {
getp = "unsafe.Pointer"
} else {
getp = gtypes[i][j+1]
}
getypes[i][j+1] = getp
}
methprotos[i] = fmt.Sprintf(
`- (%s)%s%s;`, m.Type.Node.CType(), m.Name, parms)
if x := m.Type.GoType(); x == "Void" {
grtypes[i] = ""
} else {
grtypes[i] = " " + x
}
ct := m.Type.Node.CType()
if ct == "instancetype" {
ct = dname + "*"
}
if ct == "id" {
ct = "void*"
grtypes[i] = " Id"
}
if i < sms {
smethprotos[i] = fmt.Sprintf(
`- (%s)super_%s%s;`, ct, m.Name, parms)
}
if i < sms {
_, cntps, _ := w.cparamlist(m)
sfunprotos[i] = fmt.Sprintf(
`%s %s_super_%s(%s);`, ct, dname, m.Name, cntps)
}
crtypes[i] = m.Type.CTypeAttrib()
if m.Type.IsPointer() {
cgtypes[i] = "unsafe.Pointer"
} else {
crtypes[i] = m.Type.CTypeAttrib()
cgtypes[i] = m.Type.CGoType()
}
}
var supcls string
var protos string
if sub {
supcls = pnames[0]
protos = ""
} else {
supcls = "NSObject"
protos = "<" + strings.Join(pnames, ", ") + ">"
}
ccode.WriteString(fmt.Sprintf(`
@interface %s : %s %s
{ }
%s
`, dname, supcls, protos, strings.Join(methprotos, "\n")))
havesupmethods := sms > 0
if sub {
for i, sp := range smethprotos {
if methods[i].Name != "dealloc" {
ccode.WriteString(sp + "\n")
} else {
if sms == 1 {
havesupmethods = false
}
}
}
}
ccode.WriteString(`
@end
`)
if sub {
for i, sf := range sfunprotos {
if methods[i].Name != "dealloc" {
ccode.WriteString(sf + "\n")
}
}
}
//2. ObjC implementation
methdecls := make([]string, len(methods))
smethdecls := make([]string, len(methods))
sfundecls := make([]string, len(methods))
for i, mp := range methprotos {
mp := mp[:len(mp)-1]
var smp, sfp, superdealloc string
if sub && i < sms {
smp = smethprotos[i][:len(smethprotos[i])-1]
sfp = sfunprotos[i][:len(sfunprotos[i])-1]
if methods[i].Name == "dealloc" {
superdealloc = "\n\t[super dealloc];"
}
}
var ret string
if crtypes[i] != "void" {
ret = "return "
}
methdecls[i] = fmt.Sprintf(`
%s
{
%s%s(%s);
}
`, mp, ret, gname+gnames[i], strings.Join(vnames[i], ", "))
methdecls[i] = fmt.Sprintf(`
%s
{
%s%s(%s);%s
}
`, mp, ret, gname+gnames[i], strings.Join(vnames[i], ", "), superdealloc)
if sub && i < sms {
smethdecls[i] = fmt.Sprintf(`
%s
{
%s[super %s];
}
`, smp, ret, strings.Join(vpnames[i], " "))
sfundecls[i] = fmt.Sprintf(`
%s
{
%s[(%s*)o super_%s];
}
`, sfp, ret, dname, strings.Join(vpnames[i], " "))
}
}
ccode.WriteString(fmt.Sprintf(`
@implementation %s
%s
`, dname, strings.Join(methdecls, "\n")))
if sub {
for i, sm := range smethdecls {
if methods[i].Name != "dealloc" {
ccode.WriteString(sm + "\n")
}
}
}
ccode.WriteString(`
@end
`)
if sub {
for i, sf := range sfundecls {
if methods[i].Name != "dealloc" {
ccode.WriteString(sf + "\n")
}
}
}
//3. ObjC constructor function
ccode.WriteString(fmt.Sprintf(`
void*
%sAlloc() {
`, dname))
if Autorelease {
ccode.WriteString(fmt.Sprintf(
` return [[%s alloc] autorelease];
}
`, dname))
} else {
ccode.WriteString(fmt.Sprintf(
` return [%s alloc];
}
`, dname))
}
//4. Go type
if !w.ProcessedTypes[gname] {
gotypes.WriteString(
types.NewTypeFromString(gname, supr).GoInterfaceDecl(Gogc))
//5. Go constructor
var finalizer string
dbg := ""
dbg2 := ""
if Debug {
dbg = fmt.Sprintf(`fmt.Printf("Setting finalizer (%s): %%p -> %%p\n", ret, ret.ptr)
`, gname)
dbg2 = fmt.Sprintf(`fmt.Printf("Finalizer (%s): release %%p -> %%p\n", o, o.ptr)
`, gname)
}
if Gogc {
w.goImports["runtime"] = true
if Debug { w.goImports["fmt"] = true }
finalizer = fmt.Sprintf(
`if ret.ptr == nil { return ret }
%sruntime.SetFinalizer(ret,func(o *%s) {
%so.Release()
})
`, dbg, gname, dbg2)
}
gocode.WriteString(fmt.Sprintf(`
func %sAlloc() *%s {
ret := &%s{}
ret.ptr = unsafe.Pointer(C.%sAlloc())
%sreturn ret
}
`, gname, gname, gname, dname, finalizer))
if Gogc {
if Debug {
dbg = fmt.Sprintf(`fmt.Printf("Setting finalizer (%s): %%p -> %%p\n", o, o.ptr)
`, gname)
}
gocode.WriteString(fmt.Sprintf(`
func (o *%s) GC() {
if o.ptr == nil { return }
%sruntime.SetFinalizer(o,func(o *%s) {
%so.Release()
})
}
`, gname, dbg, gname, dbg2))
}
}
w.ProcessedTypes[gname] = true
//6. Go dispatch database for callbacks
dispitems := make([]string, len(gnames))
sdispitems := make([]string, sms)
for i, n := range gnames {
if !sub || sms == 0 {
gtypes[i] = gtypes[i][1:]
}
dispitems[i] = fmt.Sprintf(
` %s func(%s)%s`, n, strings.Join(gtypes[i], ", "), grtypes[i])
if sub && i < sms && methods[i].Name != "dealloc" {
sdispitems[i] = fmt.Sprintf(
` %s func(%s)%s
`, n, strings.Join(gtypes[i][1:], ", "), grtypes[i])
}
}
gocode.WriteString(fmt.Sprintf(`
type %sDispatch struct {
%s
}
var %sLookup = map[unsafe.Pointer]%sDispatch{}
var %sMux sync.RWMutex
`, gname, strings.Join(dispitems, "\n"), gname, gname, gname))
w.goImports["sync"] = true
if sub && sms > 0 && havesupmethods {
gocode.WriteString(fmt.Sprintf(`
type %sSupermethods struct {
%s
}
`, gname, strings.Join(sdispitems, "")))
}
//To create (per method):
cprotos.WriteString("\n\n")
for i, m := range methods {
//1. ObjC function prototypes for go exports
_, _, ctps := w.cparamlist(m)
cprotos.WriteString(fmt.Sprintf(
`%s %s%s(%s);
`, crtypes[i], gname, gnames[i], ctps))
//2. Go callback registration functions
gocode.WriteString(fmt.Sprintf(`
func (d %s) %sCallback(f func(%s)%s) {
%sMux.Lock()
dispatch := %sLookup[d.Ptr()]
dispatch.%s = f
%sLookup[d.Ptr()] = dispatch
%sMux.Unlock()
}
`, gname, gnames[i], strings.Join(gtypes[i], ", "), grtypes[i], gname, gname, gnames[i], gname, gname))
//3. Go exported callback function wrappers
earglist := []string{"o unsafe.Pointer"}
garglist := []string{}
gargconv := []string{}
if sub && sms > 0 && m.Name != "dealloc" {
garglist = []string{"super"}
}
for j := 1; j < len(vnames[i]); j++ {
earglist = append(earglist, vnames[i][j]+" "+getypes[i][j])
var gt2 string
if sub {
gt2 = gtypes[i][j]
} else {
gt2 = gtypes[i][j-1]
}
if types.PtrIsGoInterface(gt2) {
gt2 = "*Id"
}
if gt2 == "*Id" || types.PtrShouldWrap(gt2) {
garglist = append(garglist, fmt.Sprintf(
`a%d`, j))
gargconv = append(gargconv, fmt.Sprintf(
` a%d := &%s{}; a%d.ptr = %s`, j, gt2[1:], j, vnames[i][j]))
} else {
garglist = append(garglist, fmt.Sprintf(
`(%s)(%s)`, gt2, vnames[i][j]))
}
}
retdecl := ""
retname := ""
retn := ""
retnparen := ""
crtype := ""
if cgtypes[i] != "C.void" {
retdecl = "var ret " + cgtypes[i] + "\n\t"
retname = " ret"
if cgtypes[i] == "unsafe.Pointer" {
retn = "return unsafe.Pointer("
crtype = " unsafe.Pointer"
if types.ShouldWrap(m.Type.GoType()) {
retnparen = ".Ptr()"
}
} else {
retn = "return (" + cgtypes[i] + ")("
crtype = " " + cgtypes[i]
}
retnparen = retnparen + ")"
}
sdispentries := make([]string, sms)
for i, _ := range sdispentries {
if methods[i].Name != "dealloc" {
sdispentries[i] = fmt.Sprintf(
` self.Super%s,
`, gnames[i])
}
}
sper := ""
if sub && sms > 0 && m.Name != "dealloc" {
sper = fmt.Sprintf(
` self := %s{}
self.ptr = o
super := %sSupermethods{
%s }
`, gname, gname, strings.Join(sdispentries, ""))
}
if len(gargconv) > 0 {
retn = "\n " + retn
} else {
retn = " " + retn
}
goexports.WriteString(fmt.Sprintf(`
//export %s%s
func %s%s(%s)%s {
%sMux.RLock()
%scb := %sLookup[o].%s
%sMux.RUnlock()
if cb == nil { return%s }
%s%s%scb(%s)%s
}
`, gname, gnames[i], gname, gnames[i], strings.Join(earglist, ", "), crtype, gname, retdecl, gname, gnames[i], gname, retname, sper, strings.Join(gargconv, "\n"), retn, strings.Join(garglist, ", "), retnparen))
//4. Go wrapper functions for superclass methods
if !sub || i >= sms {
continue
} // for subclasses only
if m.Name == "dealloc" {
continue
}
grtype := m.Type.GoType()
if grtype == "Void" {
grtype = ""
}
if types.IsGoInterface(grtype) {
grtype = "*Id"
}
if grtype == "BOOL" {
grtype = "bool"
}
if sub {
gocode.WriteString(fmt.Sprintf(`
func (o *%s) Super%s(%s) %s {
`, gname, gnames[i], strings.Join(earglist[1:], ", "), grtype))
ns, _, snames, tps, _ := w.gpntp(m)
lparm := len(tps) - 1
if len(tps) > 0 && tps[lparm].Variadic {
vn := ns[lparm]
vn = vn[:len(vn)-1]
ns[lparm] = vn
gocode.WriteString(fmt.Sprintf(
` var %s [%d]unsafe.Pointer
for i,o := range %ss {
%s[i] = o.Ptr()
}
`, vn, w.Vaargs, vn, vn))
}
gocode.WriteString("\t" + types.GoToC(m.Name, dname+"_super_"+m.Name, ns, snames, m.Type, tps, false, m.ShouldFinalize(), m.ClassMethod) + "\n}\n")
}
}
w.cCode.WriteString(cprotos.String())
w.cCode.WriteString(ccode.String())
w.goTypes.WriteString(gotypes.String())
w.goCode.WriteString(gocode.String())
w.goExports.WriteString(goexports.String())
// add methods from parent class that we are not overriding
for _,m := range supmeths {
w.ProcessMethodForClass(m,dname)
}
}
//Add class and instance methods from super class
func (w *Wrapper) AddSupermethods(i *Interface) {
var supmethods func(string)
procsups := func(mc, smc *MethodCollection) {
for _, m := range smc.Methods {
m2 := &Method{
Name: m.Name,
GoName: m.GoName,
Class: mc.Class,
GoClass: mc.GoClass,
Type: m.Type.CloneToClass(i.Name),
ClassMethod: m.ClassMethod,
Parameters: []*Parameter{},
Unavailable: m.Unavailable,
}
for _, p := range m.Parameters {
p2 := &Parameter{
Pname: p.Pname,
Vname: p.Vname,
Type: p.Type.CloneToClass(i.Name),
}
m2.Parameters = append(m2.Parameters, p2)
}
mc.Methods = append(mc.Methods, m2)
}
}
supmethods = func(s string) {
if sup, ok := w.Interfaces[s]; !ok {
return
} else {
procsups(i.ClassMethods, sup.ClassMethods)
procsups(i.InstanceMethods, sup.InstanceMethods)
// depth last
supmethods(types.Super(s))
}
}
supmethods(types.Super(i.Name))
}
//Add methods from a Protocol
func (w *Wrapper) AddProtocolMethods(i *Interface, p *Protocol) {
procmeths := func(mc, pmc *MethodCollection) {
for _, m := range pmc.Methods {
m2 := &Method{
Name: m.Name,
GoName: m.GoName,
Class: i.Name,
GoClass: i.GoName,
Type: m.Type.CloneToClass(i.Name),
ClassMethod: m.ClassMethod,
Parameters: []*Parameter{},
Unavailable: m.Unavailable,
}
for _, p := range m.Parameters {
p2 := &Parameter{
Pname: p.Pname,
Vname: p.Vname,
Type: p.Type.CloneToClass(i.Name),
}
m2.Parameters = append(m2.Parameters,p2)
}
mc.Methods = append(mc.Methods, m2)
}
}
procmeths(i.ClassMethods, p.ClassMethods)
procmeths(i.InstanceMethods, p.InstanceMethods)
}
func printDebug() {
fmt.Printf("ShouldWrap(NSString) = %t\n",types.ShouldWrap("NSString"))
fmt.Printf("ShouldWrap(*NSString) = %t\n",types.ShouldWrap("*NSString"))
fmt.Printf("IsGoInterface(NSObject) = %t\n",types.IsGoInterface("NSObject"))
fmt.Printf("IsGoInterface(*NSObject) = %t\n",types.IsGoInterface("*NSObject"))
fmt.Printf("IsGoInterface(NSString) = %t\n",types.IsGoInterface("NSString"))
fmt.Printf("IsGoInterface(*NSString) = %t\n",types.IsGoInterface("*NSString"))
fmt.Printf("PtrShouldWrap(NSString) = %t\n",types.PtrShouldWrap("NSString"))
fmt.Printf("PtrShouldWrap(*NSString) = %t\n",types.PtrShouldWrap("*NSString"))
fmt.Printf("PtrIsGoInterface(NSObject) = %t\n",types.PtrIsGoInterface("NSObject"))
fmt.Printf("PtrIsGoInterface(*NSObject) = %t\n",types.PtrIsGoInterface("*NSObject"))
fmt.Printf("PtrIsGoInterface(NSString) = %t\n",types.PtrIsGoInterface("NSString"))
fmt.Printf("PtrIsGoInterface(*NSString) = %t\n",types.PtrIsGoInterface("*NSString"))
fmt.Printf("Super(NSString) = %s\n",types.Super("NSString"))
fmt.Printf("Super(*NSString) = %s\n",types.Super("*NSString"))
fmt.Printf("Super(NSObject) = %s\n",types.Super("NSObject"))
fmt.Printf("Super(*NSObject) = %s\n",types.Super("*NSObject"))
fmt.Printf("Super(NSString*) = %s\n",types.Super("NSString*"))
fmt.Printf("Super(NSObject*) = %s\n",types.Super("NSObject*"))
}
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)
}
ef, err := os.Create(path.Join(w.Package, "exports.go"))
if err != nil {
fmt.Printf("Error opening file %s\n%s\n", path.Join(w.Package, "exports.go"), err)
os.Exit(-1)
}
fmt.Printf("Writing output to %s\n", path.Join(w.Package, "main.go"))
pInterfaces := map[string]*Interface{}
//Note: the following code eliminates duplicates, so it is acceptable
//to have duplicate interfaces in 'toproc'
for _, iface := range toproc {
pInterfaces[iface] = w.Interfaces[iface]
}
//FIXME: sort pInterfaces
for _, i := range pInterfaces {
if i == nil {
continue
}
w.processType(types.NewTypeFromString(i.GoName, ""))
if i.Name == "NSString" {
w.StringHelpers()
}
if i.Name == "NSEnumerator" {
w.EnumeratorHelpers()
}
w.AddSupermethods(i)
//FIXME: sort properties
for _, p := range i.Properties {
//Properties are not supported, use getter/setter
//methods instead.
if Debug {
fmt.Printf(" property: %s (%s)\n", p.Name, p.Type.CType())
}
}
// add methods for Protocols that this interface implements
for _, p := range i.Protocols {
prot, ok := w.Protocols[p]
if !ok {
fmt.Printf("Failed to find protocol %s for interface %s\n", p, i.Name)
os.Exit(-1)
}
w.AddProtocolMethods(i,prot)
}
Disambiguate(i.ClassMethods)
Disambiguate(i.InstanceMethods)
fmt.Printf("Interface %s: %d properties, %d class methods, %d instance methods\n", i.Name, len(i.Properties), len(i.ClassMethods.Methods), len(i.InstanceMethods.Methods))
for _, m := range i.ClassMethods.Methods {
w.ProcessMethod(m)
}
for _, m := range i.InstanceMethods.Methods {
w.ProcessMethod(m)
}
}
for _, m := range w.Functions {
if Debug {
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)
}
for n, p := range w.Delegates {
w.ProcessDelegate(n, p)
}
for n, s := range w.Subclasses {
w.ProcessSubclass(n, s)
}
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")
arc := " -fno-objc-arc"
if Arc {
arc = " -fobjc-arc"
}
w.cgoFlags.WriteString(fmt.Sprintf(`
/*
#cgo CFLAGS: -x objective-c%s`, arc))
ldflags := ""
if w.Frameworks != nil && len(w.Frameworks) > 0 {
for _, s := range w.Frameworks {
w.cImports.WriteString(fmt.Sprintf("#import <%s/%s.h>\n", s, s))
}
ldflags = "-framework " + strings.Join(w.Frameworks, " -framework ")
}
if w.Frameworkdirs != nil && len(w.Frameworkdirs) > 0 {
s := strings.Join(w.Frameworkdirs, " -F")
w.cgoFlags.WriteString(" -F" + s)
ldflags = ldflags + " -F" + s
}
if ldflags != "" {
w.cgoFlags.WriteString(`
#cgo LDFLAGS: ` + ldflags)
}
for _, s := range w.Pragmas {
w.cgoFlags.WriteString("\n#pragma " + s + "\n")
}
of.WriteString(w.cgoFlags.String() + "\n")
of.WriteString(w.cImports.String() + "\n")
of.WriteString(w.cCode.String())
imports := []string{}
for k := range w.goImports {
imports = append(imports,"\t\"" + k + "\"")
}
startThread := ""
goInit := ""
if w.Interfaces["NSThread"] != nil {
startThread = `
void
NSWrap_init() {
[[NSThread new] start]; // put the runtime into multi-threaded mode
}
`
goInit = `
func init() {
C.NSWrap_init()
}
`
}
of.WriteString(fmt.Sprintf(`
%s
*/
import "C"
import (
%s
)
%s
`,startThread,strings.Join(imports,"\n"),goInit))
of.WriteString(w.goTypes.String())
of.WriteString(w.goConst.String())
of.WriteString(w.goHelpers.String())
of.WriteString(w.goCode.String())
of.Close()
if len(w.Delegates) > 0 || len(w.Subclasses) > 0 {
ef.WriteString("package " + w.Package + "\n\n")
ef.WriteString(w.cgoFlags.String() + "\n")
ef.WriteString(w.cImports.String() + "\n")
ef.WriteString(`
*/
import "C"
import (
"unsafe"
)
`)
ef.WriteString(w.goExports.String())
ef.Close()
} else {
ef.Close()
err := os.Remove(path.Join(w.Package, "exports.go"))
if err != nil {
fmt.Printf("Error removing 'exports.go'. %s\n", err)
os.Exit(-1)
}
}
}