// 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" "go/types" "strings" ) // TODO(hyangah): error code/domain propagation type objcGen struct { *printer fset *token.FileSet pkg *types.Package err ErrorList prefix string // prefix arg passed by flag. // fields set by init. pkgName string namePrefix string funcs []*types.Func names []*types.TypeName } func (g *objcGen) init() { g.pkgName = g.pkg.Name() g.namePrefix = g.prefix + strings.Title(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: if isCallable(obj) { 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 %[1]s Go package. // gobind %[2]s %[3]s // // File is generated by gobind. Do not edit. ` func (g *objcGen) genH() error { g.init() g.Printf(objcPreamble, g.pkg.Path(), g.gobindOpts(), g.pkg.Path()) g.Printf("#ifndef __Go%s_H__\n", strings.Title(g.pkgName)) g.Printf("#define __Go%s_H__\n", strings.Title(g.pkgName)) g.Printf("\n") g.Printf("#include ") g.Printf("\n\n") // @class names for _, obj := range g.names { named := obj.Type().(*types.Named) switch t := named.Underlying().(type) { case *types.Struct: g.Printf("@class %s%s;\n\n", g.namePrefix, obj.Name()) case *types.Interface: if !makeIfaceSummary(t).implementable { g.Printf("@class %s%s;\n\n", g.namePrefix, obj.Name()) } } } // @interfaces for _, obj := range g.names { named := obj.Type().(*types.Named) switch t := named.Underlying().(type) { case *types.Struct: g.genStructH(obj, t) g.Printf("\n") 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) 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.init() g.Printf(objcPreamble, g.pkg.Path(), g.gobindOpts(), g.pkg.Path()) g.Printf("#include %q\n", g.namePrefix+".h") g.Printf("#include \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("@protocol goSeqRefInterface\n") g.Printf("-(GoSeqRef*) ref;\n") g.Printf("@end\n") 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") // struct, interface. var interfaces []*types.TypeName 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: if g.genInterfaceM(obj, t) { interfaces = append(interfaces, obj) } } g.Printf("\n") } // global functions. for _, obj := range g.funcs { g.genFuncM(obj) g.Printf("\n") } // register proxy functions. if len(interfaces) > 0 { g.Printf("__attribute__((constructor)) static void init() {\n") g.Indent() for _, obj := range interfaces { g.Printf("go_seq_register_proxy(\"go.%s.%s\", proxy%s%s);\n", g.pkgName, obj.Name(), g.namePrefix, obj.Name()) } g.Outdent() 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) 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", 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) genGetter(desc string, f *types.Var) { t := f.Type() if isErrorType(t) { t = types.Typ[types.String] } s := &funcSummary{ name: f.Name(), ret: g.objcType(t), retParams: []paramInfo{{typ: t, 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") } func (g *objcGen) genSetter(desc string, f *types.Var) { t := f.Type() if isErrorType(t) { t = types.Typ[types.String] } s := &funcSummary{ name: "set" + f.Name(), ret: "void", params: []paramInfo{{typ: t, 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") } 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("if ([(id)(%s) isKindOfClass:[%s class]]) {\n", p.name, g.refTypeBase(p.typ)) g.Indent() g.Printf("id %[1]s_proxy = (id)(%[1]s);\n", p.name) g.Printf("go_seq_writeRef(&in_, %s_proxy.ref);\n", p.name) g.Outdent() g.Printf("} else {\n") g.Indent() g.Printf("go_seq_writeObjcRef(&in_, %s);\n", p.name) g.Outdent() g.Printf("}\n") } 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, g.refTypeBase(p.typ), 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 { 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, g.refTypeBase(p.typ), 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) 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") g.Printf("\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) 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, m := range summary.callable { g.Printf("#define %s_%s_ (0x%x0a)\n", desc, m.Name(), i+1) } g.Printf("\n") if summary.implementable { // @interface Interface -- similar to what genStructH does. g.genInterfaceInterface(obj, summary, true) } // @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(desc+"_DESCRIPTOR_", desc+"_"+m.Name()+"_", s, true) g.Outdent() g.Printf("}\n\n") } g.Printf("@end\n") g.Printf("\n") // proxy function. if summary.implementable { g.Printf("static void proxy%s%s(id obj, int code, GoSeq* in, GoSeq* out) {\n", g.namePrefix, obj.Name()) g.Indent() g.Printf("switch (code) {\n") for _, m := range summary.callable { g.Printf("case %s_%s_: {\n", desc, m.Name()) g.Indent() g.genInterfaceMethodProxy(obj, g.funcSummary(m)) g.Outdent() g.Printf("} break;\n") } g.Printf("default:\n") g.Indent() g.Printf("NSLog(@\"unknown code %%x for %s_DESCRIPTOR_\", code);\n", desc) g.Outdent() g.Printf("}\n") g.Outdent() g.Printf("}\n") } return summary.implementable } func (g *objcGen) genInterfaceMethodProxy(obj *types.TypeName, s *funcSummary) { g.Printf("id<%[1]s%[2]s> o = (id<%[1]s%[2]s>)(obj);\n", g.namePrefix, obj.Name()) // read params from GoSeq* inseq for _, p := range s.params { stype := g.seqType(p.typ) ptype := g.objcType(p.typ) if stype == "Ref" { g.Printf("GoSeqRef* %s_ref = go_seq_readRef(in);\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, g.refTypeBase(p.typ), p.name) g.Outdent() g.Printf("}\n") } else { g.Printf("%s %s = go_seq_read%s(in);\n", ptype, p.name, stype) } } // call method if !s.returnsVal() { for _, p := range s.retParams { if isErrorType(p.typ) { g.Printf("NSError* %s = NULL;\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)) } // write result to GoSeq* outseq if len(s.retParams) == 0 { return } if s.returnsVal() { // len(s.retParams) == 1 && s.retParams[0] != error p := s.retParams[0] if stype := g.seqType(p.typ); stype == "Ref" { g.Printf("if ([(id)(returnVal) isKindOfClass:[%s class]]) {\n", g.refTypeBase(p.typ)) g.Indent() g.Printf("idretVal_proxy = (id)(returnVal);\n") g.Printf("go_seq_writeRef(out, retVal_proxy.ref);\n") g.Outdent() g.Printf("} else {\n") g.Indent() g.Printf("go_seq_writeRef(out, returnVal);\n") g.Outdent() g.Printf("}\n") } else { g.Printf("go_seq_write%s(out, returnVal);\n", stype) } return } for i, p := range s.retParams { if isErrorType(p.typ) { if i == len(s.retParams)-1 { // last param. g.Printf("if (returnVal) {\n") } else { g.Printf("if (%s == NULL) {\n", p.name) } g.Indent() g.Printf("go_seq_writeUTF8(out, NULL);\n") g.Outdent() g.Printf("} else {\n") g.Indent() g.Printf("NSString* %[1]sDesc = [%[1]s localizedDescription];\n", p.name) g.Printf("if (%[1]sDesc == NULL || %[1]sDesc.length == 0) {\n", p.name) g.Indent() g.Printf("%[1]sDesc = @\"gobind: unknown error\";\n", p.name) g.Outdent() g.Printf("}\n") g.Printf("go_seq_writeUTF8(out, %sDesc);\n", p.name) g.Outdent() g.Printf("}\n") } else if seqTyp := g.seqType(p.typ); seqTyp == "Ref" { // TODO(hyangah): NULL. g.Printf("if ([(id)(%s) isKindOfClass:[%s class]]) {\n", p.name, g.refTypeBase(p.typ)) g.Indent() g.Printf("id%[1]s_proxy = (id)(%[1]s);\n", p.name) g.Printf("go_seq_writeRef(out, %s_proxy.ref);\n", p.name) g.Outdent() g.Printf("} else {\n") g.Indent() g.Printf("go_seq_writeObjcRef(out, %s);\n", p.name) g.Outdent() g.Printf("}\n") } else { g.Printf("go_seq_write%s(out, %s);\n", seqTyp, p.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) { name, typ := f.Name(), g.objcFieldType(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 { g.genGetter(desc, f) g.genSetter(desc, f) } 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) 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. panic(fmt.Sprintf("wtf: %+T", typ)) 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: 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 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" } }