mirror of
https://github.com/go-i2p/gomobile-java.git
synced 2025-07-13 14:18:23 -04:00

Converting a Go string to a string suitable use a specialized function, UTF16Encode, that can encode the string directly to a malloc'ed buffer. That way, only two copies are made when strings are passed from Go to Java; once for UTF-8 to UTF-16 encoding and once for the creation of the Java String. This CL implements the same optimization in the other direction, with a UTF-16 to UTF-8 decoder implemented in C. Unfortunately, while calling into a Go decoder also saves the extra copy, the Cgo overhead makes the calls much slower for short strings. To alleviate the risk of introducing decoding bugs, I've added the tests from the encoding/utf16 package to SeqTest. As a sideeffect, both Java and ObjC now always copy strings, regardless of the argument mode. The cpy argument can therefore be removed from the string conversion functions. Furthermore, the modeRetained and modeReturned modes can be collapsed into just one. While we're here, delete a leftover function from seq/strings.go that wasn't removed when the old seq buffers went away. Benchmarks, as compared with benchstat over 5 runs: name old time/op new time/op delta JavaStringShort 11.4µs ±13% 11.6µs ± 4% ~ (p=0.859 n=10+5) JavaStringShortDirect 19.5µs ± 9% 20.3µs ± 2% +3.68% (p=0.019 n=9+5) JavaStringLong 103µs ± 8% 24µs ± 4% -77.13% (p=0.001 n=9+5) JavaStringLongDirect 113µs ± 9% 32µs ± 7% -71.63% (p=0.001 n=9+5) JavaStringShortUnicode 11.1µs ±16% 10.7µs ± 5% ~ (p=0.190 n=9+5) JavaStringShortUnicodeDirect 19.6µs ± 7% 20.2µs ± 1% +2.78% (p=0.029 n=9+5) JavaStringLongUnicode 97.1µs ± 9% 28.0µs ± 5% -71.17% (p=0.001 n=9+5) JavaStringLongUnicodeDirect 105µs ±10% 34µs ± 5% -67.23% (p=0.002 n=8+5) JavaStringRetShort 14.2µs ± 2% 13.9µs ± 1% -2.15% (p=0.006 n=8+5) JavaStringRetShortDirect 20.8µs ± 2% 20.4µs ± 2% ~ (p=0.065 n=8+5) JavaStringRetLong 42.2µs ± 9% 42.4µs ± 3% ~ (p=0.190 n=9+5) JavaStringRetLongDirect 51.2µs ±21% 50.8µs ± 8% ~ (p=0.518 n=9+5) GoStringShort 23.4µs ± 7% 22.5µs ± 3% -3.55% (p=0.019 n=9+5) GoStringLong 51.9µs ± 9% 53.1µs ± 3% ~ (p=0.240 n=9+5) GoStringShortUnicode 24.2µs ± 6% 22.8µs ± 1% -5.54% (p=0.002 n=9+5) GoStringLongUnicode 58.6µs ± 8% 57.6µs ± 3% ~ (p=0.518 n=9+5) GoStringRetShort 27.6µs ± 1% 23.2µs ± 2% -15.87% (p=0.003 n=7+5) GoStringRetLong 129µs ±12% 33µs ± 2% -74.03% (p=0.001 n=10+5) Change-Id: Icb9481981493ffca8defed9fb80a9433d6048937 Reviewed-on: https://go-review.googlesource.com/20250 Reviewed-by: David Crawshaw <crawshaw@golang.org>
1014 lines
25 KiB
Go
1014 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("%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\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.
|
|
|
|
`
|
|
)
|