541 lines
13 KiB
Go
541 lines
13 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/token"
|
|
"golang.org/x/tools/go/types"
|
|
"log"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type objcGen struct {
|
|
*printer
|
|
fset *token.FileSet
|
|
pkg *types.Package
|
|
err ErrorList
|
|
|
|
// fields set by init.
|
|
pkgName string
|
|
namePrefix string
|
|
funcs []*types.Func
|
|
names []*types.TypeName
|
|
}
|
|
|
|
func capitalize(n string) string {
|
|
firstRune, size := utf8.DecodeRuneInString(n)
|
|
return string(unicode.ToUpper(firstRune)) + n[size:]
|
|
}
|
|
|
|
func (g *objcGen) init() {
|
|
g.pkgName = g.pkg.Name()
|
|
g.namePrefix = "Go" + capitalize(g.pkgName)
|
|
g.funcs = nil
|
|
g.names = nil
|
|
|
|
scope := g.pkg.Scope()
|
|
for _, name := range scope.Names() {
|
|
obj := scope.Lookup(name)
|
|
if !obj.Exported() {
|
|
continue
|
|
}
|
|
switch obj := obj.(type) {
|
|
case *types.Func:
|
|
g.funcs = append(g.funcs, obj)
|
|
case *types.TypeName:
|
|
g.names = append(g.names, obj)
|
|
// TODO(hyangah): *types.Const, *types.Var
|
|
}
|
|
}
|
|
}
|
|
|
|
const objcPreamble = `// Objective-C API for talking to %s Go package.
|
|
// gobind -lang=objc %s
|
|
//
|
|
// File is generated by gobind. Do not edit.
|
|
|
|
`
|
|
|
|
func (g *objcGen) genH() error {
|
|
g.init()
|
|
|
|
g.Printf(objcPreamble, g.pkg.Path(), g.pkg.Path())
|
|
g.Printf("#ifndef __Go%s_H__\n", capitalize(g.pkgName))
|
|
g.Printf("#define __Go%s_H__\n", capitalize(g.pkgName))
|
|
g.Printf("\n")
|
|
g.Printf(`#include <Foundation/Foundation.h>`)
|
|
g.Printf("\n\n")
|
|
|
|
// @class names
|
|
for _, obj := range g.names {
|
|
named := obj.Type().(*types.Named)
|
|
switch named.Underlying().(type) {
|
|
case *types.Struct, *types.Interface:
|
|
g.Printf("@class %s%s;\n", g.namePrefix, obj.Name())
|
|
}
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// @interfaces
|
|
for _, obj := range g.names {
|
|
named := obj.Type().(*types.Named)
|
|
switch t := named.Underlying().(type) {
|
|
case *types.Struct:
|
|
g.genStructH(obj, t)
|
|
case *types.Interface:
|
|
g.genInterfaceH(obj, t)
|
|
}
|
|
g.Printf("\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) genM() error {
|
|
g.init()
|
|
|
|
g.Printf(objcPreamble, g.pkg.Path(), 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("\n")
|
|
g.Printf("static NSString *errDomain = @\"go.%s\";\n", g.pkg.Path())
|
|
g.Printf("\n")
|
|
|
|
g.Printf("#define _DESCRIPTOR_ %q\n\n", g.pkgName)
|
|
for i, obj := range g.funcs {
|
|
g.Printf("#define _CALL_%s_ %d\n", obj.Name(), i+1)
|
|
}
|
|
g.Printf("\n")
|
|
|
|
// @implementation Go*_* : GoSeqProxyObject
|
|
for _, obj := range g.names {
|
|
named := obj.Type().(*types.Named)
|
|
switch t := named.Underlying().(type) {
|
|
case *types.Struct:
|
|
g.genStructM(obj, t)
|
|
case *types.Interface:
|
|
g.genInterfaceM(obj, t)
|
|
}
|
|
g.Printf("\n")
|
|
}
|
|
|
|
// global functions.
|
|
for _, obj := range g.funcs {
|
|
g.genFuncM(obj)
|
|
g.Printf("\n")
|
|
}
|
|
|
|
if len(g.err) > 0 {
|
|
return g.err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type funcSummary struct {
|
|
name string
|
|
ret string
|
|
params, retParams []paramInfo
|
|
}
|
|
|
|
type paramInfo struct {
|
|
typ types.Type
|
|
name string
|
|
}
|
|
|
|
func (g *objcGen) funcSummary(obj *types.Func) *funcSummary {
|
|
s := &funcSummary{name: obj.Name()}
|
|
|
|
sig := obj.Type().(*types.Signature)
|
|
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, 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) seqType(typ types.Type) string {
|
|
s := seqType(typ)
|
|
if s == "String" {
|
|
// TODO(hyangah): non utf-8 strings.
|
|
s = "UTF8"
|
|
}
|
|
return s
|
|
}
|
|
|
|
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("_DESCRIPTOR_", fmt.Sprintf("_CALL_%s_", s.name), s, false)
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
}
|
|
|
|
func (g *objcGen) genFunc(pkgDesc, callDesc string, s *funcSummary, isMethod bool) {
|
|
g.Printf("GoSeq in_ = {};\n")
|
|
g.Printf("GoSeq out_ = {};\n")
|
|
if isMethod {
|
|
g.Printf("go_seq_writeRef(&in_, self.ref);\n")
|
|
}
|
|
for _, p := range s.params {
|
|
st := g.seqType(p.typ)
|
|
if st == "Ref" {
|
|
g.Printf("go_seq_write%s(&in_, %s.ref);\n", st, p.name)
|
|
} else {
|
|
g.Printf("go_seq_write%s(&in_, %s);\n", st, p.name)
|
|
}
|
|
}
|
|
g.Printf("go_seq_send(%s, %s, &in_, &out_);\n", pkgDesc, callDesc)
|
|
|
|
if s.returnsVal() {
|
|
p := s.retParams[0]
|
|
if seqTyp := g.seqType(p.typ); seqTyp != "Ref" {
|
|
g.Printf("%s %s = go_seq_read%s(&out_);\n", g.objcType(p.typ), p.name, g.seqType(p.typ))
|
|
} else {
|
|
ptype := g.objcType(p.typ)
|
|
g.Printf("GoSeqRef* %s_ref = go_seq_readRef(&out_);\n", p.name)
|
|
g.Printf("%s %s = %s_ref.obj;\n", ptype, p.name, p.name)
|
|
g.Printf("if (%s == NULL) {\n", p.name)
|
|
g.Indent()
|
|
g.Printf("%s = [[%s alloc] initWithRef:%s_ref];\n", p.name, ptype[:len(ptype)-1], p.name)
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
}
|
|
} else {
|
|
for _, p := range s.retParams {
|
|
if isErrorType(p.typ) {
|
|
g.Printf("NSString* _%s = go_seq_readUTF8(&out_);\n", p.name)
|
|
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 if seqTyp := g.seqType(p.typ); seqTyp != "Ref" {
|
|
g.Printf("%s %s_val = go_seq_read%s(&out_);\n", g.objcType(p.typ), p.name, g.seqType(p.typ))
|
|
g.Printf("if (%s != NULL) {\n", p.name)
|
|
g.Indent()
|
|
g.Printf("*%s = %s_val;\n", p.name, p.name)
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
} else {
|
|
ptype := g.objcType(p.typ)
|
|
g.Printf("GoSeqRef* %s_ref = go_seq_readRef(&out_);\n", p.name)
|
|
g.Printf("if (%s != NULL) {\n", p.name)
|
|
g.Indent()
|
|
g.Printf("*%s = %s_ref.obj;\n", p.name, p.name)
|
|
g.Printf("if (*%s == NULL) {\n", p.name)
|
|
g.Indent()
|
|
g.Printf("*%s = [[%s alloc] initWithRef:%s_ref];\n", p.name, ptype[:len(ptype)-1], p.name)
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
g.Outdent()
|
|
g.Printf("}\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
g.Printf("go_seq_free(&in_);\n")
|
|
g.Printf("go_seq_free(&out_);\n")
|
|
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) genInterfaceH(obj *types.TypeName, t *types.Interface) {
|
|
log.Printf("TODO: %s", obj.Name())
|
|
}
|
|
func (g *objcGen) genInterfaceM(obj *types.TypeName, t *types.Interface) {
|
|
log.Printf("TODO: %s", obj.Name())
|
|
}
|
|
|
|
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) {
|
|
// TODO(hyangah): error type field?
|
|
name, typ := f.Name(), g.objcType(f.Type())
|
|
g.Printf("- (%s)%s;\n", typ, 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", 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()))
|
|
|
|
desc := fmt.Sprintf("_GO_%s_%s", g.pkgName, obj.Name())
|
|
g.Printf("#define %s_DESCRIPTOR_ \"go.%s.%s\"\n", desc, g.pkgName, obj.Name())
|
|
for i, f := range fields {
|
|
g.Printf("#define %s_FIELD_%s_GET_ (0x%x0f)\n", desc, f.Name(), i)
|
|
g.Printf("#define %s_FIELD_%s_SET_ (0x%x1f)\n", desc, f.Name(), i)
|
|
}
|
|
for i, m := range methods {
|
|
g.Printf("#define %s_%s_ (0x%x0c)\n", desc, m.Name(), i)
|
|
}
|
|
|
|
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 {
|
|
// getter
|
|
// TODO(hyangah): support error type fields?
|
|
s := &funcSummary{
|
|
name: f.Name(),
|
|
ret: g.objcType(f.Type()),
|
|
}
|
|
s.retParams = append(s.retParams, paramInfo{typ: f.Type(), name: "ret_"})
|
|
|
|
g.Printf("- %s {\n", s.asMethod(g))
|
|
g.Indent()
|
|
g.genFunc(desc+"_DESCRIPTOR_", desc+"_FIELD_"+f.Name()+"_GET_", s, true)
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
|
|
// setter
|
|
s = &funcSummary{
|
|
name: "set" + f.Name(),
|
|
ret: "void",
|
|
}
|
|
s.params = append(s.params, paramInfo{typ: f.Type(), name: "v"})
|
|
|
|
g.Printf("- %s {\n", s.asMethod(g))
|
|
g.Indent()
|
|
g.genFunc(desc+"_DESCRIPTOR_", desc+"_FIELD_"+f.Name()+"_SET_", s, true)
|
|
g.Outdent()
|
|
g.Printf("}\n\n")
|
|
}
|
|
|
|
for _, m := range methods {
|
|
s := g.funcSummary(m)
|
|
g.Printf("- %s {\n", s.asMethod(g))
|
|
g.Indent()
|
|
g.genFunc(desc+"_DESCRIPTOR_", desc+"_"+m.Name()+"_", s, true)
|
|
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) objcType(typ types.Type) string {
|
|
if isErrorType(typ) {
|
|
return "NSError*"
|
|
}
|
|
|
|
switch typ := typ.(type) {
|
|
case *types.Basic:
|
|
switch typ.Kind() {
|
|
case types.Bool:
|
|
return "BOOL"
|
|
case types.Int:
|
|
return "int"
|
|
case types.Int8:
|
|
return "int8_t"
|
|
case types.Int16:
|
|
return "int16_t"
|
|
case types.Int32:
|
|
return "int32_t"
|
|
case types.Int64:
|
|
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:
|
|
return "double"
|
|
case types.String:
|
|
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 typ.Underlying().(type) {
|
|
case *types.Interface:
|
|
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"
|
|
}
|
|
}
|