2
0
mirror of synced 2025-02-23 23:08:14 +00:00
mobile/bind/genobjcw.go
Elias Naur 6ecf8eedb8 bind,cmd: add generator for ObjC API wrappers
Using the new ObjC type analyzer API, scan the bound packages for
references to ObjC classes and protocols and generate Go wrappers for them.

This is the second part of the implementation of proposal golang/go#17102.

For golang/go#17102

Change-Id: I773db7b0362a7ff526d0a0fd6da5b2fa33301144
Reviewed-on: https://go-review.googlesource.com/29174
Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-16 10:25:40 +00:00

736 lines
18 KiB
Go

// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package bind
import (
"path/filepath"
"strings"
"golang.org/x/mobile/internal/importers/objc"
)
type (
// ObjCWrapper generates Go and C stubs for ObjC interfaces and protocols.
ObjcWrapper struct {
*Printer
imported map[string]*objc.Named
// The list of ObjC types.
types []*objc.Named
// The list of Go package paths with ObjC wrappers.
pkgNames []string
modules []string
// For each ObjC module, the list of ObjC types within.
modMap map[string][]*objc.Named
// For each module/name Go package path, the ObjC type
// with static functions or constants.
typePkgs map[string]*objc.Named
}
)
func (g *ObjcWrapper) Init(types []*objc.Named) {
g.types = types
g.imported = make(map[string]*objc.Named)
g.modMap = make(map[string][]*objc.Named)
g.typePkgs = make(map[string]*objc.Named)
pkgSet := make(map[string]struct{})
for _, n := range types {
g.imported[n.GoName] = n
typePkg := n.Module + "/" + n.GoName
g.typePkgs[typePkg] = n
if _, exists := g.modMap[n.Module]; !exists {
g.modules = append(g.modules, n.Module)
}
g.modMap[n.Module] = append(g.modMap[n.Module], n)
if _, exists := pkgSet[n.Module]; !exists {
pkgSet[n.Module] = struct{}{}
g.pkgNames = append(g.pkgNames, n.Module)
}
g.pkgNames = append(g.pkgNames, typePkg)
}
}
func (g *ObjcWrapper) GenM() {
g.Printf("//File is generated by gobind. Do not edit.\n\n")
// For objc_msgSend* functions.
g.Printf("@import ObjectiveC.message;\n")
g.Printf("#include \"seq.h\"\n")
g.Printf("#include \"interfaces.h\"\n\n")
for _, n := range g.types {
g.genM(n)
}
g.Printf("\n")
for _, n := range g.types {
for _, f := range n.AllMethods {
if !g.isFuncSupported(f) {
continue
}
g.genCFuncDecl("cproxy", n.GoName, f)
g.genCFuncBody(n, f, false)
if !n.Protocol {
g.genCFuncDecl("csuper", n.GoName, f)
g.genCFuncBody(n, f, true)
}
}
}
}
func (g *ObjcWrapper) genCFuncBody(n *objc.Named, f *objc.Func, super bool) {
g.Printf(" {\n")
g.Indent()
if !f.Static {
g.Printf("%s _this = go_seq_from_refnum(this).obj;\n", n.ObjcType())
}
var errParam *objc.Param
for i, a := range f.Params {
if i == len(f.Params)-1 && g.isErrorType(a.Type) {
errParam = a
break
}
g.genCToObjC(a.Name, a.Type, modeTransient)
}
if errParam != nil {
g.Printf("NSError *%s = nil;\n", errParam.Name)
}
if f.Constructor {
g.Printf("%s _this = [%s alloc];\n", n.ObjcType(), n.Name)
}
if super {
g.Printf("struct objc_super _super = {\n")
g.Printf(" .receiver = _this,\n")
g.Printf(" .super_class = [%s class],\n", n.Name)
g.Printf("};\n")
}
retType := "void"
if f.Ret != nil {
retType = g.objcType(f.Ret)
g.Printf("%s res = ", retType)
}
// There is no direct way to send a message to a class' super
// class from outside the class itself. Use objc_msgSendSuper instead
// which is what the compiler uses itself. To keep us honest and to exercise
// the code paths more use objc_msgSend for regular calls as well.
//
// A regular call looks like this:
//
// res = ((<return type> (*)(id, SEL, <argument_types>))objc_msgSend)(_this, @selector(...), <arguments>)
//
// a call to super looks like this:
//
// ret = ((<return type> (*)(id, SEL, <argument_types>))objc_msgSendSuper)(<struct objc_super>, <arguments>)
if f.Ret != nil {
switch f.Ret.Kind {
case objc.String, objc.Bool, objc.Data, objc.Int, objc.Uint, objc.Short, objc.Ushort, objc.Char, objc.Float, objc.Double, objc.Class, objc.Protocol:
default:
// If support struct is added, objc_msgSend_stret must be used
panic("unsupported type kind - use objc_msgSend_stret?")
}
}
g.Printf("((%s (*)(", retType)
if super {
g.Printf("struct objc_super *")
} else {
g.Printf("id")
}
g.Printf(", SEL")
for _, a := range f.Params {
g.Printf(", %s", g.objcType(a.Type))
}
g.Printf("))")
if super {
g.Printf("objc_msgSendSuper")
} else {
g.Printf("objc_msgSend")
}
g.Printf(")(")
if f.Static && !f.Constructor {
g.Printf("[%s class]", n.Name)
} else {
if super {
g.Printf("&_super")
} else {
g.Printf("_this")
}
}
g.Printf(", @selector(%s)", f.Sig)
for _, a := range f.Params {
arg := "_" + a.Name
if a == errParam {
arg = "&" + a.Name
}
g.Printf(", %s", arg)
}
g.Printf(");\n")
if errParam != nil {
g.Printf("id<GoUniverseerror> _%s = nil;\n", errParam.Name)
if f.Ret != nil {
g.Printf("if (!res && %s != nil) {\n", errParam.Name)
} else {
g.Printf("if (%s != nil) {\n", errParam.Name)
}
g.Printf(" _%[1]s = %[1]s;\n", errParam.Name)
g.Printf("}\n")
g.genObjCToC("_"+errParam.Name, g.errType(), modeRetained)
}
ret := f.Ret
if ret != nil && ret.Kind == objc.Bool && errParam != nil {
ret = nil
}
if ret != nil {
g.genObjCToC("res", ret, modeRetained)
}
switch {
case ret != nil && errParam != nil:
stype := strings.Replace(g.cType(ret), " ", "_", -1)
g.Printf("ret_%s _sres = {_res, __%s};\n", stype, errParam.Name)
g.Printf("return _sres;\n")
case ret != nil:
g.Printf("return _res;\n")
case errParam != nil:
g.Printf("return __%s;\n", errParam.Name)
}
g.Outdent()
g.Printf("}\n\n")
}
func (_ *ObjcWrapper) errType() *objc.Type {
return &objc.Type{Kind: objc.Class, Name: "NSError"}
}
func (g *ObjcWrapper) genM(n *objc.Named) {
for _, f := range n.Funcs {
if !g.isFuncSupported(f) {
continue
}
g.genCFuncDecl("cproxy", n.GoName, f)
g.genCFuncBody(n, f, false)
}
}
func (g *ObjcWrapper) GenH() {
g.Printf("//File is generated by gobind. Do not edit.\n\n")
g.Printf("#include \"seq.h\"\n\n")
for _, m := range g.modules {
g.Printf("@import %s;\n", m)
}
for _, tn := range []string{"int", "nstring", "nbyteslice", "long", "unsigned long", "short", "unsigned short", "bool", "char", "unsigned char", "float", "double"} {
sn := strings.Replace(tn, " ", "_", -1)
g.Printf("typedef struct ret_%s {\n", sn)
g.Printf(" %s res;\n", tn)
g.Printf(" int err;\n")
g.Printf("} ret_%s;\n", sn)
}
g.Printf("\n")
for _, n := range g.types {
for _, f := range n.AllMethods {
if !g.isFuncSupported(f) {
continue
}
g.Printf("extern ")
g.genCFuncDecl("cproxy", n.GoName, f)
g.Printf(";\n")
if !n.Protocol {
g.Printf("extern ")
g.genCFuncDecl("csuper", n.GoName, f)
g.Printf(";\n")
}
}
}
for _, cls := range g.types {
g.genH(cls)
}
}
func (g *ObjcWrapper) genH(n *objc.Named) {
for _, f := range n.Funcs {
if !g.isFuncSupported(f) {
continue
}
g.Printf("extern ")
g.genCFuncDecl("cproxy", n.GoName, f)
g.Printf(";\n")
}
}
func (g *ObjcWrapper) genCFuncDecl(prefix, name string, f *objc.Func) {
returnsErr := len(f.Params) > 0 && g.isErrorType(f.Params[len(f.Params)-1].Type)
ret := f.Ret
if ret != nil && returnsErr && ret.Kind == objc.Bool {
ret = nil
}
switch {
case ret != nil && returnsErr:
g.Printf("ret_%s", strings.Replace(g.cType(ret), " ", "_", -1))
case ret != nil:
g.Printf(g.cType(ret))
case returnsErr:
g.Printf("int")
default:
g.Printf("void")
}
g.Printf(" ")
g.Printf(prefix)
if f.Static {
g.Printf("_s")
}
g.Printf("_%s_%s(", name, f.GoName)
if !f.Static {
g.Printf("int this")
}
for i, p := range f.Params {
if i == len(f.Params)-1 && returnsErr {
break
}
if !f.Static || i > 0 {
g.Printf(", ")
}
g.Printf("%s %s", g.cType(p.Type), p.Name)
}
g.Printf(")")
}
func (g *ObjcWrapper) GenGo() {
g.Printf("// File is generated by gobind. Do not edit.\n\n")
g.Printf("package gomobile_bind\n\n")
g.Printf("// #cgo CFLAGS: -fobjc-arc -fmodules -fblocks\n")
g.Printf("// #include \"interfaces.h\"\n")
g.Printf("import \"C\"\n\n")
g.Printf("import \"ObjC\"\n")
g.Printf("import _seq \"golang.org/x/mobile/bind/seq\"\n")
for _, n := range g.types {
for _, f := range n.Funcs {
if g.isFuncSupported(f) {
pkgName := n.Module + "/" + n.GoName
g.Printf("import %q\n", "ObjC/"+pkgName)
break
}
}
}
g.Printf("\n")
g.Printf("type proxy interface { Bind_proxy_refnum__() int32 }\n\n")
g.Printf("// Suppress unused package error\n\n")
g.Printf("var _ = _seq.FromRefNum\n")
g.Printf("const _ = ObjC.Dummy\n\n")
for _, n := range g.types {
g.genGo(n)
}
}
func (g *ObjcWrapper) genGo(n *objc.Named) {
g.Printf("func init() {\n")
g.Indent()
for _, f := range n.Funcs {
if !g.isFuncSupported(f) {
continue
}
g.Printf("%s.%s = func", n.GoName, f.GoName)
g.genFuncDecl(false, f)
g.genFuncBody(n, f, "cproxy")
}
g.Outdent()
g.Printf("}\n\n")
g.Printf("type proxy_class_%s _seq.Ref\n\n", n.GoName)
g.Printf("func (p *proxy_class_%s) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() }\n\n", n.GoName)
for _, f := range n.AllMethods {
if !g.isFuncSupported(f) {
continue
}
g.Printf("func (p *proxy_class_%s) %s", n.GoName, f.GoName)
g.genFuncDecl(false, f)
g.genFuncBody(n, f, "cproxy")
}
if !n.Protocol {
g.Printf("func (p *proxy_class_%s) Super() ObjC.%s {\n", n.GoName, n.Module+"_"+n.GoName)
g.Printf(" return &super_%s{p}\n", n.GoName)
g.Printf("}\n\n")
g.Printf("type super_%s struct {*proxy_class_%[1]s}\n\n", n.GoName)
for _, f := range n.AllMethods {
if !g.isFuncSupported(f) {
continue
}
g.Printf("func (p *super_%s) %s", n.GoName, f.GoName)
g.genFuncDecl(false, f)
g.genFuncBody(n, f, "csuper")
}
}
}
func (g *ObjcWrapper) genFuncBody(n *objc.Named, f *objc.Func, prefix string) {
g.Printf(" {\n")
g.Indent()
var errParam *objc.Param
for i, a := range f.Params {
if i == len(f.Params)-1 && g.isErrorType(a.Type) {
errParam = a
break
}
g.genWrite(a)
}
ret := f.Ret
if ret != nil && errParam != nil && ret.Kind == objc.Bool {
ret = nil
}
if ret != nil || errParam != nil {
g.Printf("res := ")
}
g.Printf("C.")
g.Printf(prefix)
if f.Static {
g.Printf("_s")
}
g.Printf("_%s_%s(", n.GoName, f.GoName)
if !f.Static {
g.Printf("C.int(p.Bind_proxy_refnum__())")
}
for i, a := range f.Params {
if a == errParam {
break
}
if !f.Static || i > 0 {
g.Printf(", ")
}
g.Printf("_%s", a.Name)
}
g.Printf(")\n")
switch {
case ret != nil && errParam != nil:
g.genRead("_res", "res.res", ret)
g.genRefRead("_"+errParam.Name, "res.err", "error", "proxy_error")
g.Printf("return _res, _%s\n", errParam.Name)
case ret != nil:
g.genRead("_res", "res", ret)
g.Printf("return _res\n")
case errParam != nil:
g.genRefRead("_"+errParam.Name, "res", "error", "proxy_error")
g.Printf("return _%s\n", errParam.Name)
}
g.Outdent()
g.Printf("}\n\n")
}
func (g *ObjcWrapper) genCToObjC(name string, t *objc.Type, mode varMode) {
switch t.Kind {
case objc.String:
g.Printf("NSString *_%s = go_seq_to_objc_string(%s);\n", name, name)
case objc.Bool:
g.Printf("BOOL _%s = %s ? YES : NO;\n", name, name)
case objc.Data:
g.Printf("NSData *_%s = go_seq_to_objc_bytearray(%s, %d);\n", name, name, toCFlag(mode == modeRetained))
case objc.Int, objc.Uint, objc.Short, objc.Ushort, objc.Char, objc.Float, objc.Double:
g.Printf("%s _%s = (%s)%s;\n", g.objcType(t), name, g.objcType(t), name)
case objc.Class, objc.Protocol:
g.Printf("GoSeqRef* %s_ref = go_seq_from_refnum(%s);\n", name, name)
g.Printf("%s _%s;\n", g.objcType(t), name)
g.Printf("if (%s_ref != NULL) {\n", name)
g.Printf(" _%s = %s_ref.obj;\n", name, name)
g.Printf("}\n")
default:
panic("invalid kind")
}
}
func (g *ObjcWrapper) genObjCToC(name string, t *objc.Type, mode varMode) {
switch t.Kind {
case objc.String:
g.Printf("nstring _%s = go_seq_from_objc_string(%s);\n", name, name)
case objc.Data:
g.Printf("nbyteslice _%s = go_seq_from_objc_bytearray(%s, %d);\n", name, name, toCFlag(mode == modeRetained))
case objc.Bool, objc.Int, objc.Uint, objc.Short, objc.Ushort, objc.Char, objc.Float, objc.Double:
g.Printf("%s _%s = (%s)%s;\n", g.cType(t), name, g.cType(t), name)
case objc.Protocol, objc.Class:
g.Printf("int _%s = go_seq_to_refnum(%s);\n", name, name)
default:
panic("invalid kind")
}
}
func (g *ObjcWrapper) genWrite(a *objc.Param) {
switch a.Type.Kind {
case objc.String:
g.Printf("_%s := encodeString(%s)\n", a.Name, a.Name)
case objc.Data:
g.Printf("_%s := fromSlice(%s, 0)\n", a.Name, a.Name)
case objc.Bool:
g.Printf("_%s := %s(0)\n", a.Name, g.cgoType(a.Type))
g.Printf("if %s {\n", a.Name)
g.Printf(" _%s = %s(1)\n", a.Name, g.cgoType(a.Type))
g.Printf("}\n")
case objc.Int, objc.Uint, objc.Short, objc.Ushort, objc.Char, objc.Float, objc.Double:
g.Printf("_%s := %s(%s)\n", a.Name, g.cgoType(a.Type), a.Name)
case objc.Protocol, objc.Class:
g.Printf("var _%s %s = _seq.NullRefNum\n", a.Name, g.cgoType(a.Type))
g.Printf("if %s != nil {\n", a.Name)
g.Printf(" _%s = %s(_seq.ToRefNum(%s))\n", a.Name, g.cgoType(a.Type), a.Name)
g.Printf("}\n")
default:
panic("invalid kind")
}
}
func (g *ObjcWrapper) genRead(to, from string, t *objc.Type) {
switch t.Kind {
case objc.Int, objc.Uint, objc.Short, objc.Ushort, objc.Char, objc.Float, objc.Double:
g.Printf("%s := %s(%s)\n", to, g.goType(t, false), from)
case objc.Bool:
g.Printf("%s := %s != 0\n", to, from)
case objc.String:
g.Printf("%s := decodeString(%s)\n", to, from)
case objc.Data:
g.Printf("%s := toSlice(%s, 1)\n", to, from)
case objc.Protocol, objc.Class:
var proxyName string
if n := g.lookupImported(t); n != nil {
proxyName = "proxy_class_" + n.GoName
}
g.genRefRead(to, from, g.goType(t, false), proxyName)
default:
panic("invalid kind")
}
}
func (g *ObjcWrapper) genRefRead(to, from string, intfName, proxyName string) {
g.Printf("var %s %s\n", to, intfName)
g.Printf("%s_ref := _seq.FromRefNum(int32(%s))\n", to, from)
g.Printf("if %s_ref != nil {\n", to)
g.Printf(" if %s < 0 { // go object\n", from)
g.Printf(" %s = %s_ref.Get().(%s)\n", to, to, intfName)
if proxyName != "" {
g.Printf(" } else { // foreign object\n")
g.Printf(" %s = (*%s)(%s_ref)\n", to, proxyName, to)
}
g.Printf(" }\n")
g.Printf("}\n")
}
// Packages return the list of Go packages to be generated.
func (g *ObjcWrapper) Packages() []string {
return g.pkgNames
}
func (g *ObjcWrapper) GenPackage(idx int) {
pkg := g.pkgNames[idx]
g.Printf("//File is generated by gobind. Do not edit.\n\n")
g.Printf("package %s\n\n", filepath.Base(pkg))
g.Printf("import \"ObjC\"\n\n")
g.Printf("const _ = ObjC.Dummy\n\n")
for _, n := range g.modMap[pkg] {
g.Printf("type %s ObjC.%s\n", n.GoName, n.Module+"_"+n.GoName)
}
if n, ok := g.typePkgs[pkg]; ok {
g.Printf("var (\n")
g.Indent()
// Functions
for _, f := range n.Funcs {
if !g.isFuncSupported(f) {
continue
}
g.Printf("%s func", f.GoName)
g.genFuncDecl(false, f)
g.Printf("\n")
}
g.Outdent()
g.Printf(")\n\n")
}
}
func (g *ObjcWrapper) GenInterfaces() {
g.Printf("//File is generated by gobind. Do not edit.\n\n")
g.Printf("package ObjC\n\n")
g.Printf("// Used to silence this package not used errors\n")
g.Printf("const Dummy = 0\n\n")
for _, n := range g.types {
g.genInterface(n)
}
}
func (g *ObjcWrapper) genInterface(n *objc.Named) {
g.Printf("type %s interface {\n", n.Module+"_"+n.GoName)
g.Indent()
// Methods
for _, f := range n.AllMethods {
if !g.isFuncSupported(f) {
continue
}
g.Printf(f.GoName)
g.genFuncDecl(true, f)
g.Printf("\n")
}
if !n.Protocol {
g.Printf("Super() %s\n", n.Module+"_"+n.GoName)
}
g.Outdent()
g.Printf("}\n\n")
}
func (g *ObjcWrapper) genFuncDecl(local bool, f *objc.Func) {
var returnsErr bool
g.Printf("(")
for i, p := range f.Params {
if i == len(f.Params)-1 && g.isErrorType(p.Type) {
returnsErr = true
break
}
if i > 0 {
g.Printf(", ")
}
g.Printf("%s %s", p.Name, g.goType(p.Type, local))
}
g.Printf(")")
if f.Ret != nil || returnsErr {
ret := f.Ret
if ret.Kind == objc.Bool && returnsErr {
// Skip the bool result and use the error results.
ret = nil
}
if ret != nil {
g.Printf(" (%s", g.goType(f.Ret, local))
if returnsErr {
g.Printf(", error")
}
g.Printf(")")
} else {
g.Printf(" error")
}
}
}
func (g *ObjcWrapper) isFuncSupported(f *objc.Func) bool {
for i, p := range f.Params {
if !g.isSupported(p.Type) {
if i < len(f.Params)-1 || !g.isErrorType(p.Type) {
return false
}
}
}
if f.Ret != nil {
return g.isSupported(f.Ret)
}
return true
}
func (g *ObjcWrapper) isErrorType(t *objc.Type) bool {
// Must be a NSError ** type
return t.Kind == objc.Class && t.Indirect && t.Name == "NSError"
}
func (g *ObjcWrapper) isSupported(t *objc.Type) bool {
if t.Indirect {
return false
}
switch t.Kind {
case objc.Unknown:
return false
case objc.Protocol:
// TODO: support inout parameters.
return !strings.HasSuffix(t.Decl, " *")
case objc.Class:
return t.Name != "SEL" && t.Name != "void"
default:
return true
}
}
func (g *ObjcWrapper) cgoType(t *objc.Type) string {
switch t.Kind {
case objc.Uint:
return "C.ulong"
case objc.Ushort:
return "C.ushort"
case objc.Uchar:
return "C.uchar"
default:
return "C." + g.cType(t)
}
}
func (g *ObjcWrapper) cType(t *objc.Type) string {
switch t.Kind {
case objc.Protocol, objc.Class:
return "int"
case objc.String:
return "nstring"
case objc.Data:
return "nbyteslice"
case objc.Int:
return "long"
case objc.Uint:
return "unsigned long"
case objc.Short:
return "short"
case objc.Ushort:
return "unsigned short"
case objc.Bool:
return "char"
case objc.Char:
return "char"
case objc.Uchar:
return "unsigned char"
case objc.Float:
return "float"
case objc.Double:
return "double"
default:
panic("invalid kind")
}
}
func (g *ObjcWrapper) objcType(t *objc.Type) string {
return t.Decl
}
func (g *ObjcWrapper) lookupImported(t *objc.Type) *objc.Named {
var mangled string
switch t.Kind {
case objc.Class:
mangled = t.Name + "C"
case objc.Protocol:
mangled = t.Name + "P"
default:
panic("invalid type kind")
}
if n, exists := g.imported[mangled]; exists {
return n
}
return g.imported[t.Name]
}
func (g *ObjcWrapper) goType(t *objc.Type, local bool) string {
switch t.Kind {
case objc.String:
return "string"
case objc.Data:
return "[]byte"
case objc.Int:
return "int"
case objc.Uint:
return "uint"
case objc.Short:
return "int16"
case objc.Ushort:
return "uint16"
case objc.Bool:
return "bool"
case objc.Char:
return "byte"
case objc.Float:
return "float32"
case objc.Double:
return "float64"
case objc.Protocol, objc.Class:
var n *objc.Named
n = g.lookupImported(t)
if n == nil {
return "interface{}"
}
name := n.Module + "_" + n.GoName
if !local {
name = "ObjC." + name
}
return name
default:
panic("invalid kind")
}
}