25bd84fa93b98c7c9358b1bed461f9334c4ceb82
[nit.git] / contrib / objcwrapper / src / objc_generator.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Code generation
16 module objc_generator
17
18 import opts
19
20 import objc_model
21
22 redef class Sys
23 # Path to the output file
24 var opt_output = new OptionString("Output file", "-o")
25 end
26
27 class CodeGenerator
28 # Merge the calls to `alloc` and `init...` in a single constructor?
29 #
30 # If `true`, also the default behavior, initializing an extern Objective-C object looks like:
31 # ~~~nitish
32 # var o = new NSArray.init_with_array(some_other_array)
33 # ~~~
34 #
35 # If `false`, the object must first be allocated and then initialized.
36 # This is closer to the Objective-C behavior:
37 # ~~~nitish
38 # var o = new NSArray
39 # o.init_with_array(some_other_array)
40 # ~~~
41 var init_with_alloc = true is writable
42
43 # Generate Nit code to wrap `classes`
44 fun generate(classes: Array[ObjcClass])
45 do
46 # Open specified path or stdin
47 var file
48 var path = opt_output.value
49 if path != null then
50 if path.file_extension != "nit" then
51 print_error "Warning: output file path does not end with '.nit'"
52 end
53
54 file = new FileWriter.open(path)
55 else
56 file = stdout
57 end
58
59 # Generate code
60 file.write "import cocoa::foundation\n\n"
61 for classe in classes do
62 write_class(classe, file)
63 end
64
65 if path != null then file.close
66 end
67
68 private fun type_convertor(type_word: String): String
69 do
70 var types = new HashMap[String, String]
71 types["char"] = "Byte"
72 types["short"] = "Int"
73 types["short int"] = "Int"
74 types["int"] = "Int"
75 types["long"] = "Int"
76 types["long int"] = "Int"
77 types["long long"] = "Int"
78 types["long long int"] = "Int"
79 types["float"] = "Float"
80 types["double"] = "Float"
81 types["long double"] = "Float"
82
83 types["NSUInteger"] = "Int"
84 types["BOOL"] = "Bool"
85 types["id"] = "NSObject"
86
87 if types.has_key(type_word) then
88 return types[type_word]
89 else
90 return type_word
91 end
92 end
93
94 private fun write_class(classe: ObjcClass, file: Writer)
95 do
96 var commented_methods = new Array[ObjcMethod]
97 file.write "extern class " + classe.name + """ in "ObjC" `{ """ + classe.name + """ * `}\n"""
98 for super_name in classe.super_names do
99 file.write """ super """ + super_name + "\n"
100 end
101 if classe.super_names.is_empty then file.write """ super NSObject\n"""
102 write_constructor(classe, file)
103 file.write "\n"
104 for attribute in classe.attributes do
105 write_attribute(attribute, file)
106 end
107 for method in classe.methods do
108 if method.is_commented then
109 commented_methods.add(method)
110 else
111 if init_with_alloc and method.params.first.name.has("init") then continue
112 file.write """ """
113 write_method(method, file)
114 file.write """ in "ObjC" `{\n """
115 write_objc_method_call(method, file)
116 file.write """ `}"""
117 if method != classe.methods.last then file.write "\n\n"
118 end
119 end
120 for commented_method in commented_methods do
121 if commented_method == commented_methods.first then file.write "\n"
122 file.write """ #"""
123 write_method(commented_method, file)
124 if commented_method != commented_methods.last then file.write "\n"
125 end
126 file.write "\nend\n"
127 end
128
129 private fun write_constructor(classe: ObjcClass, file: Writer)
130 do
131 if init_with_alloc then
132 for method in classe.methods do
133 if method.params.first.name.has("init") and not method.is_commented then
134 file.write """\n """
135 if method.params.first.name == "init" then
136 file.write "new"
137 else
138 write_method(method, file)
139 end
140 file.write """ in "ObjC" `{\n"""
141 write_objc_init_call(classe.name, method, file)
142 file.write """ `}\n"""
143 end
144 end
145 else
146 file.write """\n new in "ObjC"`{\n"""
147 file.write """ return [""" + classe.name + " alloc];\n"
148 file.write """ `}\n"""
149 end
150 end
151
152 private fun write_attribute(attribute: ObjcAttribute, file: Writer)
153 do
154 write_attribute_getter(attribute, file)
155 # TODO write_attribute_setter if there is no `readonly` annotation
156 file.write "\n"
157 end
158
159 private fun write_attribute_getter(attribute: ObjcAttribute, file: Writer)
160 do
161 file.write """ fun """ + attribute.name.to_snake_case + ": " + type_convertor(attribute.return_type)
162 file.write """ in "ObjC" `{\n"""
163 file.write """ return [self """ + attribute.name + "];\n"
164 file.write """ `}\n"""
165 end
166
167 private fun write_attribute_setter(attribute: ObjcAttribute, file: Writer)
168 do
169 file.write """ fun """ + attribute.name.to_snake_case + "=(value: " + type_convertor(attribute.return_type) + ")"
170 file.write " in \"ObjC\" `\{\n"
171 file.write """ self.""" + attribute.name + " = value;\n"
172 file.write """ `}\n"""
173 end
174
175 private fun write_method(method: ObjcMethod, file: Writer)
176 do
177 var name = ""
178 for param in method.params do
179 name += param.name[0].to_upper.to_s + param.name.substring_from(1)
180 name = name.to_snake_case
181 end
182 if name.has("init") and init_with_alloc then
183 file.write "new "
184 else
185 if not init_with_alloc and name == "init" then name = "init_0"
186 file.write "fun "
187 end
188 file.write name
189 for param in method.params do
190 if param == method.params.first and not param.is_single then
191 file.write "(" + param.variable_name + ": " + type_convertor(param.return_type)
192 end
193 if param != method.params.first and not param.is_single then
194 file.write ", " + param.variable_name + ": " + type_convertor(param.return_type)
195 end
196 if param == method.params.last and not param.is_single then
197 file.write ")"
198 end
199 end
200 if method.return_type != "void" and not method.params.first.name.has("init") then
201 file.write ": " + type_convertor(method.return_type)
202 end
203 end
204
205 private fun write_objc_init_call(classe_name: String, method: ObjcMethod, file: Writer)
206 do
207 file.write """ return [[""" + classe_name + " alloc] "
208 for param in method.params do
209 if not param.is_single then
210 file.write param.name + ":" + param.variable_name
211 if not param == method.params.last then file.write " "
212 else
213 file.write param.name
214 end
215 end
216 file.write "];\n"
217 end
218
219 private fun write_objc_method_call(method: ObjcMethod, file: Writer)
220 do
221 if method.return_type != "void" then file.write "return "
222 file.write "[self "
223 for param in method.params do
224 if not param.is_single then
225 file.write param.name + ":" + param.variable_name
226 if not param == method.params.last then file.write " "
227 else
228 file.write param.name
229 end
230 end
231 file.write "];\n"
232 end
233 end