| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // The objc package takes the result of an AST traversal by the |
| // importers package and uses the clang command to dump the type |
| // information for the referenced ObjC classes and protocols. |
| // |
| // It is the of go/types for ObjC types and is used by the bind |
| // package to generate Go wrappers for ObjC API on iOS. |
| package objc |
| |
| import ( |
| "bufio" |
| "bytes" |
| "fmt" |
| "os/exec" |
| "strings" |
| "unicode" |
| "unicode/utf8" |
| |
| "golang.org/x/mobile/internal/importers" |
| ) |
| |
| type parser struct { |
| sdkPath string |
| sc *bufio.Scanner |
| |
| decl string |
| indent int |
| last string |
| // Current module as parsed from the AST tree. |
| module string |
| } |
| |
| type TypeKind int |
| |
| // Named represents ObjC classes and protocols. |
| type Named struct { |
| Name string |
| GoName string |
| Module string |
| Funcs []*Func |
| Methods []*Func |
| AllMethods []*Func |
| Supers []Super |
| // For deduplication of function or method |
| // declarations. |
| funcMap map[string]struct{} |
| Protocol bool |
| // Generated is true if the type is wrapper of a |
| // generated Go struct. |
| Generated bool |
| } |
| |
| // Super denotes a super class or protocol. |
| type Super struct { |
| Name string |
| Protocol bool |
| } |
| |
| // Func is a ObjC method, static functions as well as |
| // instance methods. |
| type Func struct { |
| Sig string |
| GoName string |
| Params []*Param |
| Ret *Type |
| Static bool |
| // Method whose name start with "init" |
| Constructor bool |
| } |
| |
| type Param struct { |
| Name string |
| Type *Type |
| } |
| |
| type Type struct { |
| Kind TypeKind |
| // For Interface and Protocol types. |
| Name string |
| // For 'id' types. |
| instanceType bool |
| // The declared type raw from the AST. |
| Decl string |
| // Set if the type is a pointer to its kind. For classes |
| // Indirect is true if the type is a double pointer, e.g. |
| // NSObject **. |
| Indirect bool |
| } |
| |
| const ( |
| Unknown TypeKind = iota |
| Protocol |
| Class |
| String |
| Data |
| Int |
| Uint |
| Short |
| Ushort |
| Bool |
| Char |
| Uchar |
| Float |
| Double |
| ) |
| |
| // Import returns descriptors for a list of references to |
| // ObjC protocols and classes. |
| // |
| // The type information is parsed from the output of clang -cc1 |
| // -ast-dump. |
| func Import(refs *importers.References) ([]*Named, error) { |
| var modules []string |
| modMap := make(map[string]struct{}) |
| typeNames := make(map[string][]string) |
| typeSet := make(map[string]struct{}) |
| genMods := make(map[string]struct{}) |
| for _, emb := range refs.Embedders { |
| genMods[initialUpper(emb.Pkg)] = struct{}{} |
| } |
| for _, ref := range refs.Refs { |
| var module, name string |
| if idx := strings.Index(ref.Pkg, "/"); idx != -1 { |
| // ref is a static method reference. |
| module = ref.Pkg[:idx] |
| name = ref.Pkg[idx+1:] |
| } else { |
| // ref is a type name. |
| module = ref.Pkg |
| name = ref.Name |
| } |
| if _, exists := typeSet[name]; !exists { |
| typeNames[module] = append(typeNames[module], name) |
| typeSet[name] = struct{}{} |
| } |
| if _, exists := modMap[module]; !exists { |
| // Include the module only if it is generated. |
| if _, exists := genMods[module]; !exists { |
| modMap[module] = struct{}{} |
| modules = append(modules, module) |
| } |
| } |
| } |
| sdkPathOut, err := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path").CombinedOutput() |
| if err != nil { |
| return nil, err |
| } |
| sdkPath := strings.TrimSpace(string(sdkPathOut)) |
| var allTypes []*Named |
| typeMap := make(map[string]*Named) |
| for _, module := range modules { |
| types, err := importModule(string(sdkPath), module, typeNames[module], typeMap) |
| if err != nil { |
| return nil, fmt.Errorf("%s: %v", module, err) |
| } |
| allTypes = append(allTypes, types...) |
| } |
| // Embedders refer to every exported Go struct that will have its class |
| // generated. Allow Go code to reverse bind to those classes by synthesizing |
| // their descriptors. |
| for _, emb := range refs.Embedders { |
| module := initialUpper(emb.Pkg) |
| named := &Named{ |
| Name: module + emb.Name, |
| GoName: emb.Name, |
| Module: module, |
| Generated: true, |
| } |
| for _, ref := range emb.Refs { |
| t, exists := typeMap[ref.Name] |
| if !exists { |
| return nil, fmt.Errorf("type not found: %q", ref.Name) |
| } |
| named.Supers = append(named.Supers, Super{ |
| Name: t.Name, |
| Protocol: t.Protocol, |
| }) |
| } |
| typeMap[emb.Name] = named |
| allTypes = append(allTypes, named) |
| } |
| initTypes(allTypes, refs, typeMap) |
| // Include implicit types that are used in parameter or return values. |
| newTypes := allTypes |
| for len(newTypes) > 0 { |
| var impTypes []*Named |
| for _, t := range newTypes { |
| for _, funcs := range [][]*Func{t.Funcs, t.AllMethods} { |
| for _, f := range funcs { |
| types := implicitFuncTypes(f) |
| for _, name := range types { |
| if _, exists := typeSet[name]; exists { |
| continue |
| } |
| typeSet[name] = struct{}{} |
| t, exists := typeMap[name] |
| if !exists { |
| return nil, fmt.Errorf("implicit type %q not found", name) |
| } |
| impTypes = append(impTypes, t) |
| } |
| } |
| } |
| } |
| initTypes(impTypes, refs, typeMap) |
| allTypes = append(allTypes, impTypes...) |
| newTypes = impTypes |
| } |
| return allTypes, nil |
| } |
| |
| func implicitFuncTypes(f *Func) []string { |
| var types []string |
| if rt := f.Ret; rt != nil && !rt.instanceType && (rt.Kind == Class || rt.Kind == Protocol) { |
| types = append(types, rt.Name) |
| } |
| for _, p := range f.Params { |
| if t := p.Type; !t.instanceType && (t.Kind == Class || t.Kind == Protocol) { |
| types = append(types, t.Name) |
| } |
| } |
| return types |
| } |
| |
| func initTypes(types []*Named, refs *importers.References, typeMap map[string]*Named) { |
| for _, t := range types { |
| fillAllMethods(t, typeMap) |
| } |
| // Move constructors to functions. They are represented in Go |
| // as functions. |
| for _, t := range types { |
| var methods []*Func |
| for _, f := range t.AllMethods { |
| if f.Constructor { |
| f.Static = true |
| t.Funcs = append(t.Funcs, f) |
| } else { |
| methods = append(methods, f) |
| } |
| } |
| t.AllMethods = methods |
| } |
| for _, t := range types { |
| mangleMethodNames(t.AllMethods) |
| mangleMethodNames(t.Funcs) |
| } |
| filterReferences(types, refs, typeMap) |
| for _, t := range types { |
| resolveInstanceTypes(t, t.Funcs) |
| resolveInstanceTypes(t, t.AllMethods) |
| } |
| } |
| |
| func filterReferences(types []*Named, refs *importers.References, typeMap map[string]*Named) { |
| refFuncs := make(map[[2]string]struct{}) |
| for _, ref := range refs.Refs { |
| if sep := strings.Index(ref.Pkg, "/"); sep != -1 { |
| pkgName := ref.Pkg[sep+1:] |
| n := typeMap[pkgName] |
| if n == nil { |
| continue |
| } |
| refFuncs[[...]string{pkgName, ref.Name}] = struct{}{} |
| } |
| } |
| for _, t := range types { |
| var filtered []*Func |
| for _, f := range t.Funcs { |
| if _, exists := refFuncs[[...]string{t.GoName, f.GoName}]; exists { |
| filtered = append(filtered, f) |
| } |
| } |
| t.Funcs = filtered |
| filtered = nil |
| for _, m := range t.Methods { |
| if _, exists := refs.Names[m.GoName]; exists { |
| filtered = append(filtered, m) |
| } |
| } |
| t.Methods = filtered |
| filtered = nil |
| for _, m := range t.AllMethods { |
| if _, exists := refs.Names[m.GoName]; exists { |
| filtered = append(filtered, m) |
| } |
| } |
| t.AllMethods = filtered |
| } |
| } |
| |
| // mangleMethodsNames assigns unique Go names to ObjC methods. If a method name is unique |
| // within the same method list, its name is used with its first letter in upper case. |
| // Multiple methods with the same name have their full signature appended, with : removed. |
| func mangleMethodNames(allFuncs []*Func) { |
| goName := func(n string, constructor bool) string { |
| if constructor { |
| n = "new" + n[len("init"):] |
| } |
| return initialUpper(n) |
| } |
| overloads := make(map[string][]*Func) |
| for i, f := range allFuncs { |
| // Copy function so each class can have its own |
| // name mangling. |
| f := *f |
| allFuncs[i] = &f |
| f.GoName = goName(f.Sig, f.Constructor) |
| if colon := strings.Index(f.GoName, ":"); colon != -1 { |
| f.GoName = f.GoName[:colon] |
| } |
| overloads[f.GoName] = append(overloads[f.GoName], &f) |
| } |
| fallbacks := make(map[string][]*Func) |
| for _, funcs := range overloads { |
| if len(funcs) == 1 { |
| continue |
| } |
| for _, f := range funcs { |
| sig := f.Sig |
| if strings.HasSuffix(sig, ":") { |
| sig = sig[:len(sig)-1] |
| } |
| sigElems := strings.Split(f.Sig, ":") |
| for i := 0; i < len(sigElems); i++ { |
| sigElems[i] = initialUpper(sigElems[i]) |
| } |
| name := strings.Join(sigElems, "") |
| f.GoName = goName(name, f.Constructor) |
| fallbacks[f.GoName] = append(fallbacks[f.GoName], f) |
| } |
| } |
| for _, funcs := range fallbacks { |
| if len(funcs) == 1 { |
| continue |
| } |
| for _, f := range funcs { |
| name := strings.Replace(f.Sig, ":", "_", -1) |
| f.GoName = goName(name, f.Constructor) |
| } |
| } |
| } |
| |
| func resolveInstanceType(n *Named, t *Type) *Type { |
| if !t.instanceType || t.Kind != Protocol { |
| return t |
| } |
| // Copy and update the type name for instancetype types |
| ct := *t |
| ct.instanceType = false |
| ct.Decl = n.Name + " *" |
| if n.Name == "NSString" { |
| ct.Kind = String |
| ct.Name = "" |
| } else { |
| ct.Kind = Class |
| ct.Name = n.Name |
| } |
| return &ct |
| } |
| |
| func resolveInstanceTypes(n *Named, funcs []*Func) { |
| for _, f := range funcs { |
| for _, p := range f.Params { |
| p.Type = resolveInstanceType(n, p.Type) |
| } |
| if f.Ret != nil { |
| f.Ret = resolveInstanceType(n, f.Ret) |
| } |
| } |
| } |
| |
| func fillAllMethods(n *Named, typeMap map[string]*Named) { |
| if len(n.AllMethods) > 0 { |
| return |
| } |
| if len(n.Supers) == 0 { |
| n.AllMethods = n.Methods |
| return |
| } |
| for _, sup := range n.Supers { |
| super := lookup(sup.Name, sup.Protocol, typeMap) |
| fillAllMethods(super, typeMap) |
| } |
| methods := make(map[string]struct{}) |
| for _, sup := range n.Supers { |
| super := lookup(sup.Name, sup.Protocol, typeMap) |
| for _, f := range super.AllMethods { |
| if _, exists := methods[f.Sig]; !exists { |
| methods[f.Sig] = struct{}{} |
| n.AllMethods = append(n.AllMethods, f) |
| } |
| } |
| } |
| for _, f := range n.Methods { |
| if _, exists := methods[f.Sig]; !exists { |
| n.AllMethods = append(n.AllMethods, f) |
| } |
| } |
| } |
| |
| const ( |
| frameworksPath = "/System/Library/Frameworks/" |
| ) |
| |
| // importModule parses ObjC type information with clang -cc1 -ast-dump. |
| // |
| // TODO: Use module.map files to precisely model the @import Module.Identifier |
| // directive. For now, importModules assumes the single umbrella header |
| // file Module.framework/Headers/Module.h contains every declaration. |
| func importModule(sdkPath, module string, identifiers []string, typeMap map[string]*Named) ([]*Named, error) { |
| hFile := fmt.Sprintf(sdkPath+frameworksPath+"%s.framework/Headers/%[1]s.h", module) |
| clang := exec.Command("xcrun", "--sdk", "iphonesimulator", "clang", "-cc1", "-isysroot", sdkPath, "-ast-dump", "-fblocks", "-fobjc-arc", "-x", "objective-c", hFile) |
| out, err := clang.CombinedOutput() |
| if err != nil { |
| return nil, fmt.Errorf("clang failed to parse module: %v: %s", err, out) |
| } |
| p := &parser{ |
| sdkPath: sdkPath, |
| sc: bufio.NewScanner(bytes.NewBuffer(out)), |
| } |
| if err := p.parseModule(module, typeMap); err != nil { |
| return nil, err |
| } |
| var types []*Named |
| for _, ident := range identifiers { |
| named, exists := typeMap[ident] |
| if !exists { |
| return nil, fmt.Errorf("no such type: %s", ident) |
| } |
| types = append(types, named) |
| } |
| return types, nil |
| } |
| |
| func (p *parser) scanLine() bool { |
| for { |
| l := p.last |
| if l == "" { |
| if !p.sc.Scan() { |
| return false |
| } |
| l = p.sc.Text() |
| } else { |
| p.last = "" |
| } |
| indent := (strings.Index(l, "-") + 1) / 2 |
| switch { |
| case indent > p.indent: |
| // Skip |
| case indent < p.indent: |
| p.indent-- |
| p.last = l |
| return false |
| case indent == p.indent: |
| p.decl = l[p.indent*2:] |
| return true |
| } |
| } |
| } |
| |
| func (p *parser) parseModule(module string, typeMap map[string]*Named) (err error) { |
| defer func() { |
| if rerr := recover(); rerr != nil { |
| err = rerr.(error) |
| } |
| }() |
| if !p.scanLine() { |
| return nil |
| } |
| // A header file AST starts with |
| // |
| // TranslationUnitDecl 0x103833ad0 <<invalid sloc>> <invalid sloc> |
| if w := p.scanWord(); w != "TranslationUnitDecl" { |
| return fmt.Errorf("unexpected AST root: %q", w) |
| } |
| p.indent++ |
| for { |
| if !p.scanLine() { |
| break |
| } |
| switch w := p.scanWord(); w { |
| case "ObjCCategoryDecl": |
| // ObjCCategoryDecl 0x103d9bdb8 <line:48:1, line:63:2> line:48:12 NSDateCreation |
| // |-ObjCInterface 0x103d9a788 'NSDate' |
| // Skip the node address, the source code range, position. |
| p.scanWord() |
| p.parseLocation() |
| catName := p.scanWord() |
| p.indent++ |
| if !p.scanLine() { |
| return fmt.Errorf("no interface for category %s", catName) |
| } |
| if w := p.scanWord(); w != "ObjCInterface" { |
| return fmt.Errorf("unexpected declaaration %s for category %s", w, catName) |
| } |
| p.scanWord() |
| clsName := p.scanWord() |
| clsName = clsName[1 : len(clsName)-1] |
| named := lookup(clsName, false, typeMap) |
| if named == nil { |
| return fmt.Errorf("category %s references unknown class %s", catName, clsName) |
| } |
| p.parseInterface(named) |
| case "ObjCInterfaceDecl", "ObjCProtocolDecl": |
| // ObjCProtocolDecl 0x104116450 <line:15:1, line:47:2> line:15:11 NSObject |
| // or |
| // ObjCInterfaceDecl 0x1041ca480 <line:17:29, line:64:2> line:17:40 UIResponder |
| |
| prot := w == "ObjCProtocolDecl" |
| |
| // Skip the node address, the source code range, position. |
| p.scanWord() |
| if strings.HasPrefix(p.decl, "prev ") { |
| p.scanWord() |
| p.scanWord() |
| } |
| p.parseLocation() |
| if strings.HasPrefix(p.decl, "implicit ") { |
| p.scanWord() |
| } |
| name := p.decl |
| named := p.lookupOrCreate(name, prot, typeMap) |
| p.indent++ |
| p.parseInterface(named) |
| default: |
| } |
| } |
| return nil |
| } |
| |
| func lookup(name string, prot bool, typeMap map[string]*Named) *Named { |
| var mangled string |
| if prot { |
| mangled = name + "P" |
| } else { |
| mangled = name + "C" |
| } |
| if n := typeMap[mangled]; n != nil { |
| return n |
| } |
| return typeMap[name] |
| } |
| |
| // lookupOrCreate looks up the type name in the type map. If it doesn't exist, it creates |
| // and returns a new type. If it does exist, it returns the existing type. If there are both |
| // a class and a protocol with the same name, their type names are mangled by prefixing |
| // 'C' or 'P' and then re-inserted into the type map. |
| func (p *parser) lookupOrCreate(name string, prot bool, typeMap map[string]*Named) *Named { |
| mangled := name + "C" |
| otherMangled := name + "P" |
| if prot { |
| mangled, otherMangled = otherMangled, mangled |
| } |
| named, exists := typeMap[mangled] |
| if exists { |
| return named |
| } |
| named, exists = typeMap[name] |
| if exists { |
| if named.Protocol == prot { |
| return named |
| } |
| // Both a class and a protocol exists with the same name. |
| delete(typeMap, name) |
| named.GoName = otherMangled |
| typeMap[otherMangled] = named |
| named = &Named{ |
| GoName: mangled, |
| } |
| } else { |
| named = &Named{ |
| GoName: name, |
| } |
| } |
| named.Name = name |
| named.Protocol = prot |
| named.funcMap = make(map[string]struct{}) |
| named.Module = p.module |
| typeMap[named.GoName] = named |
| return named |
| } |
| |
| func (p *parser) parseInterface(n *Named) { |
| for { |
| more := p.scanLine() |
| if !more { |
| break |
| } |
| switch w := p.scanWord(); w { |
| case "super": |
| if w := p.scanWord(); w != "ObjCInterface" { |
| panic(fmt.Errorf("unknown super type: %s", w)) |
| } |
| // Skip node address. |
| p.scanWord() |
| super := p.scanWord() |
| // Remove single quotes |
| super = super[1 : len(super)-1] |
| n.Supers = append(n.Supers, Super{super, false}) |
| case "ObjCProtocol": |
| p.scanWord() |
| super := p.scanWord() |
| super = super[1 : len(super)-1] |
| n.Supers = append(n.Supers, Super{super, true}) |
| case "ObjCMethodDecl": |
| f := p.parseMethod() |
| if f == nil { |
| continue |
| } |
| var key string |
| if f.Static { |
| key = "+" + f.Sig |
| } else { |
| key = "-" + f.Sig |
| } |
| if _, exists := n.funcMap[key]; !exists { |
| n.funcMap[key] = struct{}{} |
| if f.Static { |
| n.Funcs = append(n.Funcs, f) |
| } else { |
| n.Methods = append(n.Methods, f) |
| } |
| } |
| } |
| } |
| } |
| |
| func (p *parser) parseMethod() *Func { |
| // ObjCMethodDecl 0x103bdfb80 <line:17:1, col:27> col:1 - isEqual: 'BOOL':'_Bool' |
| |
| // Skip the address, range, position. |
| p.scanWord() |
| p.parseLocation() |
| if strings.HasPrefix(p.decl, "implicit") { |
| p.scanWord() |
| } |
| f := new(Func) |
| switch w := p.scanWord(); w { |
| case "+": |
| f.Static = true |
| case "-": |
| f.Static = false |
| default: |
| panic(fmt.Errorf("unknown method type for %q", w)) |
| } |
| f.Sig = p.scanWord() |
| if f.Sig == "dealloc" { |
| // ARC forbids dealloc |
| return nil |
| } |
| if strings.HasPrefix(f.Sig, "init") { |
| f.Constructor = true |
| } |
| f.Ret = p.parseType() |
| p.indent++ |
| for { |
| more := p.scanLine() |
| if !more { |
| break |
| } |
| switch p.scanWord() { |
| case "UnavailableAttr": |
| p.indent-- |
| return nil |
| case "ParmVarDecl": |
| f.Params = append(f.Params, p.parseParameter()) |
| } |
| } |
| return f |
| } |
| |
| func (p *parser) parseParameter() *Param { |
| // ParmVarDecl 0x1041caca8 <col:70, col:80> col:80 event 'UIEvent * _Nullable':'UIEvent *' |
| |
| // Skip address, source range, position. |
| p.scanWord() |
| p.parseLocation() |
| return &Param{Name: p.scanWord(), Type: p.parseType()} |
| } |
| |
| func (p *parser) parseType() *Type { |
| // NSUInteger':'unsigned long' |
| s := strings.SplitN(p.decl, ":", 2) |
| decl := s[0] |
| var canon string |
| if len(s) == 2 { |
| canon = s[1] |
| } else { |
| canon = decl |
| } |
| // unquote the type |
| canon = canon[1 : len(canon)-1] |
| if canon == "void" { |
| return nil |
| } |
| decl = decl[1 : len(decl)-1] |
| instancetype := strings.HasPrefix(decl, "instancetype") |
| // Strip modifiers |
| mods := []string{"__strong", "__unsafe_unretained", "const", "__strong", "_Nonnull", "_Nullable", "__autoreleasing"} |
| for _, mod := range mods { |
| if idx := strings.Index(canon, mod); idx != -1 { |
| canon = canon[:idx] + canon[idx+len(mod):] |
| } |
| if idx := strings.Index(decl, mod); idx != -1 { |
| decl = decl[:idx] + decl[idx+len(mod):] |
| } |
| } |
| canon = strings.TrimSpace(canon) |
| decl = strings.TrimSpace(decl) |
| t := &Type{ |
| Decl: decl, |
| instanceType: instancetype, |
| } |
| switch canon { |
| case "int", "long", "long long": |
| t.Kind = Int |
| case "unsigned int", "unsigned long", "unsigned long long": |
| t.Kind = Uint |
| case "short": |
| t.Kind = Short |
| case "unsigned short": |
| t.Kind = Ushort |
| case "char": |
| t.Kind = Char |
| case "unsigned char": |
| t.Kind = Uchar |
| case "float": |
| t.Kind = Float |
| case "double": |
| t.Kind = Double |
| case "_Bool": |
| t.Kind = Bool |
| case "NSString *": |
| t.Kind = String |
| case "NSData *": |
| t.Kind = Data |
| default: |
| switch { |
| case strings.HasPrefix(canon, "enum"): |
| t.Kind = Int |
| case strings.HasPrefix(canon, "id"): |
| _, gen := p.splitGeneric(canon) |
| t.Kind = Protocol |
| t.Name = gen |
| default: |
| if ind := strings.Count(canon, "*"); 1 <= ind && ind <= 2 { |
| space := strings.Index(canon, " ") |
| name := canon[:space] |
| name, _ = p.splitGeneric(name) |
| t.Kind = Class |
| t.Name = name |
| t.Indirect = ind > 1 |
| } |
| } |
| } |
| return t |
| } |
| |
| func (p *parser) splitGeneric(decl string) (string, string) { |
| // NSArray<KeyType> |
| if br := strings.Index(decl, "<"); br != -1 { |
| return decl[:br], decl[br+1 : len(decl)-1] |
| } else { |
| return decl, "" |
| } |
| } |
| |
| func (p *parser) parseSrcPos() { |
| const invPref = "<invalid sloc>" |
| if strings.HasPrefix(p.decl, invPref) { |
| p.decl = p.decl[len(invPref):] |
| return |
| } |
| // line:17:2, col:18 or, a file location: |
| // /.../UIKit.framework/Headers/UISelectionFeedbackGenerator.h:16:1 |
| loc := p.scanWord() |
| locs := strings.SplitN(loc, ":", 2) |
| if len(locs) != 2 && len(locs) != 3 { |
| panic(fmt.Errorf("invalid source position: %q", loc)) |
| } |
| switch loc := locs[0]; loc { |
| case "line", "col": |
| default: |
| if !strings.HasPrefix(loc, p.sdkPath) { |
| panic(fmt.Errorf("invalid source position: %q", loc)) |
| } |
| loc = loc[len(p.sdkPath):] |
| switch { |
| case strings.HasPrefix(loc, "/usr/include/objc/"): |
| p.module = "Foundation" |
| case strings.HasPrefix(loc, frameworksPath): |
| loc = loc[len(frameworksPath):] |
| i := strings.Index(loc, ".framework") |
| if i == -1 { |
| panic(fmt.Errorf("invalid source position: %q", loc)) |
| } |
| p.module = loc[:i] |
| // Some types are declared in CoreFoundation.framework |
| // even though they belong in Foundation in Objective-C. |
| if p.module == "CoreFoundation" { |
| p.module = "Foundation" |
| } |
| default: |
| } |
| } |
| } |
| |
| func (p *parser) parseLocation() { |
| // Source ranges are on the form: <line:17:29, line:64:2>. |
| if !strings.HasPrefix(p.decl, "<") { |
| panic(fmt.Errorf("1no source range first in %s", p.decl)) |
| } |
| p.decl = p.decl[1:] |
| p.parseSrcPos() |
| if strings.HasPrefix(p.decl, ", ") { |
| p.decl = p.decl[2:] |
| p.parseSrcPos() |
| } |
| if !strings.HasPrefix(p.decl, "> ") { |
| panic(fmt.Errorf("no source range first in %s", p.decl)) |
| } |
| p.decl = p.decl[2:] |
| p.parseSrcPos() |
| } |
| |
| func (p *parser) scanWord() string { |
| i := 0 |
| loop: |
| for ; i < len(p.decl); i++ { |
| switch p.decl[i] { |
| case ' ', '>', ',': |
| break loop |
| } |
| } |
| w := p.decl[:i] |
| p.decl = p.decl[i:] |
| for len(p.decl) > 0 && p.decl[0] == ' ' { |
| p.decl = p.decl[1:] |
| } |
| return w |
| } |
| |
| func initialUpper(s string) string { |
| if s == "" { |
| return "" |
| } |
| r, n := utf8.DecodeRuneInString(s) |
| return string(unicode.ToUpper(r)) + s[n:] |
| } |
| |
| func (t *Named) ObjcType() string { |
| if t.Protocol { |
| return fmt.Sprintf("id<%s>", t.Name) |
| } else { |
| return t.Name + " *" |
| } |
| } |