43 |
44 |
45 |
Initializer
46 |
Alloc & Init
47 |
Objective-C void test() {
48 | Foo *foo;
49 | foo = [[Foo alloc] init];
50 | // ...
51 | }
52 |
53 |
Swift func test() {
54 | var foo:Foo!
55 | foo = Foo()
56 | // ...
57 | }
58 |
59 |
60 |
Designated Initializer
61 |
Designated Initializer is partially supported. The signature can be coverted but inner implementation is not modified.
62 |
Objective-C @implementation Foo
63 | -(id)init {
64 | self = [super init]; // Remains in Swift code
65 | if(self) { // Remains in Swift code
66 | // ...
67 | }
68 | return self; // Remains in Swift code
69 | }
70 |
71 | -(instancetype)initWithFrame:(CGRect) frame {
72 | // ...
73 | }
74 | @end
75 |
76 |
Swift class Foo {
77 | init() {
78 | self = super.init() // Remains in Swift code
79 | if (self != nil) { // Remains in Swift code
80 | // ...
81 | }
82 | return self // Remains in Swift code
83 | }
84 |
85 | init(frame:CGRect) {
86 | // ...
87 | }
88 | }
89 |
90 |
91 |
92 |
96 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/docs/doc/class.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
43 |
44 |
45 |
Class and Category
46 |
Basic Class Definition
47 |
Objective-C @interface Foo
48 | @property int bar;
49 | -(void)method;
50 | @end
51 |
52 | @implementation Foo
53 | -(void)method {
54 | // do something
55 | }
56 | @end
57 |
58 |
Swift class Foo {
59 | var bar:Int
60 |
61 | func method() {
62 | // do something
63 | }
64 | }
65 |
66 |
67 |
Inheritance
68 |
Objective-C @interface Foo : Bar
69 | @end
70 |
71 | @implementation Foo
72 | // ...
73 | @end
74 |
75 |
Swift class Foo : Bar {
76 | // ...
77 | }
78 |
79 |
80 |
Anonymous Category
81 |
Properties declared in interface or anonymous category are converted into instance variables of a single class together.
82 |
Properties from the anonymous category are treated as private.
83 |
84 |
Objective-C @interface Foo
85 | @property int bar;
86 | @end
87 |
88 | @interface Foo ()
89 | @property int baz;
90 | @end
91 |
92 | @implementation Foo
93 | @end
94 |
95 |
Swift class Foo {
96 | var bar:Int
97 | private var baz:Int
98 | }
99 |
100 |
101 |
102 |
106 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/src/import-resolver.js:
--------------------------------------------------------------------------------
1 | module.exports = (function(){
2 | "use strict";
3 |
4 | var fs = require('fs-extra');
5 | var glob = require('glob');
6 | var crypto = require('crypto');
7 | var path = require('path');
8 |
9 |
10 | var ImportResolver = function(options) {
11 | this.verbose = options.verbose;
12 | this.buildFrameworkHeaderMap(options.frameworkPath||[]);
13 | this.buildHeaderMap(options.includePath||[]);
14 | };
15 |
16 | ImportResolver.VERSION = "0.0.1";
17 |
18 | ImportResolver.prototype.loadFile = function(path) {
19 | try {
20 | return fs.readFileSync(path, 'utf-8');
21 | } catch(e) {
22 | return null;
23 | }
24 | };
25 |
26 | var _getCacheRoot = function() {
27 | return (process.env.HOME||process.env.USERPROFILE) + "/.objc2swift/cache";
28 | };
29 | ImportResolver.getCacheRoot = _getCacheRoot;
30 |
31 | var _getCacheDir = function() {
32 | return _getCacheRoot() + '/' + ImportResolver.VERSION + '/';
33 | };
34 | ImportResolver.getCacheDir = _getCacheDir;
35 |
36 | function hash_name(name) {
37 | var sha = crypto.createHash('md5');
38 | sha.update(name, 'utf8');
39 | return name.replace(/^.*\//,'') + "_" + sha.digest('hex');
40 | }
41 |
42 | ImportResolver.prototype.readCache = function(path) {
43 | try {
44 | var cacheName = _getCacheDir() + hash_name(path);
45 |
46 | var stat = fs.statSync(path);
47 | var cstat = fs.statSync(cacheName);
48 | if(cstat.mtime.getTime() < stat.mtime.getTime()) {
49 | if(this.verbose) {
50 | console.error("Cache expired: " + path.replace(/^.*\//,''));
51 | }
52 | return null;
53 | }
54 | var text = fs.readFileSync(cacheName,'utf-8');
55 | return JSON.parse(text);
56 | } catch(e) {
57 | return null;
58 | }
59 | }
60 |
61 | ImportResolver.prototype.writeCache = function(path,data) {
62 | try {
63 | var cacheName = _getCacheDir() + hash_name(path);
64 | var json = JSON.stringify(data,function(k,v){
65 | if(k=="ast") {
66 | return null;
67 | }
68 | return v;
69 | },' ')
70 | fs.writeFileSync(cacheName,json,'utf-8');
71 | } catch(e) {
72 | }
73 | };
74 |
75 | ImportResolver.prototype.buildFrameworkHeaderMap = function(frameworkPathList) {
76 |
77 | this.frameworkHeaderMap = {};
78 | for(var i=0;i
2 |
3 |
4 | Objc2Swift.js - Document
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Blocks
46 |
Blocks as Variable
47 |
Objective-C void (^blk)(int) = ^(int a) {
48 | // ...
49 | };
50 |
51 |
Swift let blk:(Int)->Void = { (a:Int) in
52 | // ...
53 | }
54 |
55 |
56 |
Blocks as Property
57 |
Objective-C @interface Foo
58 | @property void(^blk)(NSString *);
59 | @end
60 |
61 | @implementation Foo
62 | -(void)method {
63 | blk = ^(NSString *msg){
64 | NSLog(@"In block: %@", msg);
65 | // ...
66 | };
67 | }
68 | @end
69 |
70 |
Swift class Foo {
71 | var blk:(String!)->Void
72 |
73 | func method() {
74 | blk = { (msg:String!) in
75 | NSLog("In block: %@", msg)
76 | // ...
77 | }
78 | }
79 | }
80 |
81 |
82 |
Blocks as Parameter
83 |
Objective-C @implementation Foo
84 | -(void)method:(void (^)(int))blk {
85 | // ...
86 | }
87 | @end
88 |
89 |
Swift class Foo {
90 | func method(blk:(Int)->Void) {
91 | // ...
92 | }
93 | }
94 |
95 |
96 |
Direct Blocks Declaration
97 |
This pattern is not supported yet.
98 |
Objective-C /** Not Supported yet */
99 | void (^blk)(int) {
100 | // ...
101 | };
102 |
103 |
Swift /** Not Supported yet */
104 | func blk() {
105 | // ...
106 | };
107 |
108 |
109 |
110 |
114 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/docs/doc/optional.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Objc2Swift.js - Document
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Optional Type
46 |
Nullability Qualifier
47 |
Objective-C @implementation A
48 | -(void)someMethod:(NSString *)arg1 :(nonnull NSString *)arg2 :(nullable NSString *)arg3 {
49 | //...
50 | }
51 | @end
52 |
53 |
Swift class A {
54 | func someMethod(arg1:String!, arg2:String, arg3:String?) {
55 | //...
56 | }
57 | }
58 |
59 |
60 |
Auto-unwrapping
61 |
Objective-C @implementation A
62 | -(void)someMethod:(nullable NSString *)str {
63 | NSLog(@"%l",[str length]);
64 | }
65 | @end
66 |
67 |
Swift class A {
68 | func someMethod(str:String?) {
69 | NSLog("%l",str!.length())
70 | }
71 | }
72 |
73 |
74 |
Nullability Annotation
75 |
Objective-C NS_ASSUME_NONNULL_BEGIN
76 | @interface AAPLList : NSObject
77 | // ...
78 | - (nullable AAPLListItem *)itemWithName:(NSString *)name;
79 | - (NSInteger)indexOfItem:(AAPLListItem *)item;
80 |
81 | @property (copy, nullable) NSString *name;
82 | @property (copy, readonly) NSArray *allItems;
83 | // ...
84 | @end
85 | NS_ASSUME_NONNULL_END
86 |
87 | @implementation AAPLList
88 | - (nullable AAPLListItem *)itemWithName:(NSString *)name {
89 | // ...
90 | }
91 |
92 | - (NSInteger)indexOfItem:(AAPLListItem *)item {
93 | // ...
94 | }
95 | @end
96 |
97 |
Swift class AAPLList : NSObject {
98 | @NSCopying var name:String?
99 | @NSCopying private(set) var allItems:[AnyObject]
100 |
101 | func itemWithName(name:String) -> AAPLListItem? {
102 | // ...
103 | }
104 |
105 | func indexOfItem(item:AAPLListItem) -> Int {
106 | // ...
107 | }
108 | }
109 |
110 |
111 |
112 |
116 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/docs/doc/enum.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Objc2Swift.js - Document
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Enumeration
46 |
47 | Note: Enumeration conversion specification will be fixed. Currently the converter treats the enum type name as the prefix of each enumerators.
48 | This results approximatly fine, but strictly, it is different from Swift's behavior.
49 |
50 |
51 |
Enumeration Declaration
52 |
Objective-C typedef NS_ENUM(NSUInteger, Animal) {
53 | AnimalSwift,
54 | AnimalLion,
55 | AnimalPanther,
56 | AnimalTiger,
57 | };
58 |
59 |
Swift enum Animal:UInt {
60 | case Swift
61 | case Lion
62 | case Panther
63 | case Tiger
64 | }
65 |
66 |
67 |
Enumeration Reference
68 |
Objective-C typedef NS_ENUM(NSUInteger, Animal) {
69 | AnimalSwift,
70 | AnimalLion,
71 | AnimalPanther,
72 | AnimalTiger,
73 | };
74 |
75 | @implementation MyClass
76 | -(NSString *)someMethod:(MyColor)color {
77 | switch(color) {
78 | case AnimalLion:
79 | case AnimalPanther:
80 | case AnimalTiger:
81 | return "OS X code name";
82 | case AnimalSwift:
83 | return "Language name";
84 | default:
85 | return NULL;
86 | }
87 | }
88 | @end
89 |
90 |
Swift enum Animal:UInt {
91 | case Swift
92 | case Lion
93 | case Panther
94 | case Tiger
95 | }
96 |
97 | class MyClass {
98 | func someMethod(color:MyColor) -> String! {
99 | switch(color) {
100 | case Animal.Lion,
101 | Animal.Panther,
102 | Animal.Tiger:
103 | return "OS X code name"
104 | case Animal.Swift:
105 | return "Language name"
106 | default:
107 | return nil
108 | }
109 | }
110 | }
111 |
112 |
113 |
114 |
118 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/spec/statement.spec:
--------------------------------------------------------------------------------
1 | # Statement
2 |
3 | ## If Statement
4 | ```
5 | @implementation Foo
6 | -(NSString *)method {
7 | if(Condition) {
8 | return @"OK";
9 | } else {
10 | return @"NG";
11 | }
12 | }
13 | @end
14 | ```
15 |
16 | ```
17 | class Foo {
18 | func method() -> String! {
19 | if Condition {
20 | return "OK"
21 | } else {
22 | return "NG"
23 | }
24 | }
25 | }
26 | ```
27 |
28 | ## Switch Statement
29 | ```
30 | @implementation Foo
31 | -(NSString *)method {
32 | switch(Condition) {
33 | case Apple:
34 | return @"apple";
35 | case Orange:
36 | return @"orange";
37 | default:
38 | return @"else";
39 | }
40 | }
41 | @end
42 | ```
43 |
44 | ```
45 | class Foo {
46 | func method() -> String! {
47 | switch(Condition) {
48 | case Apple:
49 | return "apple"
50 | case Orange:
51 | return "orange"
52 | default:
53 | return "else"
54 | }
55 | }
56 | }
57 | ```
58 |
59 | ## Switch Statement (sequential cases)
60 | ```
61 | @implementation Foo
62 | -(NSString *)method {
63 | switch(Condition) {
64 | case Apple:
65 | case Orange:
66 | return @"fruit";
67 | default:
68 | return @"else";
69 | }
70 | }
71 | @end
72 | ```
73 | ```
74 | class Foo {
75 | func method() -> String! {
76 | switch(Condition) {
77 | case Apple,
78 | Orange:
79 | return "fruit"
80 | default:
81 | return "else"
82 | }
83 | }
84 | }
85 | ```
86 |
87 | ## For Statement
88 | ```
89 | @implementation Foo
90 | -(void)method {
91 | for(int i=0;i<100;i++) {
92 | NSLog("%d",i);
93 | }
94 | }
95 | @end
96 | ```
97 | ```
98 | class Foo {
99 | func method() {
100 | for var i:Int=0 ; i<100 ; i++ {
101 | NSLog("%d",i)
102 | }
103 | }
104 | }
105 | ```
106 |
107 | ## For-In Statement
108 | ```
109 | @implementation Foo
110 | -(void)method {
111 | for(int bar in Foo) {
112 | NSLog("%d",bar);
113 | }
114 | }
115 | @end
116 | ```
117 |
118 | ```
119 | class Foo {
120 | func method() {
121 | for bar:Int in Foo {
122 | NSLog("%d",bar)
123 | }
124 | }
125 | }
126 | ```
127 |
128 | ## While Statement
129 | ```
130 | @implementation Foo
131 | -(void)method {
132 | while(flag) {
133 | // Do Something
134 | }
135 | }
136 | @end
137 | ```
138 |
139 | ```
140 | class Foo {
141 | func method() {
142 | while flag {
143 | // Do Something
144 | }
145 | }
146 | }
147 | ```
148 |
149 | ## Do-White Statement
150 | ```
151 | @implementation Foo
152 | -(void)method {
153 | do {
154 | // Do Something
155 | } while(flag);
156 | }
157 | @end
158 | ```
159 | ```
160 | class Foo {
161 | func method() {
162 | repeat {
163 | // Do Something
164 | } while flag
165 | }
166 | }
167 | ```
168 |
169 | ## Try-Catch Statment
170 | Since Swift's try-catch statement has different semantics from Objective-C's, @try, @catch and @finally keywords are not be converted.
171 |
172 | ```
173 | @implementation Foo
174 | -(void)method {
175 | @try {
176 | // ...
177 | }
178 | @catch(Error *err) {
179 | // ...
180 | }
181 | @finally {
182 | // ...
183 | }
184 | }
185 | @end
186 | ```
187 | ```
188 | class Foo {
189 | func method() {
190 | @try {
191 | // ...
192 | }
193 | @catch(err:Error!) {
194 | // ...
195 | }
196 | @finally {
197 | // ...
198 | }
199 | }
200 | }
201 | ```
202 |
203 | ## Jump and Labeled Statement
204 |
205 | ```
206 | @implementation Foo
207 | -(void)method {
208 | for(int i = 0; i < 100; i++) {
209 | if (i == 50) {
210 | goto bar;
211 | }
212 | }
213 | bar:
214 | return;
215 | }
216 | @end
217 | ```
218 | ```
219 | class Foo {
220 | func method() {
221 | for var i:Int=0 ; i < 100 ; i++ {
222 | if i == 50 {
223 | goto bar
224 | }
225 | }
226 | bar:
227 | return
228 | }
229 | }
230 | ```
231 |
232 |
--------------------------------------------------------------------------------
/docs/doc/protocol.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Objc2Swift.js - Document
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Protocol
46 |
Protocol Declaration
47 | Note the converter does not generate Swift protocol code if the protocol declaration is found in the imported header.
48 | If you would like to get Swift protocol code, pass the header file where the protocol is declared to the converter.
49 |
50 |
Objective-C @protocol MyProtocol
51 | @property int property;
52 | @property (readonly) int readonlyProperty;
53 | +(void)classMethod;
54 | -(void)method;
55 | @optional
56 | @property int optionalProperty;
57 | -(void)optionalClassMethod;
58 | -(void)optionalMethod;
59 | @end
60 |
61 |
Swift protocol MyProtocol {
62 | var property:Int { get set }
63 | var readonlyProperty:Int { get }
64 | class func classMethod()
65 | func method()
66 |
67 | optional var optionalProperty:Int { get set }
68 | optional func optionalClassMethod()
69 | optional func optionalMethod()
70 | }
71 |
72 |
73 |
Protocol Reference
74 |
Objective-C @interface Foo : NSObject <Bar, Baz>
75 | @end
76 |
77 | @implementation Foo
78 | // ...
79 | @end
80 |
81 |
Swift class Foo : NSObject, Bar, Baz {
82 | // ...
83 | }
84 |
85 |
86 |
Protocol Name Confliction
87 |
Objective-C @interface Foo
88 | @end
89 |
90 | // Objective-C allows the same protocol name with
91 | // the class but Swift doesn't.
92 | // suffix `-Protocol` is added to the Swift
93 | // protocol if name confliction is found.
94 | @protocol Foo
95 | @end
96 |
97 | @protocol Bar
98 | @end
99 |
100 | @interface MyClass : Foo<Foo,Bar>
101 | @property Foo *oFoo;
102 | @property id<Foo> pFoo;
103 | @property id<Bar> pBar;
104 | @end
105 |
106 | @implementation MyClass
107 | @end
108 |
109 |
Swift // Objective-C allows the same protocol name with
110 | // the class but Swift doesn't.
111 | // suffix `-Protocol` is added to the Swift
112 | // protocol if name confliction is found.
113 |
114 |
115 | class MyClass : Foo, FooProtocol, Bar {
116 | var oFoo:Foo!
117 | var pFoo:FooProtocol!
118 | var pBar:Bar!
119 | }
120 |
121 |
122 |
123 |
127 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/docs/doc/method.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Objc2Swift.js - Document
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Class
46 |
Class method
47 |
Objective-C @implementation Foo
48 | +(int)method {
49 | return 0;
50 | }
51 | @end
52 |
53 |
Swift class Foo {
54 | class func method() -> Int {
55 | return 0
56 | }
57 | }
58 |
59 |
60 |
Instance method
61 |
Objective-C @implementation Foo
62 | -(int)method {
63 | return 0;
64 | }
65 | @end
66 |
67 |
Swift class Foo {
68 | func method() -> Int {
69 | return 0
70 | }
71 | }
72 |
73 |
74 |
Method with params
75 |
Objective-C @implementation Foo
76 | -(void)method:(int)arg1 label:(NSString *)arg2 {}
77 | -(void)method2:(int)arg1 :(NSString *)arg2 {}
78 | -(void)method3:(int)arg1 arg2:(NSString *)arg2 {}
79 | @end
80 |
81 |
Swift class Foo {
82 | func method(arg1:Int, label arg2:String!) {}
83 | func method2(arg1:Int, arg2:String!) {}
84 | func method3(arg1:Int, arg2:String!) {}
85 | }
86 |
87 |
88 |
Override Completion
89 |
Objective-C @interface SuperFoo
90 | -(void)someMethod:(NSNumber *)arg;
91 | @end
92 |
93 | @interface Foo : SuperFoo
94 | -(void)someMethod:(NSNumber *)arg;
95 | @end
96 |
97 | @implementation Foo
98 | -(void)someMethod:(NSNumber *)arg {
99 | // ...
100 | }
101 | @end
102 |
103 |
Swift class Foo : SuperFoo {
104 | override func someMethod(arg:NSNumber!) {
105 | // ...
106 | }
107 | }
108 |
109 |
110 |
Respect Interface declaration
111 |
If the signature of the method implementation is different from interface,
112 | the generated swift code follows interface declaration. Such patterns are mostly apparel with nullability specifiers.
113 |
114 |
115 |
Objective-C @interface Foo
116 | -(NSString * nonnull)method;
117 | @end
118 |
119 | @implementation Foo
120 | -(NSString *)method {
121 | // ...
122 | }
123 | @end
124 |
125 |
Swift class Foo {
126 | func method() -> String {
127 | // ...
128 | }
129 | }
130 |
131 |
132 |
133 |
137 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/docs/css/index.css:
--------------------------------------------------------------------------------
1 | * {padding:0;margin:0;box-sizing:border-box;}
2 | html,body {width:100%;height:100%;}
3 |
4 | body {
5 | font-family:Helvetica, Arial, sans-serif;
6 | font-size:15px;
7 | background-color:#fafafa;
8 | min-width:900px;
9 | }
10 |
11 | a {
12 | color:#3a76ff;
13 | text-decoration: none;
14 | }
15 |
16 | section.page-header {
17 | position:relative;
18 | width:100%;
19 | color:#fff;
20 | background-color:#3a76ff;
21 | background-image: linear-gradient(120deg, #3a76ff, #155799);
22 | padding:72px;
23 | }
24 |
25 | .page-header h1 {
26 | font-weight:bold;
27 | font-size:48px;
28 | line-height:72px;
29 | vertical-align:middle;
30 | }
31 |
32 | .page-header h2 {
33 | font-weight:300;
34 | font-size:16px;
35 | line-height:24px;
36 | }
37 |
38 | .page-header .btn {
39 | display:inline-block;
40 | padding:0 8px;
41 | height:36px;
42 | line-height:36px;
43 | border-radius:2px;
44 | border:1px solid #fff;
45 | font-size:16px;
46 | font-weight:normal;
47 | margin:16px 0;
48 | vertical-align:middle;
49 | }
50 |
51 | .page-header a.btn {
52 | color:white;
53 | text-decoration: none;
54 | transition:opacity 0.15s linear;
55 | }
56 |
57 | .page-header a.btn:hover {
58 | opacity:0.5;
59 | }
60 |
61 | nav.root-nav {
62 | position:absolute;
63 | left:72px;right:72px;
64 | bottom:0;
65 | }
66 |
67 | .root-nav a {
68 | cursor:pointer;
69 | text-decoration: none;
70 | color:white;
71 | transition:opacity 0.15s linear;
72 | }
73 |
74 | .root-nav a:hover {
75 | text-decoration: none;
76 | opacity:0.5;
77 | }
78 |
79 | .root-nav ul {
80 | margin-left:-8px;
81 | }
82 |
83 | .root-nav li {
84 | display:inline-block;
85 | font-size:14px;
86 | padding:0 8px;
87 | height:48px;
88 | line-height:48px;
89 | list-style-type: none;
90 | text-transform: uppercase;
91 | }
92 |
93 | .root-nav li.selected {
94 | border-bottom:2px solid yellow;
95 | }
96 |
97 | section.content {
98 | padding:0 72px 56px 72px;
99 | line-height:160%;
100 | }
101 |
102 | .content ul {
103 |
104 | }
105 |
106 | .content li {
107 | margin-left:24px;
108 | }
109 |
110 | .content code {
111 | border-radius:2px;
112 | padding:.25em .5em;
113 | background-color:#efeff4;
114 | }
115 |
116 | .clearfix:after {
117 | content: ".";
118 | display: block;
119 | height: 0;
120 | font-size:0;
121 | clear: both;
122 | visibility:hidden;
123 | }
124 |
125 | .source {
126 | width:50%;
127 | float:left;
128 | padding-right:1em;
129 | }
130 | .result {
131 | width:50%;
132 | float:left;
133 | padding-left:1em;
134 | }
135 |
136 | .source pre, .result pre, .source #editor {
137 | height:280px;
138 | }
139 |
140 | p {
141 | margin:.75em 0;
142 | line-height:160%;
143 | }
144 |
145 | p.elapsed {
146 | color:rgba(0,0,0,.54);
147 | font-size:12px;
148 | }
149 |
150 | p.note {
151 | font-size:90%;
152 | font-weight:bold;
153 | }
154 |
155 | pre {
156 | width:100%;
157 | height:100%;
158 | white-space: pre-wrap;
159 | }
160 |
161 | pre code {
162 | font-size:12px;
163 | font-weight:normal;
164 | display:block;
165 | width:100%;
166 | height:100%;
167 | padding:.5em;
168 | overflow:auto;
169 | line-height:130%;
170 | }
171 |
172 | code {
173 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
174 | }
175 |
176 | code.objectivec {
177 | background-color:#ffffff;
178 | }
179 |
180 | code.swift {
181 | background-color:#efeff4;
182 | }
183 |
184 | .content h1 {
185 | font-size:24px;
186 | color:rgba(0,0,0,0.87);
187 | margin:56px 0 24px 0;
188 | }
189 |
190 | .content h3 {
191 | font-size:16px;
192 | color:#3a76ff;
193 | margin:16px 0;
194 | }
195 |
196 | .source-header select {
197 | margin:16px 0 16px 16px;
198 | float:left;
199 | font-size:16px;
200 | }
201 |
202 | .source-header h3 {
203 | float:left;
204 | }
205 |
206 | input.url {
207 | font-size:12px;
208 | width:75%;
209 | padding:.5em;
210 | }
211 |
212 | button {
213 | font-size:14px;
214 | padding:0 8px;
215 | line-height:36px;
216 | height:36px;
217 | border-radius:2px;
218 | color:white;
219 | background-color:#3a76ff;
220 | border:none;
221 | }
222 |
223 | button:focus, select:focus, .swift:focus {
224 | outline:none;
225 | }
226 |
227 | button:hover {
228 | box-shadow:0 3px 8px 0 rgba(0,0,0,.25);
229 | }
230 |
231 | button:disabled {
232 | font-size:14px;
233 | background-color:#f0f0f0;
234 | color:#ccc;
235 | box-shadow:none;
236 | }
237 |
238 | button#retrieve {
239 | min-width:120px;
240 | margin-bottom:1em;
241 | }
242 |
243 | button#convert {
244 | width:100%;
245 | margin:1em 0;
246 | cursor:pointer;
247 | }
248 |
249 | hr {
250 | border:none;
251 | border-top:1px dotted #ccc;
252 | margin:16px 0;
253 | }
254 |
255 | footer {
256 | font-size:10px;
257 | color:#777;
258 | margin:56px 0 0px 0;
259 | }
260 |
--------------------------------------------------------------------------------
/docs/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Objc2Swift.js - Live Demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
38 |
39 |
40 | Live Demo
41 |
42 | Choose a code sample from the menu, or type your code to the input box. Then press [Convert] to generate Swift code.
43 | You can also drag-and-drop your source file to the box.
44 |
45 | Note: This demo NEVER send your code to the server. The conversion process is executed locally in your browser.
46 |
47 |
48 |
61 |
@interface MyClass
62 | - (void)hello;
63 | @end
64 |
65 | @implementation MyClass
66 | - (void)hello {
67 | NSLog(@"Hello the Swift World!");
68 | }
69 | @end
70 |
Convert
71 |
72 |
76 |
77 |
78 | The command-line version is also available for OS X. It is capable to analyze header files and provides more accurate conversion. See GitHub Repository .
79 |
80 |
84 |
85 |
86 |
90 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/docs/doc/misc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Objc2Swift.js - Document
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Miscellaneous
46 |
Assignment analysis
47 |
Objective-C @implementation MyClass
48 | -(void)test {
49 | NSString *foo = @"FOO";
50 | NSString *bar = @"BAR";
51 |
52 | foo = @"baz";
53 | // ...
54 | return foo;
55 | }
56 | @end
57 |
58 |
Swift class MyClass {
59 | func test() {
60 | var foo:String! = "FOO"
61 | let bar:String! = "BAR"
62 |
63 | foo = "baz"
64 | // ...
65 | return foo
66 | }
67 | }
68 |
69 |
70 |
NULL check idiom
71 |
Objective-C @implementation MyClass
72 | -(void)test:(NSString *)str {
73 | if(!str) {
74 | NSLog(@"str is NULL");
75 | }
76 | }
77 | @end
78 |
79 |
Swift class MyClass {
80 | func test(str:String!) {
81 | if (str == nil) {
82 | NSLog("str is NULL")
83 | }
84 | }
85 | }
86 |
87 |
88 |
class Keyword
89 |
Objective-C void test() {
90 |
91 | NSString *str;
92 |
93 | // Get type from class.
94 | [NSString class];
95 | NSString.class;
96 |
97 | // Get dynamic type from instance.
98 | [str class];
99 | str.class;
100 |
101 | }
102 |
103 |
Swift func test() {
104 |
105 | var str:String!
106 |
107 | // Get type from class.
108 | String.self
109 | String.self
110 |
111 | // Get dynamic type from instance.
112 | str.dynamicType
113 | str.dynamicType
114 |
115 | }
116 |
117 |
118 |
isKindOfClass
119 |
Objective-C void test(id obj) {
120 | if([obj isKindOfClass:[NSString class]]) {
121 | // Do something
122 | }
123 | }
124 |
125 |
Swift func test(obj:AnyObject!) {
126 | if (obj is NSString) {
127 | // Do something
128 | }
129 | }
130 |
131 |
132 |
instancetype
133 |
Objective-C @implementation Foo
134 | -(instancetype)method {
135 | // ...
136 | return self;
137 | }
138 | @end
139 |
140 |
Swift class Foo {
141 | func method() -> Self {
142 | // ...
143 | return self
144 | }
145 | }
146 |
147 |
148 |
@available
149 |
Objective-C @implementation MyClass
150 | -(void)test {
151 | if(@available(iOS 10.0, *)) {
152 | // >= iOS10
153 | } else {
154 | // <= iOS9
155 | }
156 | }
157 | @end
158 |
159 |
Swift class MyClass {
160 | func test() {
161 | if #available(iOS 10.0, *) {
162 | // >= iOS10
163 | } else {
164 | // <= iOS9
165 | }
166 | }
167 | }
168 |
169 |
170 |
171 |
175 |
181 |
182 |
183 |
--------------------------------------------------------------------------------
/docs/css/online.css:
--------------------------------------------------------------------------------
1 | * {padding:0;margin:0;box-sizing:border-box;}
2 | html {
3 | width:100%;height:100%;
4 | }
5 |
6 | button:focus {
7 | outline:none;
8 | }
9 |
10 | .material-icons {
11 | font-size:inherit;
12 | }
13 |
14 | body {
15 | font-family:Helvetica, Arial, sans-serif;
16 | background-color:#808080;
17 | min-width:900px;
18 | height:100%;
19 | width:100%;
20 | overflow:hidden;
21 | }
22 |
23 | a {
24 | color:#3a76ff;
25 | text-decoration: none;
26 | }
27 |
28 | section {
29 | min-width:800px;
30 | }
31 |
32 | section.page-header {
33 | position:relative;
34 | width:100%;
35 | color:#fff;
36 | background-color:#3a76ff;
37 | background-image: linear-gradient(120deg, #3a76ff, #155799);
38 | padding:0 72px;
39 | height:24px;
40 | top:0;
41 | }
42 |
43 | nav.root-nav {
44 | position:absolute;
45 | left:72px;right:72px;
46 | bottom:0;
47 | }
48 |
49 | .root-nav a {
50 | cursor:pointer;
51 | text-decoration: none;
52 | color:white;
53 | transition:opacity 0.15s linear;
54 | }
55 |
56 | .root-nav a:hover {
57 | text-decoration: none;
58 | opacity:0.5;
59 | }
60 |
61 | .root-nav ul {
62 | margin-left:-8px;
63 | }
64 |
65 | .root-nav li {
66 | display:inline-block;
67 | font-size:12px;
68 | padding:0 8px;
69 | height:24px;
70 | line-height:24px;
71 | list-style-type: none;
72 | text-transform: uppercase;
73 | }
74 |
75 | .root-nav li.selected {
76 | border-bottom:2px solid yellow;
77 | }
78 |
79 | section.toolbar {
80 | position:absolute;
81 | height:44px;
82 | width:100%;
83 | background-color:#fafafa;
84 | border-top:1px solid #fff;
85 | border-bottom:1px solid #333;
86 | }
87 |
88 | section.toolbar .left:after {
89 | content:'';
90 | clear:both;
91 | }
92 |
93 | section.toolbar .left {
94 | width:50%;
95 | padding:4px 8px;
96 | }
97 |
98 | section.toolbar button {
99 | min-width:56px;
100 | height:32px;
101 | font-size:12px;
102 | line-height:22px;
103 | border-radius: 2px;
104 | background:none;
105 | color:rgba(0,0,0,.84);
106 | cursor: pointer;
107 | padding:0 12px 0 8px;
108 | margin:auto 0 auto 8px;
109 | transition:0.25s all;
110 | border:none;
111 | }
112 |
113 | section.toolbar button .material-icons {
114 | font-size:20px;
115 | margin-top:-1px;
116 | vertical-align:middle;
117 | }
118 |
119 | section.toolbar button:hover {
120 | background-color:#f0f0f0;
121 | }
122 |
123 | section.page-footer {
124 | position:absolute;
125 | width:100%;
126 | bottom:0;
127 | height:48px;
128 | line-height:48px;
129 | background-color:#555;
130 | border-top:1px solid #888;
131 | font-size:12px;
132 | color:#fff;
133 | font-weight:normal;
134 | padding-left:20px;
135 | }
136 |
137 | section.page-header h1 {
138 | position:relative;
139 | left:16px;
140 | font-weight:normal;
141 | font-size:12px;
142 | line-height:24px;
143 | text-align:center;
144 | }
145 |
146 | section.content-wrap {
147 | position:absolute;
148 | top:0;bottom:0;left:0;right:0;
149 | padding:80px 0 48px 0;
150 | }
151 |
152 | .content {
153 | position:relative;
154 | height:100%;
155 | border-bottom:1px solid #444;
156 | }
157 |
158 | .content h3 {
159 | font-size:10px;
160 | line-height:24px;
161 | font-weight:bold;
162 | text-align:center;
163 | width:120px;
164 | background-color:#fff;
165 | margin:4px 4px 0 8px;
166 | border-radius:2px 2px 0 0;
167 | }
168 |
169 | .content .left {
170 | width:50%;
171 | height:100%;
172 | float:left;
173 | }
174 |
175 | .content .right {
176 | width:50%;
177 | height:100%;
178 | float:left;
179 | }
180 |
181 | .clearfix {
182 | content:'';
183 | clear:both;
184 | }
185 |
186 | .editor-wrap {
187 | position:relative;
188 | width:100%;
189 | height:100%;
190 | padding-bottom:28px;
191 | }
192 |
193 | #editor, #viewer {
194 | width:100%;height:100%;
195 | font-size:11px;
196 | }
197 |
198 | #modal-background {
199 | position:absolute;
200 | width:100%;height:100%;
201 | background-color:rgba(0,0,0,.5);
202 | z-index:998;
203 | display:none;
204 | }
205 |
206 | #modal-stage {
207 | position:absolute;
208 | width:100%;height:100%;
209 | z-index:999;
210 | display:none;
211 | }
212 |
213 | .dialog {
214 | position:absolute;
215 | padding:20px;
216 | background-color:#555;
217 | border-radius:2px;
218 | border:1px solid #444;
219 | margin:auto;
220 | top:0px;left:0;right:0;bottom:120px;
221 | box-shadow:0 2px 8px 0 rgba(0,0,0,0.25);
222 | color:white;
223 | font-size:12px;
224 | line-height: 160%
225 | }
226 |
227 | .dialog h1 {
228 | font-size:14px;
229 | line-height:20px;
230 | margin-bottom:12px;
231 | }
232 |
233 | .dialog .button-box {
234 | margin:8px 0;
235 | }
236 |
237 | .dialog .button-box button {
238 | float:right;
239 | }
240 |
241 | .dialog button {
242 | margin:0 0 0 8px;
243 | padding:0 8px;
244 | line-height:36px;
245 | font-size:14px;
246 | color:white;
247 | border:none;
248 | background:none;
249 | cursor: pointer;
250 | border-radius: 2px;
251 | transition: background-color 0.5s;
252 | min-width:64px;
253 | }
254 |
255 | .dialog button:hover {
256 | background-color:#3a76ff;
257 | }
258 |
259 | .dialog p {
260 | margin:12px 0;
261 | }
262 |
263 | .dialog p a {
264 | text-decoration: underline;
265 | color:#f0f0f0;
266 | }
267 |
268 | #open-url-dialog {
269 | width:640px;
270 | height:160px;
271 | }
272 |
273 | #open-url-dialog input {
274 | width:100%;
275 | font-size:12px;
276 | padding:4px 8px;
277 | }
--------------------------------------------------------------------------------
/docs/doc/property.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Objc2Swift.js - Document
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Property
46 |
Readonly Property
47 |
The property with readonly attribute is converted to a variable and its setter is set private.
48 |
Objective-C @interface MyClass
49 | @property (readonly) NSString *str;
50 | @end
51 |
52 | @implementation MyClass
53 | @end
54 |
55 |
Swift class MyClass {
56 | private(set) var str:String!
57 | }
58 |
59 |
60 |
Copy Property
61 |
Objective-C @interface MyClass
62 | @property (copy) NSString *str;
63 | @end
64 |
65 | @implementation MyClass
66 | @end
67 |
68 |
Swift class MyClass {
69 | @NSCopying var str:String!
70 | }
71 |
72 |
73 |
Private Property
74 |
The property declared in any anonymous category is converted into a private variable. Other properies are treated as public.
75 |
Objective-C @interface MyClass
76 | @property int bar;
77 | @end
78 |
79 | @interface MyClass ()
80 | @property int baz;
81 | @end
82 |
83 | @implementation MyClass
84 | @end
85 |
86 |
Swift class MyClass {
87 | var bar:Int
88 | private var baz:Int
89 | }
90 |
91 |
92 |
Auto-synthesizing Aware
93 |
Objective-C automatically synthesizes instantce variable _foo for @property foo declaration. Objc2swift checks the reference of _foo in any instance method and if so, explicit setter and getter functions are generated for safe.
94 |
95 |
Objective-C @interface MyClass
96 | @property NSString *foo; // This property implicitly generates `_foo`.
97 | @end
98 |
99 | @implementation MyClass
100 | -(void)someMethod {
101 | NSLog("%@", _foo); // reference of auto-synthesized variable.
102 | }
103 | @end
104 |
105 |
Swift class MyClass {
106 | private var _foo:String!
107 | var foo:String! {
108 | get { return _foo }
109 | set { _foo = newValue }
110 | }
111 |
112 | func someMethod() {
113 | NSLog("%@", _foo) // reference of auto-synthesized variable.
114 | }
115 | }
116 |
117 |
118 |
Setter and Getter
119 |
If there are setter and getter code in the class implementation, they are converted into Swift's variables with setter and getter function.
120 |
121 |
Objective-C @interface MyClass
122 | @property int foo;
123 | @end
124 |
125 | @implementation MyClass
126 | -(void)setFoo:(int)value {
127 | _foo = foo * 2;
128 | }
129 | -(int)foo {
130 | return _foo / 2;
131 | }
132 | @end
133 |
134 |
Swift class MyClass {
135 | private var _foo:Int
136 | var foo:Int {
137 | get {
138 | return _foo / 2
139 | }
140 | set(value) {
141 | _foo = foo * 2
142 | }
143 | }
144 |
145 | // `setFoo:` has moved as a setter.
146 | // `foo` has moved as a getter.
147 | }
148 |
149 |
150 |
151 |
155 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/docs/js/demo.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | var editor;
4 |
5 | function showError(message) {
6 | document.getElementById("result").value = message;
7 | }
8 |
9 | function doConvert() {
10 | var button = document.getElementById("convert");
11 | var btnText = button.innerText;
12 | button.innerText = "Wait";
13 | button.disabled = true;
14 | setTimeout(function(){
15 | compile(editor.getValue());
16 | button.innerText = btnText;
17 | button.disabled = false;
18 | },100);
19 | }
20 |
21 | function retrieve(url, complete) {
22 | var baseUrl = url.replace(/^(https?:\/\/)raw.githubusercontent\.com\/([^\/]+\/[^\/]+)\/(.*)$/,'$1github.com/$2/blob/$3').replace(/[^\/]*$/,'');
23 | var rawUrl = url.replace(/^(https?:\/\/)github\.com\/(.*)\/blob\/(.*)$/,'$1raw.githubusercontent.com/$2/$3');
24 | var start = Date.now();
25 | var xhr = new XMLHttpRequest();
26 | xhr.open("GET",rawUrl);
27 | xhr.addEventListener('load',function(){
28 | if(xhr.status == 200 || xhr.status == 304 || xhr.status == 0) {
29 | initSourceForm(xhr.responseText);
30 | if(complete) complete();
31 | } else {
32 | var err = new Error("Error:" + xhr.statusText);
33 | initSourceForm(err);
34 | if(complete) complete(err);
35 | }
36 | });
37 | xhr.addEventListener('error',function(e){
38 | var err = new Error("Error: Check 'Access-Control-Allow-Origin' header is present for the target resource. See browser's development panel for detail. If you run this script local Chrome, `--allow-file-access-from-files` option is required.");
39 | initSourceForm(err);
40 | if(complete) complete(err);
41 | });
42 | xhr.send();
43 | }
44 |
45 | var o2s = new O2S();
46 |
47 | function initSourceForm(inp) {
48 | if(typeof(inp) == "string") {
49 | editor.setValue(inp,-1);
50 | document.getElementById("result").innerText = '';
51 | applyHighlight();
52 | } else {
53 | editor.setValue("// " + inp.message);
54 | document.getElementById("result").innerText = "";
55 | }
56 | }
57 |
58 | function compile(text) {
59 | console.log(text);
60 | convert(text);
61 | }
62 |
63 | var _SPACER = ' ';
64 | function makeIndentString(indent) {
65 | if(_SPACER.length /g,'>')
26 | }
27 |
28 | if(!opt.options.outdir || opt.argv.length == 0) {
29 | printUsageAndExit();
30 | }
31 |
32 | if(!fs.existsSync(opt.options.outdir)) {
33 | console.error(opt.options.outdir + " does not exist.");
34 | process.exit(1);
35 | }
36 |
37 | var inputFile = opt.argv[0];
38 | var outputFile = path.resolve(opt.options.outdir, path.basename(inputFile,'.spec') + '.html');
39 |
40 | var gaText='', adText='';
41 | try {
42 | gaText = fs.readFileSync(path.resolve(opt.options.outdir,'_ga.inc'),'utf-8');
43 | adText = fs.readFileSync(path.resolve(opt.options.outdir,'_ad.inc'),'utf-8');
44 | } catch(e) {
45 | }
46 |
47 | var text = fs.readFileSync(inputFile,'utf-8');
48 | var a;
49 | try {
50 | a = parser.parse(text);
51 | } catch(e) {
52 | console.log(e);
53 | console.log(ParserUtil.buildErrorMessage(e,text));
54 | process.exit(1);
55 | }
56 |
57 | var buf = [];
58 |
59 | buf.push(F2T(DOC_HEAD_TMPL));
60 | buf.push("" + a.title + " \n");
61 | buf.push(a.desc);
62 |
63 | for(var i=0;i" + spec.title + "\n");
67 | buf.push(spec.prefix);
68 | buf.push("Objective-C ");
69 | buf.push("" + escapeCode(spec.input) + " \n");
70 | buf.push(spec.midfix);
71 | buf.push("Swift ");
72 | buf.push("" + escapeCode(spec.expect) + " \n");
73 | buf.push(spec.suffix);
74 |
75 | var output;
76 | try {
77 | output = o2s.convert(spec.input,{quiet:true});
78 | } catch(e) {
79 | if(e.location) {
80 | output = ParserUtil.buildErrorMessage(e,spec.input);
81 | } else {
82 | output = e.message;
83 | }
84 | }
85 |
86 | if(spec.expect != output) {
87 |
88 | console.log("FAIL: '" + spec.title + "'' in " + inputFile);
89 |
90 | buf.push("Test Failed ");
91 | var diff = jsdiff.diffChars(spec.expect,output);
92 | buf.push("");
93 | buf.push("
");
94 | buf.push("
Diff ");
95 | buf.push("
Raw ");
96 | buf.push("
\n");
97 | buf.push("
\n")
98 | buf.push("
" + output + "\n");
99 | var code = [];
100 | diff.forEach(function(part){
101 | if(part.added) {
102 | code.push("
" + escapeCode(part.value) + " ");
103 | } else if(part.removed) {
104 | code.push("
" + escapeCode(part.value) + " ");
105 | } else {
106 | code.push(escapeCode(part.value));
107 | }
108 | });
109 | buf.push("
" + code.join('') + "\n");
110 | buf.push("
\n");
111 | buf.push("
");
112 | }
113 |
114 | buf.push("\n");
115 |
116 | }
117 |
118 | buf.push(F2T(DOC_FOOT_TMPL));
119 |
120 | var outText = buf.join('')
121 | .replace('', adText)
122 | .replace('', gaText);
123 |
124 | fs.writeFileSync(outputFile,outText,'utf-8');
125 |
126 | function F2T(f){return f.toString().replace(/^function.*?\n|\n?\*\/\}$/g,'');}
127 |
128 | function DOC_HEAD_TMPL(){/*
129 |
130 |
131 |
132 | Objc2Swift.js - Document
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
156 |
157 |
158 |
159 |
169 |
170 |
171 |
172 |
173 | */}
174 |
175 | function DOC_FOOT_TMPL(){/*
176 |
177 |
178 |
179 |
180 | */};
--------------------------------------------------------------------------------
/src/decl-info.js:
--------------------------------------------------------------------------------
1 | module.exports = (function(){
2 | "use strict"
3 |
4 | var util = require('util');
5 | var ASTUtil = require('./ast-util');
6 | var TypeInfo = require('./type-info');
7 |
8 | /**
9 | * DeclInfo is intended to be (de)serializable as pure JSON.
10 | * JS prototype chain is not used to define the DeclInfo and its derived classes.
11 | *
12 | * Thus do not use `obj instanceof DeclInfo`. Use `obj.kind == DeclInfo.KIND_XXXX` instead.
13 | */
14 | var DeclInfo = function(kind) {
15 | this.kind = kind;
16 | };
17 |
18 | DeclInfo.KIND_CLASS = "decl:class";
19 | DeclInfo.KIND_METHOD = "decl:method";
20 | DeclInfo.KIND_FUNC_PARAM = "decl:func_param";
21 | DeclInfo.KIND_METHOD_PARAM = "decl:method_param";
22 | DeclInfo.KIND_FUNC = "decl:func";
23 | DeclInfo.KIND_PROTOCOL = "decl:protocol";
24 | DeclInfo.KIND_DECLARATION = "decl:declaration";
25 | DeclInfo.KIND_TYPEDEF = "decl:typedef";
26 | DeclInfo.KIND_PROPERTY = "decl:property";
27 | DeclInfo.KIND_ENUM = "decl:enum";
28 | DeclInfo.KIND_ENUM_ITEM = "decl:enum_item";
29 |
30 | var _createObjectsFromEnumSpecifier = function(enumType, spec) {
31 | var result = [];
32 | for(var i=0;i
2 |
3 |
4 | Objc2Swift.js - Document
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
Statement
46 |
If Statement
47 |
Objective-C @implementation Foo
48 | -(NSString *)method {
49 | if(Condition) {
50 | return @"OK";
51 | } else {
52 | return @"NG";
53 | }
54 | }
55 | @end
56 |
57 |
Swift class Foo {
58 | func method() -> String! {
59 | if Condition {
60 | return "OK"
61 | } else {
62 | return "NG"
63 | }
64 | }
65 | }
66 |
67 |
68 |
Switch Statement
69 |
Objective-C @implementation Foo
70 | -(NSString *)method {
71 | switch(Condition) {
72 | case Apple:
73 | return @"apple";
74 | case Orange:
75 | return @"orange";
76 | default:
77 | return @"else";
78 | }
79 | }
80 | @end
81 |
82 |
Swift class Foo {
83 | func method() -> String! {
84 | switch(Condition) {
85 | case Apple:
86 | return "apple"
87 | case Orange:
88 | return "orange"
89 | default:
90 | return "else"
91 | }
92 | }
93 | }
94 |
95 |
96 |
Switch Statement (sequential cases)
97 |
Objective-C @implementation Foo
98 | -(NSString *)method {
99 | switch(Condition) {
100 | case Apple:
101 | case Orange:
102 | return @"fruit";
103 | default:
104 | return @"else";
105 | }
106 | }
107 | @end
108 |
109 |
Swift class Foo {
110 | func method() -> String! {
111 | switch(Condition) {
112 | case Apple,
113 | Orange:
114 | return "fruit"
115 | default:
116 | return "else"
117 | }
118 | }
119 | }
120 |
121 |
122 |
For Statement
123 |
Objective-C @implementation Foo
124 | -(void)method {
125 | for(int i=0;i<100;i++) {
126 | NSLog("%d",i);
127 | }
128 | }
129 | @end
130 |
131 |
Swift class Foo {
132 | func method() {
133 | for var i:Int=0 ; i<100 ; i++ {
134 | NSLog("%d",i)
135 | }
136 | }
137 | }
138 |
139 |
140 |
For-In Statement
141 |
Objective-C @implementation Foo
142 | -(void)method {
143 | for(int bar in Foo) {
144 | NSLog("%d",bar);
145 | }
146 | }
147 | @end
148 |
149 |
Swift class Foo {
150 | func method() {
151 | for bar:Int in Foo {
152 | NSLog("%d",bar)
153 | }
154 | }
155 | }
156 |
157 |
158 |
While Statement
159 |
Objective-C @implementation Foo
160 | -(void)method {
161 | while(flag) {
162 | // Do Something
163 | }
164 | }
165 | @end
166 |
167 |
Swift class Foo {
168 | func method() {
169 | while flag {
170 | // Do Something
171 | }
172 | }
173 | }
174 |
175 |
176 |
Do-White Statement
177 |
Objective-C @implementation Foo
178 | -(void)method {
179 | do {
180 | // Do Something
181 | } while(flag);
182 | }
183 | @end
184 |
185 |
Swift class Foo {
186 | func method() {
187 | repeat {
188 | // Do Something
189 | } while flag
190 | }
191 | }
192 |
193 |
194 |
Try-Catch Statment
195 |
Since Swift's try-catch statement has different semantics from Objective-C's, @try, @catch and @finally keywords are not be converted.
196 |
197 |
Objective-C @implementation Foo
198 | -(void)method {
199 | @try {
200 | // ...
201 | }
202 | @catch(Error *err) {
203 | // ...
204 | }
205 | @finally {
206 | // ...
207 | }
208 | }
209 | @end
210 |
211 |
Swift class Foo {
212 | func method() {
213 | @try {
214 | // ...
215 | }
216 | @catch(err:Error!) {
217 | // ...
218 | }
219 | @finally {
220 | // ...
221 | }
222 | }
223 | }
224 |
225 |
226 |
Jump and Labeled Statement
227 |
228 |
Objective-C @implementation Foo
229 | -(void)method {
230 | for(int i = 0; i < 100; i++) {
231 | if (i == 50) {
232 | goto bar;
233 | }
234 | }
235 | bar:
236 | return;
237 | }
238 | @end
239 |
240 |
Swift class Foo {
241 | func method() {
242 | for var i:Int=0 ; i < 100 ; i++ {
243 | if i == 50 {
244 | goto bar
245 | }
246 | }
247 | bar:
248 | return
249 | }
250 | }
251 |
252 |
253 |
254 |
258 |
264 |
265 |
266 |
--------------------------------------------------------------------------------
/bin/cmd.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | "use strict"
3 | function requireIfExist(m){try{return require(m);}catch(e){return null;}}
4 |
5 | var fs = require('fs-extra');
6 | var util = require('util');
7 | var path = require('path');
8 | var Getopt = require('node-getopt');
9 | var Objc2Swift = require('../src/main');
10 | var ImportResolver = require('../src/import-resolver');
11 | var Tracer = requireIfExist('pegjs-backtrace');
12 |
13 | if((process.platform != 'darwin') && (process.platform != 'linux')) {
14 | console.error("`" + process.platform + "` platform is not supported.");
15 | process.exit(1);
16 | }
17 |
18 | function getUserHome() {
19 | return (process.env.HOME||process.env.USERPROFILE);
20 | }
21 |
22 | function prependItems(items, newItems) {
23 | if(Array.isArray(newItems)) {
24 | newItems.reverse().forEach(function(e) {
25 | var idx = items.indexOf(e);
26 | if(0<=idx) {
27 | items.splice(idx,1);
28 | }
29 | items.unshift(e);
30 | });
31 | }
32 | }
33 |
34 | function resolveMetaPathString(pathList) {
35 | return pathList.map(function(e){
36 | return e.replace(/\$\{Xcode\.app\}/i,xcode)
37 | .replace("~/",getUserHome()+"/");
38 | });
39 | }
40 |
41 | function loadConfig(config, file, skipIfNotExist) {
42 | try {
43 | if(!fs.existsSync(file)) {
44 | if(skipIfNotExist) return true;
45 | throw new Error("File Not Found: " + file);
46 | }
47 | var json = fs.readFileSync(file,'utf-8');
48 | var data = JSON.parse(json);
49 |
50 | if(Array.isArray(data.systemIncludePaths)) {
51 | prependItems(config.systemIncludePaths, data.systemIncludePaths);
52 | } else {
53 | throw new Error("systemIncludePaths is not defined.");
54 | }
55 | if(Array.isArray(data.includePaths)) {
56 | prependItems(config.includePaths, data.includePaths);
57 | } else {
58 | throw new Error("includePaths is not defined.");
59 | }
60 | } catch(e) {
61 | console.error('Failed to load: ' + file);
62 | console.error(e);
63 | process.exit(1);
64 | }
65 | }
66 |
67 | var optdefs = [
68 | ['o','output=FILE','specifiy output filename.'],
69 | ['','init=ios|osx|tvos|watchos|none','Create config for the specified platform.'],
70 | ['','xcode=PATH','Specify Xcode.app path. [default:/Applications/Xcode.app]'],
71 | ['c','config=PATH','Load config from PATH. [default:~/.objc2swift/config]'],
72 | ['h','help','show this help.'],
73 | ['','isystem=PATH+','Add PATH to system include search path.'],
74 | ['I','include=PATH+','Add PATH to include search path.'],
75 | ['','optional-chaining','Use optional-chaining instead of forced-unwrapping.'],
76 | ['','skip-header','Skip to import all headers.'],
77 | ['','show-missing-header','Print out missing header file names.'],
78 | ['','show-include-path','Print out include path settings.'],
79 | ['','verbose','Show verbose messages.'],
80 | ['','quiet','Suppress trace messages.'],
81 | ['','no-cache','Do not generate pre-compiled header cache.'],
82 | ['','clear-cache','Clear the cached pre-compiled headers.'],
83 | ['S','preprocess-only','Only run preprocess step.'],
84 | ['','parse-only','Do not generate swift source.'],
85 | ['','stacktrace','Print out JS stack trace when converter fails.'],
86 | ['','inline-dump','Generate type analysis information as comment to Swift source inline.'],
87 | ['q','query=CLASS','Print out type information of the specified class to console.'],
88 | ['a','ast','Print out the internal syntax tree to console.'],
89 | ];
90 |
91 | if(Tracer) {
92 | if(Objc2Swift.canUseTracer()) {
93 | optdefs = optdefs.concat([
94 | ['b','backtrace','dump back trace after parse.'],
95 | ['t','tree','dump full parse tree.'],
96 | ['','trace','show parser trace.'],
97 | ]);
98 | }
99 | }
100 |
101 | var getopt = new Getopt(optdefs).bindHelp();
102 | getopt.setHelp("\nUsage: objc2swift [OPTION] file\n\nOptions:\n[[OPTIONS]]\n");
103 | var opt = getopt.parseSystem();
104 |
105 | function printUsageAndExit() {
106 | getopt.showHelp();
107 | process.exit(0);
108 | }
109 |
110 | //
111 | // Prepare Cache
112 | //
113 | if(opt.options['clear-cache']) {
114 | var dir = ImportResolver.getCacheRoot();
115 | if(!/\.objc2swift/.test(dir)) {
116 | console.error("Invalid cache directory: " + dir);
117 | process.exit(1);
118 | }
119 | fs.removeSync(dir);
120 | console.log("Removed: " + dir);
121 | if(opt.argv.length==0) {
122 | process.exit(0);
123 | }
124 | }
125 | fs.mkdirsSync(ImportResolver.getCacheDir());
126 |
127 | //
128 | // Load or Setup configuration
129 | //
130 | var xcode = opt.options.xcode||"/Applications/Xcode.app";
131 | var configFile = opt.options.config || getUserHome() + "/.objc2swift/config.json";
132 | var config;
133 |
134 | function makeConfig(platform) {
135 | var config = {
136 | systemIncludePaths:[],
137 | includePaths:[],
138 | };
139 | var systemPathsMap = JSON.parse(fs.readFileSync(__dirname+"/sdk-path.json",'utf-8'));
140 | prependItems(config.systemIncludePaths, systemPathsMap[platform]);
141 | return config;
142 | }
143 |
144 | function addIncludePaths(config, systemIncludePaths, includePaths) {
145 | if(systemIncludePaths) {
146 | prependItems(config.systemIncludePaths, systemIncludePaths);
147 | }
148 | if(includePaths) {
149 | prependItems(config.includePaths, includePaths);
150 | }
151 | }
152 |
153 | if(opt.options.init) {
154 | config = makeConfig(opt.options.init);
155 | addIncludePaths(config, opt.options.isystem, opt.options.include);
156 | fs.writeFileSync(configFile, JSON.stringify(config,null," "), 'utf-8');
157 | console.log("Generated: " + configFile);
158 | process.exit(0);
159 | }
160 |
161 | if(fs.existsSync(configFile)) {
162 | config = makeConfig("none");
163 | loadConfig(config, configFile, true);
164 | addIncludePaths(config, opt.options.isystem, opt.options.include);
165 | } else {
166 | console.log("Warning: " + configFile + " does not exist. The default `ios` platform configuration is applied.\n`--init` option can be used to generate the config file.");
167 | config = makeConfig("ios");
168 | addIncludePaths(config, opt.options.isystem, opt.options.include);
169 | }
170 |
171 | if(opt.argv.length!=0) {
172 | config.includePaths.unshift(path.dirname(path.resolve(opt.argv[0])));
173 | }
174 |
175 | if(opt.options['show-include-path']) {
176 | resolveMetaPathString(config.systemIncludePaths).forEach(function(e){console.log("[System] " + e);});
177 | resolveMetaPathString(config.includePaths).forEach(function(e){console.log(e);});
178 | if(opt.argv.length == 0) process.exit(0);
179 | }
180 |
181 | if(opt.argv.length == 0) {
182 | printUsageAndExit();
183 | }
184 |
185 | //
186 | // Setup options
187 | //
188 | var options = {
189 | ast:opt.options['ast'],
190 | skipHeader:opt.options['skip-header'],
191 | forceUnwrap:!opt.options['optional-chaining'],
192 | inlineDump:opt.options['inline-dump'],
193 | query:opt.options['query'],
194 | verbose:opt.options.verbose,
195 | sourcePath:path.resolve(opt.argv[0]),
196 | cacheHeader:!opt.options['no-cache'],
197 | platform:opt.options.platform,
198 | quiet:opt.options.quiet,
199 | showMissingHeader:opt.options['show-missing-header'],
200 | };
201 |
202 | options.importResolver = new ImportResolver({
203 | frameworkPath:resolveMetaPathString(config.systemIncludePaths),
204 | includePath:resolveMetaPathString(config.includePaths),
205 | verbose:options.verbose
206 | });
207 |
208 | //
209 | // Load source file
210 | //
211 | var source;
212 | try {
213 | source = fs.readFileSync(options.sourcePath,'utf-8');
214 | } catch(e) {
215 | console.error(e.message);
216 | process.exit(1);
217 | }
218 |
219 | //
220 | // Setup tracer if available
221 | //
222 | var tracer;
223 | if(opt.options.tree||opt.options.backtrace||opt.options.trace) {
224 | tracer = new Tracer(source,{hiddenPaths:['Identifier/.*','__','.*Token'],verbose:true, showTrace:opt.options.trace||false});
225 | options.tracer = tracer;
226 | } else {
227 | options.tracer = {trace:function(){}};
228 | }
229 |
230 | //
231 | // Parse source
232 | //
233 | var o2s = new Objc2Swift();
234 |
235 | if(opt.options['preprocess-only']) {
236 | console.log(o2s.preprocessOnly(source));
237 | process.exit(0);
238 | }
239 |
240 | var ast;
241 | try {
242 | ast = o2s.parse(source, options);
243 | if(opt.options.tree) {
244 | console.log(tracer.getParseTreeString());
245 | }
246 | } catch(e) {
247 | console.log(o2s.buildErrorMessage(e, source, opt.argv[0]));
248 | if(opt.options.stacktrace) { console.log(e.stack); }
249 | if(opt.options.backtrace) { console.log(tracer.getBacktraceString()); }
250 | process.exit(1);
251 | }
252 |
253 | if(opt.options["parse-only"]||opt.options['query']||opt.options['ast']) {
254 | process.exit(0);
255 | }
256 |
257 | //
258 | // Generate Swift code
259 | //
260 |
261 | var swift = o2s.generate(ast, options);
262 |
263 | var outfile = opt.options['output'] ||
264 | path.basename(options.sourcePath).replace(/\.m$/,'') + ".swift";
265 |
266 | if(outfile == '-' || outfile == '/dev/stdout') {
267 | console.info(swift);
268 | } else if(outfile == '/dev/stderr') {
269 | console.error(swift);
270 | } else {
271 | fs.writeFileSync(outfile,swift,'utf-8');
272 | if(!opt.options.quiet) {
273 | console.error("Output : " + outfile);
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/src/preprocessor.js:
--------------------------------------------------------------------------------
1 | module.exports = (function() {
2 | "use strict"
3 |
4 | var cpp = require('./preprocess-parser');
5 | var parser = require('./parser');
6 | var TypeAnalyzer = require('./type-analyzer');
7 | var DeclInfo = require('./decl-info');
8 | var ParserUtil = require('./parser-util');
9 |
10 | var MACRO_MAP = {
11 | "CA_EXTERN":"extern",
12 | "CF_AVAILABLE":"",
13 | "CF_BRIDGED_MUTABLE_TYPE":"",
14 | "CF_BRIDGED_TYPE":"",
15 | "CF_CONSUMED":"",
16 | "CF_ENUM_AVAILABLE":"",
17 | "CF_EXPORT":"",
18 | "CF_EXTERN_C_BEGIN":"",
19 | "CF_EXTERN_C_END":"",
20 | "CF_RETURNS_NOT_RETAINED":"",
21 | "FOUNDATION_EXPORT":"",
22 | "NS_AUTOMATED_REFCOUNT_UNAVAILABLE":"",
23 | "NS_AVAILABLE":"",
24 | "NS_AVAILABLE_IOS":"",
25 | "NS_AVAILABLE_MAC":"",
26 | "NS_CALENDAR_DEPRECATED_MAC":"",
27 | "NS_CLASS_AVAILABLE":"",
28 | "NS_CLASS_AVAILABLE_IOS":"",
29 | "NS_CLASS_DEPRECATED":"",
30 | "NS_CLASS_DEPRECATED_IOS":"",
31 | "NS_DEPRECATED":"",
32 | "NS_DEPRECATED_IOS":"",
33 | "NS_DEPRECATED_MAC":"",
34 | "NS_DESIGNATED_INITIALIZER":"__attribute__((objc_designated_initializer))",
35 | "NS_ENUM_AVAILABLE":"",
36 | "NS_ENUM_AVAILABLE_IOS":"",
37 | "NS_ENUM_DEPRECATED":"",
38 | "NS_ENUM_DEPRECATED_IOS":"",
39 | "NS_EXTENSION_UNAVAILABLE_IOS":"",
40 | "NS_FORMAT_ARGUMENT":"",
41 | "NS_FORMAT_FUNCTION":"",
42 | "NS_INLINE":"",
43 | "NS_REPLACES_RECEIVER":"",
44 | "NS_REQUIRES_NIL_TERMINATION":"",
45 | "NS_REQUIRES_PROPERTY_DEFINITIONS":"", // NSManagedObject.h
46 | "NS_REQUIRES_SUPER":"",
47 | "NS_RETURNS_INNER_POINTER":"",
48 | "NS_SWIFT_UNAVAILABLE":"",
49 | "NS_UNAVAILABLE":"",
50 | "OBJC_ARC_UNAVAILABLE":"",
51 | "OBJC_EXPORT":"",
52 | "OBJC_ISA_AVAILABILITY":"",
53 | "OBJC_ROOT_CLASS":"",
54 | "OBJC_SWIFT_UNAVAILABLE":"",
55 | "UIKIT_AVAILABLE_TVOS_ONLY":"",
56 | "UIKIT_CLASS_AVAILABLE_IOS_TVOS":"",
57 | "UI_APPEARANCE_SELECTOR":"",
58 | "UNAVAILABLE_ATTRIBUTE":"",
59 | "_MAC":"",
60 | "_STRUCT_SIGALTSTACK":"struct __darwin_sigaltstack", //_sigaltstack.h
61 | "_STRUCT_TIMESPEC":"struct timespec", // _timespec.h
62 | "_STRUCT_TIMEVAL":"struct timeval", // _timeval.h
63 | "_STRUCT_UCONTEXT":"struct __darwin_ucontext", // _ucontext.h
64 | "__BEGIN_DECLS":"",
65 | "__DARWIN_1050":"",
66 | "__DARWIN_ALIAS":"",
67 | "__DARWIN_ALIAS_C":"",
68 | "__DARWIN_ALIAS_STARTING":"",
69 | "__DARWIN_EXTSN":"",
70 | "__END_DECLS":"",
71 | "__OSX_AVAILABLE_BUT_DEPRECATED":"",
72 | "__OSX_AVAILABLE_BUT_DEPRECATED_MSG":"",
73 | "__OSX_AVAILABLE_STARTING":"",
74 | "__POSIX_C_DEPRECATED":"", // strings.h
75 | "__TVOS_PROHIBITED":"",
76 | "__WATCHOS_PROHIBITED":"",
77 | "__const":"const",
78 | "__dead":"",
79 | "__dead2":"",
80 | "__deprecated":"__attribute__((deprecated))",
81 | "__deprecated_msg":"", // _stdio.h
82 | "__printflike":"", // _stdio.h
83 | "__pure2":"",
84 | "__restrict":"restrict",
85 | "__scanflike":"", // _stdio.h
86 | "__strftimelike":"", // _time.h
87 | "__unavailable":"__attribute__((unavailable))",
88 | "__unused":"__attribute__((deprecated))",
89 | "__used":"__attribute__((used))",
90 | "NS_DURING":"@try {",
91 | "NS_HANDLER":"} @catch (NSException * localException) {",
92 | "NS_ENDHANDLER":"}",
93 |
94 | };
95 |
96 | function expand_NS_ENUM(type,name) {
97 | if(name) {
98 | // NS_ENUM(T,N) := enum N:T N;enum N:T
99 | return "enum " + name + ":" + type + " " + name + ";enum " + name + ":" + type;
100 | } else {
101 | // NS_ENUM(T) := enum : T;
102 | return "enum : " + type;
103 | }
104 | };
105 |
106 | function expand(name, argv) {
107 | if(name == "NS_ENUM" || name == "NS_OPTIONS" || name == "CF_ENUM" || name == "CF_OPTIONS") {
108 | return expand_NS_ENUM(argv[0],argv[4]);
109 | }
110 | return MACRO_MAP[name];
111 | };
112 |
113 | function normalizeSource(source) {
114 | return source.replace(/\r\n/g,'\n');
115 | }
116 |
117 | var PreProcessor = function(options) {
118 | this.options = options||{};
119 | this.importResolver = this.options.importResolver;
120 | this.headerInfoMap = {};
121 | };
122 |
123 | PreProcessor.prototype.enumerateHeaders = function(source, from) {
124 | var exp = /^ *# *(?:import|include)\s*([<"])(.*?)[>"]/gm, m, result = [];
125 | while(m = exp.exec(source)) {
126 | var file = this.importResolver.findHeaderPath(m[2],m[1]=="<");
127 | if(file) {
128 | result.push(file)
129 | } else {
130 | if(this.options.showMissingHeader) {
131 | console.log("[debug] " + m[2] + " is not found" + (from?(" from "+from):"."));
132 | }
133 | }
134 | }
135 | return result;
136 | };
137 |
138 | var addDeclInfo = function(headerInfo, declInfo) {
139 | if(declInfo.kind != DeclInfo.KIND_PROTOCOL) {
140 | headerInfo.declInfoMap[declInfo.name] = declInfo;
141 | } else {
142 | headerInfo.declInfoMap["@protocol " + declInfo.name] = declInfo;
143 | }
144 | };
145 |
146 | PreProcessor.prototype.printStatus = function(name, value) {
147 | if(!this.options.quiet) {
148 | if(this.options.verbose) {
149 | process.stderr.write("\x1b[K");
150 | console.log(name + (value?" : " + value:""));
151 | } else {
152 | process.stderr.write("\x1b[K" + name + (value?" : " + value:"") + "\x1b[G");
153 | }
154 | }
155 | };
156 |
157 | PreProcessor.prototype.processHeader = function(headerPath) {
158 |
159 | var headerName = headerPath.replace(/^.*\/(.*)$/,'$1');
160 |
161 | var headerInfo = this.headerInfoMap[headerPath];
162 | if(headerInfo) return headerInfo;
163 |
164 | headerInfo = {
165 | path:headerPath,
166 | name:headerName,
167 | declInfoMap:{},
168 | children:[],
169 | };
170 | this.headerInfoMap[headerPath] = headerInfo;
171 |
172 | this.printStatus(headerName);
173 |
174 | var source = this.importResolver.loadFile(headerPath);
175 | source = normalizeSource(source);
176 |
177 | var targets = this.enumerateHeaders(source, headerPath);
178 |
179 | for(var i=0;i