Replace test.bash with go test that builds and run the bind tests through the XCode testing framework. Running on the iOS emulator unmasked a bug where autorelease pools were not in place for Go calls into ObjC, leaking autoreleased objects. Fix that by adding autoreleasepool blocks to the tracker finalizer callback and to every generated ObjC proxy. Will not run on the emulator without CL 19206. Change-Id: I6a775f9995f3b8ea50272982069d033e41ddcb7b Reviewed-on: https://go-review.googlesource.com/20255 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
1018 lines
25 KiB
Go
1018 lines
25 KiB
Go
// Copyright 2015 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 (
|
|
"fmt"
|
|
"go/constant"
|
|
"go/types"
|
|
"math"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// TODO(hyangah): handle method name conflicts.
|
|
// - struct with SetF method and exported F field.
|
|
// - method names conflicting with NSObject methods. e.g. Init
|
|
// - interface type with InitWithRef.
|
|
|
|
// TODO(hyangah): error code/domain propagation
|
|
|
|
type objcGen struct {
|
|
prefix string // prefix arg passed by flag.
|
|
|
|
// fields set by init.
|
|
namePrefix string
|
|
|
|
*generator
|
|
}
|
|
|
|
type interfaceInfo struct {
|
|
obj *types.TypeName
|
|
t *types.Interface
|
|
summary ifaceSummary
|
|
}
|
|
|
|
type structInfo struct {
|
|
obj *types.TypeName
|
|
t *types.Struct
|
|
}
|
|
|
|
func (g *objcGen) init() {
|
|
g.generator.init()
|
|
g.namePrefix = g.prefix + strings.Title(g.pkgName)
|
|
}
|
|
|
|
func (g *objcGen) genGoH() error {
|
|
g.Printf(objcPreamble, g.pkg.Path(), g.gobindOpts(), g.pkg.Path())
|
|
g.Printf("#ifndef __%s_H__\n", g.pkgName)
|
|
g.Printf("#define __%s_H__\n\n", g.pkgName)
|
|
g.Printf("#include <stdint.h>\n")
|
|
g.Printf("#include <objc/objc.h>\n")
|
|
|
|
for _, i := range g.interfaces {
|
|
if !i.summary.implementable {
|
|
continue
|
|
}
|
|
for _, m := range i.summary.callable {
|
|
g.genInterfaceMethodSignature(m, i.obj.Name(), true)
|
|
g.Printf("\n")
|
|
}
|
|
}
|
|
|
|
g.Printf("#endif\n")
|
|
|
|
if len(g.err) > 0 {
|
|
return g.err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *objcGen) genH() error {
|
|
g.Printf(objcPreamble, g.pkg.Path(), g.gobindOpts(), g.pkg.Path())
|
|
g.Printf("#ifndef __%s%s_H__\n", g.prefix, strings.Title(g.pkgName))
|
|
g.Printf("#define __%s%s_H__\n", g.prefix, strings.Title(g.pkgName))
|
|
g.Printf("\n")
|
|
g.Printf("#include <Foundation/Foundation.h>\n")
|
|
g.Printf("\n")
|
|
|
|
// Forward declaration of @class and @protocol
|
|
for _, s := range g.structs {
|
|
g.Printf("@class %s%s;\n", g.namePrefix, s.obj.Name())
|
|
}
|
|
for _, i := range g.interfaces {
|
|
g.Printf("@protocol %s%s;\n", g.namePrefix, i.obj.Name())
|
|
if i.summary.implementable {
|
|
g.Printf("@class %s%s;\n", g.namePrefix, i.obj.Name())
|
|
// Forward declaration for other cases will be handled at the beginning of genM.
|
|
}
|
|
}
|
|
if len(g.structs) > 0 || len(g.interfaces) > 0 {
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// @interfaces
|
|
for _, s := range g.structs {
|
|
g.genStructH(s.obj, s.t)
|
|
g.Printf("\n")
|
|
}
|
|
for _, i := range g.interfaces {
|
|
g.genInterfaceH(i.obj, i.t)
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// const
|
|
// TODO: prefix with k?, or use a class method?
|
|
for _, obj := range g.constants {
|
|
switch b := obj.Type().(*types.Basic); b.Kind() {
|
|
case types.String, types.UntypedString:
|
|
g.Printf("FOUNDATION_EXPORT NSString* const %s%s;\n", g.namePrefix, obj.Name())
|
|
default:
|
|
g.Printf("FOUNDATION_EXPORT const %s %s%s;\n", g.objcType(obj.Type()), g.namePrefix, obj.Name())
|
|
}
|
|
}
|
|
if len(g.constants) > 0 {
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// var
|
|
if len(g.vars) > 0 {
|
|
g.Printf("@interface %s : NSObject\n", g.namePrefix)
|
|
for _, obj := range g.vars {
|
|
objcType := g.objcType(obj.Type())
|
|
g.Printf("+ (%s) %s;\n", objcType, lowerFirst(obj.Name()))
|
|
g.Printf("+ (void) set%s:(%s)v;\n", obj.Name(), objcType)
|
|
g.Printf("\n")
|
|
}
|
|
g.Printf("@end\n\n")
|
|
}
|
|
|
|
// static functions.
|
|
for _, obj := range g.funcs {
|
|
g.genFuncH(obj)
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// declare all named types first.
|
|
g.Printf("#endif\n")
|
|
|
|
if len(g.err) > 0 {
|
|
return g.err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *objcGen) gobindOpts() string {
|
|
opts := []string{"-lang=objc"}
|
|
if g.prefix != "Go" {
|
|
opts = append(opts, "-prefix="+g.prefix)
|
|
}
|
|
return strings.Join(opts, " ")
|
|
}
|
|
|
|
func (g *objcGen) genM() error {
|
|
g.Printf(objcPreamble, g.pkg.Path(), g.gobindOpts(), g.pkg.Path())
|
|
g.Printf("#include %q\n", g.namePrefix+".h")
|
|
g.Printf("#include <Foundation/Foundation.h>\n")
|
|
g.Printf("#include \"seq.h\"\n")
|
|
g.Printf("#include \"_cgo_export.h\"\n")
|
|
g.Printf("\n")
|
|
g.Printf("static NSString* errDomain = @\"go.%s\";\n", g.pkg.Path())
|
|
g.Printf("\n")
|
|
g.Printf(`@protocol goSeqRefInterface
|
|
-(GoSeqRef*) _ref;
|
|
@end
|
|
|
|
`)
|
|
|
|
// forward declarations skipped from genH
|
|
for _, i := range g.interfaces {
|
|
if i.summary.implementable {
|
|
g.Printf("@class %s%s;\n\n", g.namePrefix, i.obj.Name())
|
|
}
|
|
}
|
|
for _, i := range g.interfaces {
|
|
if i.summary.implementable {
|
|
// @interface Interface -- similar to what genStructH does.
|
|
g.genInterfaceInterface(i.obj, i.summary, true)
|
|
g.Printf("\n")
|
|
}
|
|
}
|
|
|
|
// struct
|
|
for _, s := range g.structs {
|
|
g.genStructM(s.obj, s.t)
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// interface
|
|
var needProxy []*types.TypeName
|
|
for _, i := range g.interfaces {
|
|
if g.genInterfaceM(i.obj, i.t) {
|
|
needProxy = append(needProxy, i.obj)
|
|
}
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// const
|
|
for _, o := range g.constants {
|
|
g.genConstM(o)
|
|
}
|
|
if len(g.constants) > 0 {
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// vars
|
|
if len(g.vars) > 0 {
|
|
g.Printf("@implementation %s\n", g.namePrefix)
|
|
for _, o := range g.vars {
|
|
g.genVarM(o)
|
|
}
|
|
g.Printf("@end\n\n")
|
|
}
|
|
|
|
g.Printf("\n")
|
|
|
|
for _, obj := range g.funcs {
|
|
g.genFuncM(obj)
|
|
g.Printf("\n")
|
|
}
|
|
|
|
for _, i := range g.interfaces {
|
|
for _, m := range i.summary.callable {
|
|
g.genInterfaceMethodProxy(i.obj, m)
|
|
}
|
|
}
|
|
|
|
g.Printf("__attribute__((constructor)) static void init() {\n")
|
|
g.Indent()
|
|
g.Printf("init_seq();\n")
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
|
|
if len(g.err) > 0 {
|
|
return g.err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *objcGen) genVarM(o *types.Var) {
|
|
objcType := g.objcType(o.Type())
|
|
|
|
// setter
|
|
g.Printf("+ (void) set%s:(%s)v {\n", o.Name(), objcType)
|
|
g.Indent()
|
|
g.genWrite("v", o.Type(), modeRetained)
|
|
g.Printf("var_set%s_%s(_v);\n", g.pkgPrefix, o.Name())
|
|
g.genRelease("v", o.Type(), modeRetained)
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
|
|
// getter
|
|
g.Printf("+ (%s) %s {\n", objcType, lowerFirst(o.Name()))
|
|
g.Indent()
|
|
g.Printf("%s r0 = ", g.cgoType(o.Type()))
|
|
g.Printf("var_get%s_%s();\n", g.pkgPrefix, o.Name())
|
|
g.genRead("_r0", "r0", o.Type(), modeRetained)
|
|
g.Printf("return _r0;\n")
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
|
|
func (g *objcGen) genConstM(o *types.Const) {
|
|
cName := fmt.Sprintf("%s%s", g.namePrefix, o.Name())
|
|
objcType := g.objcType(o.Type())
|
|
|
|
switch b := o.Type().(*types.Basic); b.Kind() {
|
|
case types.Bool, types.UntypedBool:
|
|
v := "NO"
|
|
if constant.BoolVal(o.Val()) {
|
|
v = "YES"
|
|
}
|
|
g.Printf("const BOOL %s = %s;\n", cName, v)
|
|
|
|
case types.String, types.UntypedString:
|
|
g.Printf("NSString* const %s = @%s;\n", cName, constExactString(o))
|
|
|
|
case types.Int, types.Int8, types.Int16, types.Int32:
|
|
g.Printf("const %s %s = %s;\n", objcType, cName, o.Val())
|
|
|
|
case types.Int64, types.UntypedInt:
|
|
i, exact := constant.Int64Val(o.Val())
|
|
if !exact {
|
|
g.errorf("const value %s for %s cannot be represented as %s", o.Val(), o.Name(), objcType)
|
|
return
|
|
}
|
|
if i == math.MinInt64 {
|
|
// -9223372036854775808LL does not work because 922337203685477508 is
|
|
// larger than max int64.
|
|
g.Printf("const int64_t %s = %dLL-1;\n", cName, i+1)
|
|
} else {
|
|
g.Printf("const int64_t %s = %dLL;\n", cName, i)
|
|
}
|
|
|
|
case types.Float32, types.Float64, types.UntypedFloat:
|
|
f, _ := constant.Float64Val(o.Val())
|
|
if math.IsInf(f, 0) || math.Abs(f) > math.MaxFloat64 {
|
|
g.errorf("const value %s for %s cannot be represented as double", o.Val(), o.Name())
|
|
return
|
|
}
|
|
g.Printf("const %s %s = %g;\n", objcType, cName, f)
|
|
|
|
default:
|
|
g.errorf("unsupported const type %s for %s", b, o.Name())
|
|
}
|
|
}
|
|
|
|
type funcSummary struct {
|
|
name string
|
|
ret string
|
|
sig *types.Signature
|
|
params, retParams []paramInfo
|
|
}
|
|
|
|
type paramInfo struct {
|
|
typ types.Type
|
|
name string
|
|
}
|
|
|
|
func (g *objcGen) funcSummary(obj *types.Func) *funcSummary {
|
|
sig := obj.Type().(*types.Signature)
|
|
s := &funcSummary{name: obj.Name(), sig: sig}
|
|
|
|
params := sig.Params()
|
|
for i := 0; i < params.Len(); i++ {
|
|
p := params.At(i)
|
|
v := paramInfo{
|
|
typ: p.Type(),
|
|
name: paramName(params, i),
|
|
}
|
|
s.params = append(s.params, v)
|
|
}
|
|
|
|
res := sig.Results()
|
|
switch res.Len() {
|
|
case 0:
|
|
s.ret = "void"
|
|
case 1:
|
|
p := res.At(0)
|
|
if isErrorType(p.Type()) {
|
|
s.retParams = append(s.retParams, paramInfo{
|
|
typ: p.Type(),
|
|
name: "error",
|
|
})
|
|
s.ret = "BOOL"
|
|
} else {
|
|
name := p.Name()
|
|
if name == "" || paramRE.MatchString(name) {
|
|
name = "ret0_"
|
|
}
|
|
typ := p.Type()
|
|
s.retParams = append(s.retParams, paramInfo{typ: typ, name: name})
|
|
s.ret = g.objcType(typ)
|
|
}
|
|
case 2:
|
|
name := res.At(0).Name()
|
|
if name == "" || paramRE.MatchString(name) {
|
|
name = "ret0_"
|
|
}
|
|
s.retParams = append(s.retParams, paramInfo{
|
|
typ: res.At(0).Type(),
|
|
name: name,
|
|
})
|
|
|
|
if !isErrorType(res.At(1).Type()) {
|
|
g.errorf("second result value must be of type error: %s", obj)
|
|
return nil
|
|
}
|
|
s.retParams = append(s.retParams, paramInfo{
|
|
typ: res.At(1).Type(),
|
|
name: "error", // TODO(hyangah): name collision check.
|
|
})
|
|
s.ret = "BOOL"
|
|
default:
|
|
// TODO(hyangah): relax the constraint on multiple return params.
|
|
g.errorf("too many result values: %s", obj)
|
|
return nil
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (s *funcSummary) asFunc(g *objcGen) string {
|
|
var params []string
|
|
for _, p := range s.params {
|
|
params = append(params, g.objcType(p.typ)+" "+p.name)
|
|
}
|
|
if !s.returnsVal() {
|
|
for _, p := range s.retParams {
|
|
params = append(params, g.objcType(p.typ)+"* "+p.name)
|
|
}
|
|
}
|
|
return fmt.Sprintf("%s %s%s(%s)", s.ret, g.namePrefix, s.name, strings.Join(params, ", "))
|
|
}
|
|
|
|
func (s *funcSummary) asMethod(g *objcGen) string {
|
|
var params []string
|
|
for i, p := range s.params {
|
|
var key string
|
|
if i != 0 {
|
|
key = p.name
|
|
}
|
|
params = append(params, fmt.Sprintf("%s:(%s)%s", key, g.objcType(p.typ), p.name))
|
|
}
|
|
if !s.returnsVal() {
|
|
for _, p := range s.retParams {
|
|
var key string
|
|
if len(params) > 0 {
|
|
key = p.name
|
|
}
|
|
params = append(params, fmt.Sprintf("%s:(%s)%s", key, g.objcType(p.typ)+"*", p.name))
|
|
}
|
|
}
|
|
return fmt.Sprintf("(%s)%s%s", s.ret, lowerFirst(s.name), strings.Join(params, " "))
|
|
}
|
|
|
|
func (s *funcSummary) callMethod(g *objcGen) string {
|
|
var params []string
|
|
for i, p := range s.params {
|
|
var key string
|
|
if i != 0 {
|
|
key = p.name
|
|
}
|
|
params = append(params, fmt.Sprintf("%s:_%s", key, p.name))
|
|
}
|
|
if !s.returnsVal() {
|
|
for _, p := range s.retParams {
|
|
var key string
|
|
if len(params) > 0 {
|
|
key = p.name
|
|
}
|
|
params = append(params, fmt.Sprintf("%s:&%s", key, p.name))
|
|
}
|
|
}
|
|
return fmt.Sprintf("%s%s", lowerFirst(s.name), strings.Join(params, " "))
|
|
}
|
|
|
|
func (s *funcSummary) returnsVal() bool {
|
|
return len(s.retParams) == 1 && !isErrorType(s.retParams[0].typ)
|
|
}
|
|
|
|
func (g *objcGen) genFuncH(obj *types.Func) {
|
|
if s := g.funcSummary(obj); s != nil {
|
|
g.Printf("FOUNDATION_EXPORT %s;\n", s.asFunc(g))
|
|
}
|
|
}
|
|
|
|
func (g *objcGen) genFuncM(obj *types.Func) {
|
|
s := g.funcSummary(obj)
|
|
if s == nil {
|
|
return
|
|
}
|
|
g.Printf("%s {\n", s.asFunc(g))
|
|
g.Indent()
|
|
g.genFunc(s, "")
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
}
|
|
|
|
func (g *objcGen) genGetter(oName string, f *types.Var) {
|
|
t := f.Type()
|
|
if isErrorType(t) {
|
|
t = types.Typ[types.String]
|
|
}
|
|
g.Printf("- (%s)%s {\n", g.objcType(t), lowerFirst(f.Name()))
|
|
g.Indent()
|
|
g.Printf("int32_t refnum = go_seq_go_to_refnum(self._ref);\n")
|
|
g.Printf("%s r0 = ", g.cgoType(f.Type()))
|
|
g.Printf("proxy%s_%s_%s_Get(refnum);\n", g.pkgPrefix, oName, f.Name())
|
|
g.genRead("_r0", "r0", f.Type(), modeRetained)
|
|
g.Printf("return _r0;\n")
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
|
|
func (g *objcGen) genSetter(oName string, f *types.Var) {
|
|
t := f.Type()
|
|
if isErrorType(t) {
|
|
t = types.Typ[types.String]
|
|
}
|
|
|
|
g.Printf("- (void)set%s:(%s)v {\n", f.Name(), g.objcType(t))
|
|
g.Indent()
|
|
g.Printf("int32_t refnum = go_seq_go_to_refnum(self._ref);\n")
|
|
g.genWrite("v", f.Type(), modeRetained)
|
|
g.Printf("proxy%s_%s_%s_Set(refnum, _v);\n", g.pkgPrefix, oName, f.Name())
|
|
g.genRelease("v", f.Type(), modeRetained)
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
|
|
func (g *objcGen) genWrite(varName string, t types.Type, mode varMode) {
|
|
if isErrorType(t) {
|
|
g.genWrite(varName, types.Typ[types.String], mode)
|
|
return
|
|
}
|
|
switch t := t.(type) {
|
|
case *types.Basic:
|
|
switch t.Kind() {
|
|
case types.String:
|
|
g.Printf("nstring _%s = go_seq_from_objc_string(%s);\n", varName, varName)
|
|
default:
|
|
g.Printf("%s _%s = (%s)%s;\n", g.cgoType(t), varName, g.cgoType(t), varName)
|
|
}
|
|
case *types.Slice:
|
|
switch e := t.Elem().(type) {
|
|
case *types.Basic:
|
|
switch e.Kind() {
|
|
case types.Uint8: // Byte.
|
|
g.Printf("nbyteslice _%s = go_seq_from_objc_bytearray(%s, %d);\n", varName, varName, g.toCFlag(mode == modeRetained))
|
|
default:
|
|
g.errorf("unsupported type: %s", t)
|
|
}
|
|
default:
|
|
g.errorf("unsupported type: %s", t)
|
|
}
|
|
case *types.Named:
|
|
switch u := t.Underlying().(type) {
|
|
case *types.Interface:
|
|
g.genRefWrite(varName, t)
|
|
default:
|
|
panic(fmt.Sprintf("unsupported named type: %s / %T", u, u))
|
|
}
|
|
case *types.Pointer:
|
|
g.genRefWrite(varName, t)
|
|
default:
|
|
g.Printf("%s _%s = (%s)%s;\n", g.cgoType(t), varName, g.cgoType(t), varName)
|
|
}
|
|
}
|
|
|
|
func (g *objcGen) genRefWrite(varName string, t types.Type) {
|
|
g.Printf("int32_t _%s;\n", varName)
|
|
g.Printf("if ([(id<NSObject>)(%s) isKindOfClass:[%s class]]) {\n", varName, g.refTypeBase(t))
|
|
g.Indent()
|
|
g.Printf("id<goSeqRefInterface> %[1]s_proxy = (id<goSeqRefInterface>)(%[1]s);\n", varName)
|
|
g.Printf("_%s = go_seq_go_to_refnum(%s_proxy._ref);\n", varName, varName)
|
|
g.Outdent()
|
|
g.Printf("} else {\n")
|
|
g.Indent()
|
|
g.Printf("_%s = go_seq_to_refnum(%s);\n", varName, varName)
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
}
|
|
|
|
func (g *objcGen) genRefRead(toName, fromName string, t types.Type) {
|
|
ptype := g.objcType(t)
|
|
g.Printf("%s %s = nil;\n", ptype, toName)
|
|
g.Printf("GoSeqRef* %s_ref = go_seq_from_refnum(%s);\n", toName, fromName)
|
|
g.Printf("if (%s_ref != NULL) {\n", toName)
|
|
g.Printf(" %s = %s_ref.obj;\n", toName, toName)
|
|
g.Printf(" if (%s == nil) {\n", toName)
|
|
g.Printf(" %s = [[%s alloc] initWithRef:%s_ref];\n", toName, g.refTypeBase(t), toName)
|
|
g.Printf(" }\n")
|
|
g.Printf("}\n")
|
|
}
|
|
|
|
func (g *objcGen) genRead(toName, fromName string, t types.Type, mode varMode) {
|
|
if isErrorType(t) {
|
|
g.genRead(toName, fromName, types.Typ[types.String], mode)
|
|
return
|
|
}
|
|
switch t := t.(type) {
|
|
case *types.Basic:
|
|
switch t.Kind() {
|
|
case types.String:
|
|
g.Printf("NSString *%s = go_seq_to_objc_string(%s);\n", toName, fromName)
|
|
case types.Bool:
|
|
g.Printf("BOOL %s = %s ? YES : NO;\n", toName, fromName)
|
|
default:
|
|
g.Printf("%s %s = (%s)%s;\n", g.objcType(t), toName, g.objcType(t), fromName)
|
|
}
|
|
case *types.Slice:
|
|
switch e := t.Elem().(type) {
|
|
case *types.Basic:
|
|
switch e.Kind() {
|
|
case types.Uint8: // Byte.
|
|
g.Printf("NSData *%s = go_seq_to_objc_bytearray(%s, %d);\n", toName, fromName, g.toCFlag(mode == modeRetained))
|
|
default:
|
|
g.errorf("unsupported type: %s", t)
|
|
}
|
|
default:
|
|
g.errorf("unsupported type: %s", t)
|
|
}
|
|
case *types.Pointer:
|
|
switch t := t.Elem().(type) {
|
|
case *types.Named:
|
|
g.genRefRead(toName, fromName, types.NewPointer(t))
|
|
default:
|
|
g.errorf("unsupported type %s", t)
|
|
}
|
|
case *types.Named:
|
|
switch t.Underlying().(type) {
|
|
case *types.Interface, *types.Pointer:
|
|
g.genRefRead(toName, fromName, t)
|
|
default:
|
|
g.errorf("unsupported, direct named type %s", t)
|
|
}
|
|
default:
|
|
g.Printf("%s %s = (%s)%s;\n", g.objcType(t), toName, g.objcType(t), fromName)
|
|
}
|
|
}
|
|
|
|
func (g *objcGen) genFunc(s *funcSummary, objName string) {
|
|
if objName != "" {
|
|
g.Printf("int32_t refnum = go_seq_go_to_refnum(self._ref);\n")
|
|
}
|
|
for _, p := range s.params {
|
|
g.genWrite(p.name, p.typ, modeTransient)
|
|
}
|
|
resPrefix := ""
|
|
if len(s.retParams) > 0 {
|
|
if len(s.retParams) == 1 {
|
|
g.Printf("%s r0 = ", g.cgoType(s.retParams[0].typ))
|
|
} else {
|
|
resPrefix = "res."
|
|
g.Printf("struct proxy%s_%s_%s_return res = ", g.pkgPrefix, objName, s.name)
|
|
}
|
|
}
|
|
g.Printf("proxy%s_%s_%s(", g.pkgPrefix, objName, s.name)
|
|
if objName != "" {
|
|
g.Printf("refnum")
|
|
}
|
|
for i, p := range s.params {
|
|
if i > 0 || objName != "" {
|
|
g.Printf(", ")
|
|
}
|
|
g.Printf("_%s", p.name)
|
|
}
|
|
g.Printf(");\n")
|
|
for _, p := range s.params {
|
|
g.genRelease(p.name, p.typ, modeTransient)
|
|
}
|
|
|
|
for i, r := range s.retParams {
|
|
g.genRead("_"+r.name, fmt.Sprintf("%sr%d", resPrefix, i), r.typ, modeRetained)
|
|
}
|
|
|
|
if !s.returnsVal() {
|
|
for _, p := range s.retParams {
|
|
if isErrorType(p.typ) {
|
|
g.Printf("if ([_%s length] != 0 && %s != nil) {\n", p.name, p.name)
|
|
g.Indent()
|
|
g.Printf("NSMutableDictionary* details = [NSMutableDictionary dictionary];\n")
|
|
g.Printf("[details setValue:_%s forKey:NSLocalizedDescriptionKey];\n", p.name)
|
|
g.Printf("*%s = [NSError errorWithDomain:errDomain code:1 userInfo:details];\n", p.name)
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
} else {
|
|
g.Printf("*%s = _%s;\n", p.name, p.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
if n := len(s.retParams); n > 0 {
|
|
p := s.retParams[n-1]
|
|
if isErrorType(p.typ) {
|
|
g.Printf("return ([_%s length] == 0);\n", p.name)
|
|
} else {
|
|
g.Printf("return _%s;\n", p.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *objcGen) genInterfaceInterface(obj *types.TypeName, summary ifaceSummary, isProtocol bool) {
|
|
g.Printf("@interface %[1]s%[2]s : NSObject", g.namePrefix, obj.Name())
|
|
if isProtocol {
|
|
g.Printf(" <%[1]s%[2]s>", g.namePrefix, obj.Name())
|
|
}
|
|
g.Printf(" {\n}\n")
|
|
g.Printf("@property(strong, readonly) id _ref;\n")
|
|
g.Printf("\n")
|
|
g.Printf("- (id)initWithRef:(id)ref;\n")
|
|
for _, m := range summary.callable {
|
|
s := g.funcSummary(m)
|
|
g.Printf("- %s;\n", s.asMethod(g))
|
|
}
|
|
g.Printf("@end\n")
|
|
}
|
|
|
|
func (g *objcGen) genInterfaceH(obj *types.TypeName, t *types.Interface) {
|
|
summary := makeIfaceSummary(t)
|
|
if !summary.implementable {
|
|
g.genInterfaceInterface(obj, summary, false)
|
|
return
|
|
}
|
|
g.Printf("@protocol %s%s\n", g.namePrefix, obj.Name())
|
|
for _, m := range makeIfaceSummary(t).callable {
|
|
s := g.funcSummary(m)
|
|
g.Printf("- %s;\n", s.asMethod(g))
|
|
}
|
|
g.Printf("@end\n")
|
|
}
|
|
|
|
func (g *objcGen) genInterfaceM(obj *types.TypeName, t *types.Interface) bool {
|
|
summary := makeIfaceSummary(t)
|
|
|
|
// @implementation Interface -- similar to what genStructM does.
|
|
g.Printf("@implementation %s%s {\n", g.namePrefix, obj.Name())
|
|
g.Printf("}\n")
|
|
g.Printf("\n")
|
|
g.Printf("- (id)initWithRef:(id)ref {\n")
|
|
g.Indent()
|
|
g.Printf("self = [super init];\n")
|
|
g.Printf("if (self) { __ref = ref; }\n")
|
|
g.Printf("return self;\n")
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
g.Printf("\n")
|
|
|
|
for _, m := range summary.callable {
|
|
s := g.funcSummary(m)
|
|
g.Printf("- %s {\n", s.asMethod(g))
|
|
g.Indent()
|
|
g.genFunc(s, obj.Name())
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
g.Printf("@end\n")
|
|
g.Printf("\n")
|
|
|
|
return summary.implementable
|
|
}
|
|
|
|
func (g *objcGen) genInterfaceMethodProxy(obj *types.TypeName, m *types.Func) {
|
|
oName := obj.Name()
|
|
s := g.funcSummary(m)
|
|
g.genInterfaceMethodSignature(m, oName, false)
|
|
g.Indent()
|
|
g.Printf("@autoreleasepool {\n")
|
|
g.Indent()
|
|
g.Printf("%s o = go_seq_objc_from_refnum(refnum);\n", g.objcType(obj.Type()))
|
|
for _, p := range s.params {
|
|
g.genRead("_"+p.name, p.name, p.typ, modeTransient)
|
|
}
|
|
|
|
// call method
|
|
if !s.returnsVal() {
|
|
for _, p := range s.retParams {
|
|
if isErrorType(p.typ) {
|
|
g.Printf("NSError* %s = nil;\n", p.name)
|
|
} else {
|
|
g.Printf("%s %s;\n", g.objcType(p.typ), p.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
if s.ret == "void" {
|
|
g.Printf("[o %s];\n", s.callMethod(g))
|
|
} else {
|
|
g.Printf("%s returnVal = [o %s];\n", s.ret, s.callMethod(g))
|
|
}
|
|
|
|
if len(s.retParams) > 0 {
|
|
if s.returnsVal() { // len(s.retParams) == 1 && s.retParams[0] != error
|
|
p := s.retParams[0]
|
|
g.genWrite("returnVal", p.typ, modeRetained)
|
|
g.Printf("return _returnVal;\n")
|
|
} else {
|
|
var rets []string
|
|
for i, p := range s.retParams {
|
|
if isErrorType(p.typ) {
|
|
g.Printf("NSString *%s_str = nil;\n", p.name)
|
|
if i == len(s.retParams)-1 { // last param.
|
|
g.Printf("if (!returnVal) {\n")
|
|
} else {
|
|
g.Printf("if (%s != nil) {\n", p.name)
|
|
}
|
|
g.Indent()
|
|
g.Printf("%[1]s_str = [%[1]s localizedDescription];\n", p.name)
|
|
g.Printf("if (%[1]s_str == nil || %[1]s_str.length == 0) {\n", p.name)
|
|
g.Indent()
|
|
g.Printf("%[1]s_str = @\"gobind: unknown error\";\n", p.name)
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
g.genWrite(p.name+"_str", p.typ, modeRetained)
|
|
rets = append(rets, fmt.Sprintf("_%s_str", p.name))
|
|
} else {
|
|
g.genWrite(p.name, p.typ, modeRetained)
|
|
rets = append(rets, "_"+p.name)
|
|
}
|
|
}
|
|
if len(rets) > 1 {
|
|
g.Printf("cproxy%s_%s_%s_return _sres = {\n", g.pkgPrefix, oName, m.Name())
|
|
g.Printf(" %s\n", strings.Join(rets, ", "))
|
|
g.Printf("};\n")
|
|
g.Printf("return _sres;\n")
|
|
} else {
|
|
g.Printf("return %s;\n", rets[0])
|
|
}
|
|
}
|
|
}
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
|
|
// genRelease cleans up arguments that weren't copied in genWrite.
|
|
func (g *objcGen) genRelease(varName string, t types.Type, mode varMode) {
|
|
if isErrorType(t) {
|
|
g.genRelease(varName, types.Typ[types.String], mode)
|
|
return
|
|
}
|
|
switch t := t.(type) {
|
|
case *types.Slice:
|
|
switch e := t.Elem().(type) {
|
|
case *types.Basic:
|
|
switch e.Kind() {
|
|
case types.Uint8: // Byte.
|
|
if mode == modeTransient {
|
|
// If the argument was not mutable, go_seq_from_objc_bytearray created a copy.
|
|
// Free it here.
|
|
g.Printf("if (![%s isKindOfClass:[NSMutableData class]]) {\n", varName)
|
|
g.Printf(" free(_%s.ptr);\n", varName)
|
|
g.Printf("}\n")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *objcGen) genStructH(obj *types.TypeName, t *types.Struct) {
|
|
g.Printf("@interface %s%s : NSObject {\n", g.namePrefix, obj.Name())
|
|
g.Printf("}\n")
|
|
g.Printf("@property(strong, readonly) id _ref;\n")
|
|
g.Printf("\n")
|
|
g.Printf("- (id)initWithRef:(id)ref;\n")
|
|
|
|
// accessors to exported fields.
|
|
for _, f := range exportedFields(t) {
|
|
name, typ := f.Name(), g.objcFieldType(f.Type())
|
|
g.Printf("- (%s)%s;\n", typ, lowerFirst(name))
|
|
g.Printf("- (void)set%s:(%s)v;\n", name, typ)
|
|
}
|
|
|
|
// exported methods
|
|
for _, m := range exportedMethodSet(types.NewPointer(obj.Type())) {
|
|
s := g.funcSummary(m)
|
|
g.Printf("- %s;\n", lowerFirst(s.asMethod(g)))
|
|
}
|
|
g.Printf("@end\n")
|
|
}
|
|
|
|
func (g *objcGen) genStructM(obj *types.TypeName, t *types.Struct) {
|
|
fields := exportedFields(t)
|
|
methods := exportedMethodSet(types.NewPointer(obj.Type()))
|
|
|
|
g.Printf("\n")
|
|
g.Printf("@implementation %s%s {\n", g.namePrefix, obj.Name())
|
|
g.Printf("}\n\n")
|
|
g.Printf("- (id)initWithRef:(id)ref {\n")
|
|
g.Indent()
|
|
g.Printf("self = [super init];\n")
|
|
g.Printf("if (self) { __ref = ref; }\n")
|
|
g.Printf("return self;\n")
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
|
|
for _, f := range fields {
|
|
g.genGetter(obj.Name(), f)
|
|
g.genSetter(obj.Name(), f)
|
|
}
|
|
|
|
for _, m := range methods {
|
|
s := g.funcSummary(m)
|
|
g.Printf("- %s {\n", s.asMethod(g))
|
|
g.Indent()
|
|
g.genFunc(s, obj.Name())
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
g.Printf("@end\n")
|
|
}
|
|
|
|
func (g *objcGen) errorf(format string, args ...interface{}) {
|
|
g.err = append(g.err, fmt.Errorf(format, args...))
|
|
}
|
|
|
|
func (g *objcGen) refTypeBase(typ types.Type) string {
|
|
switch typ := typ.(type) {
|
|
case *types.Pointer:
|
|
if _, ok := typ.Elem().(*types.Named); ok {
|
|
return g.objcType(typ.Elem())
|
|
}
|
|
case *types.Named:
|
|
n := typ.Obj()
|
|
if n.Pkg() == g.pkg {
|
|
switch typ.Underlying().(type) {
|
|
case *types.Interface, *types.Struct:
|
|
return g.namePrefix + n.Name()
|
|
}
|
|
}
|
|
}
|
|
|
|
// fallback to whatever objcType returns. This must not happen.
|
|
return g.objcType(typ)
|
|
}
|
|
|
|
func (g *objcGen) objcFieldType(t types.Type) string {
|
|
if isErrorType(t) {
|
|
return "NSString*"
|
|
}
|
|
return g.objcType(t)
|
|
}
|
|
|
|
func (g *objcGen) objcType(typ types.Type) string {
|
|
if isErrorType(typ) {
|
|
return "NSError*"
|
|
}
|
|
|
|
switch typ := typ.(type) {
|
|
case *types.Basic:
|
|
switch typ.Kind() {
|
|
case types.Bool, types.UntypedBool:
|
|
return "BOOL"
|
|
case types.Int:
|
|
return "int"
|
|
case types.Int8:
|
|
return "int8_t"
|
|
case types.Int16:
|
|
return "int16_t"
|
|
case types.Int32, types.UntypedRune: // types.Rune
|
|
return "int32_t"
|
|
case types.Int64, types.UntypedInt:
|
|
return "int64_t"
|
|
case types.Uint8:
|
|
// byte is an alias of uint8, and the alias is lost.
|
|
return "byte"
|
|
case types.Uint16:
|
|
return "uint16_t"
|
|
case types.Uint32:
|
|
return "uint32_t"
|
|
case types.Uint64:
|
|
return "uint64_t"
|
|
case types.Float32:
|
|
return "float"
|
|
case types.Float64, types.UntypedFloat:
|
|
return "double"
|
|
case types.String, types.UntypedString:
|
|
return "NSString*"
|
|
default:
|
|
g.errorf("unsupported type: %s", typ)
|
|
return "TODO"
|
|
}
|
|
case *types.Slice:
|
|
elem := g.objcType(typ.Elem())
|
|
// Special case: NSData seems to be a better option for byte slice.
|
|
if elem == "byte" {
|
|
return "NSData*"
|
|
}
|
|
// TODO(hyangah): support other slice types: NSArray or CFArrayRef.
|
|
// Investigate the performance implication.
|
|
g.errorf("unsupported type: %s", typ)
|
|
return "TODO"
|
|
case *types.Pointer:
|
|
if _, ok := typ.Elem().(*types.Named); ok {
|
|
return g.objcType(typ.Elem()) + "*"
|
|
}
|
|
g.errorf("unsupported pointer to type: %s", typ)
|
|
return "TODO"
|
|
case *types.Named:
|
|
n := typ.Obj()
|
|
if n.Pkg() != g.pkg {
|
|
g.errorf("type %s is in package %s; only types defined in package %s is supported", n.Name(), n.Pkg().Name(), g.pkg.Name())
|
|
return "TODO"
|
|
}
|
|
switch t := typ.Underlying().(type) {
|
|
case *types.Interface:
|
|
if makeIfaceSummary(t).implementable {
|
|
return "id<" + g.namePrefix + n.Name() + ">"
|
|
} else {
|
|
return g.namePrefix + n.Name() + "*"
|
|
}
|
|
case *types.Struct:
|
|
return g.namePrefix + n.Name()
|
|
}
|
|
g.errorf("unsupported, named type %s", typ)
|
|
return "TODO"
|
|
default:
|
|
g.errorf("unsupported type: %#+v, %s", typ, typ)
|
|
return "TODO"
|
|
}
|
|
}
|
|
|
|
func lowerFirst(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
|
|
var conv []rune
|
|
for len(s) > 0 {
|
|
r, n := utf8.DecodeRuneInString(s)
|
|
if !unicode.IsUpper(r) {
|
|
if l := len(conv); l > 1 {
|
|
conv[l-1] = unicode.ToUpper(conv[l-1])
|
|
}
|
|
return string(conv) + s
|
|
}
|
|
conv = append(conv, unicode.ToLower(r))
|
|
s = s[n:]
|
|
}
|
|
return string(conv)
|
|
}
|
|
|
|
const (
|
|
objcPreamble = `// Objective-C API for talking to %[1]s Go package.
|
|
// gobind %[2]s %[3]s
|
|
//
|
|
// File is generated by gobind. Do not edit.
|
|
|
|
`
|
|
)
|