contrib/objcwrapper: add header to generated files with the command in use
[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
24 # Path to the output file
25 var opt_output = new OptionString("Output file", "-o")
26
27 # Shall `init` methods/constructors be wrapped as methods?
28 #
29 # By default, these methods/constructors are wrapped as extern constructors.
30 # So 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 this option is set, 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 opt_init_as_methods = new OptionBool(
42 "Wrap `init...` constructors as Nit methods instead of Nit constructors",
43 "--init-as-methods")
44
45 private var objc_to_nit_types: Map[String, String] is lazy do
46 var types = new HashMap[String, String]
47 types["char"] = "Byte"
48 types["short"] = "Int"
49 types["short int"] = "Int"
50 types["int"] = "Int"
51 types["long"] = "Int"
52 types["long int"] = "Int"
53 types["long long"] = "Int"
54 types["long long int"] = "Int"
55 types["float"] = "Float"
56 types["double"] = "Float"
57 types["long double"] = "Float"
58
59 types["NSUInteger"] = "Int"
60 types["BOOL"] = "Bool"
61 types["id"] = "NSObject"
62 types["constid"] = "NSObject"
63 types["SEL"] = "NSObject"
64 types["void"] = "Pointer"
65
66 return types
67 end
68 end
69
70 redef class ObjcModel
71 redef fun knows_type(objc_type) do return super or
72 objc_to_nit_types.keys.has(objc_type)
73 end
74
75 # Wrapper generator
76 class CodeGenerator
77
78 # `ObjcModel` to wrap
79 var model: ObjcModel
80
81 # Generate Nit code to wrap `classes`
82 fun generate
83 do
84 var classes = model.classes
85
86 # Open specified path or stdin
87 var file
88 var path = opt_output.value
89 if path != null then
90 if path.file_extension != "nit" then
91 print_error "Warning: output file path does not end with '.nit'"
92 end
93
94 file = new FileWriter.open(path)
95 else
96 file = stdout
97 end
98
99 # Generate code
100 file.write """
101 # File generated by objcwrapper with the following command:
102 # {{{program_name}}} {{{args.join(" ")}}}
103
104 """
105
106 file.write "import cocoa::foundation\n"
107 for classe in classes do
108 write_class(classe, file)
109 end
110
111 if path != null then file.close
112 end
113
114 private fun write_class(classe: ObjcClass, file: Writer)
115 do
116 # Class header
117 file.write """
118
119 extern class {{{classe.name}}} in "ObjC" `{ {{{classe.name}}} * `}
120 """
121
122 # Supers
123 for super_name in classe.super_names do file.write """
124 super {{{super_name}}}
125 """
126 if classe.super_names.is_empty then file.write """
127 super NSObject
128 """
129
130 # Constructor or constructors
131 write_constructors(classe, file)
132
133 # Attributes
134 for attribute in classe.attributes do
135 write_attribute(attribute, file)
136 end
137
138 # Instance methods '-'
139 for method in classe.methods do
140 if not model.knows_all_types(method) then method.is_commented = true
141
142 if not opt_init_as_methods.value and method.is_init then continue
143 if method.is_class_property then continue
144
145 write_method_signature(method, file)
146 write_objc_method_call(method, file)
147 end
148
149 file.write """
150 end
151 """
152
153 # Class methods '+'
154 for method in classe.methods do
155 if not method.is_class_property then continue
156
157 write_method_signature(method, file)
158 write_objc_method_call(method, file)
159 end
160 end
161
162 private fun write_constructors(classe: ObjcClass, file: Writer)
163 do
164 if opt_init_as_methods.value then
165 # A single constructor for `alloc`
166 file.write """
167
168 new in "ObjC" `{
169 return [{{{classe.name}}} alloc];
170 `}
171 """
172 return
173 end
174
175 # A constructor per `init...` method
176 for method in classe.methods do
177 if not method.is_init then continue
178
179 if not model.knows_all_types(method) then method.is_commented = true
180
181 write_method_signature(method, file)
182
183 write_objc_init_call(classe.name, method, file)
184 end
185 end
186
187 private fun write_attribute(attribute: ObjcAttribute, file: Writer)
188 do
189 if not model.knows_type(attribute.return_type) then attribute.is_commented = true
190
191 write_attribute_getter(attribute, file)
192 # TODO write_attribute_setter if there is no `readonly` annotation
193 end
194
195 private fun write_attribute_getter(attribute: ObjcAttribute, file: Writer)
196 do
197 var nit_attr_name = attribute.name.to_snake_case
198 var nit_attr_type = attribute.return_type.objc_to_nit_type
199
200 var c = attribute.comment_str
201
202 file.write """
203
204 {{{attribute.doc}}}
205 {{{c}}} fun {{{nit_attr_name}}}: {{{nit_attr_type}}} in "ObjC" `{
206 {{{c}}} return [self {{{attribute.name}}}];
207 {{{c}}} `}
208 """
209 end
210
211 private fun write_attribute_setter(attribute: ObjcAttribute, file: Writer)
212 do
213 var nit_attr_name = attribute.name.to_snake_case
214 var nit_attr_type = attribute.return_type.objc_to_nit_type
215
216 var c = attribute.comment_str
217
218 file.write """
219
220 {{{attribute.doc}}}
221 {{{c}}} fun {{{nit_attr_name}}}=(value: {{{nit_attr_type}}}) in "ObjC" `{
222 {{{c}}} return self.{{{attribute.name}}} = value;
223 {{{c}}} `}
224 """
225 end
226
227 private fun write_method_signature(method: ObjcMethod, file: Writer)
228 do
229 var c = method.comment_str
230
231 # Build Nit method name
232 var name = ""
233 for param in method.params do
234 name += param.name[0].to_upper.to_s + param.name.substring_from(1)
235 end
236 name = name.to_snake_case
237
238 if name == "init" then name = ""
239
240 # If class method, prefix with class name
241 if method.is_class_property then name = "{method.objc_class.name.to_snake_case}_{name}"
242
243 # Kind of method
244 var fun_keyword = "fun"
245 if not opt_init_as_methods.value and method.is_init then
246 fun_keyword = "new"
247 end
248
249 # Params
250 var params = new Array[String]
251 for param in method.params do
252 if param.is_single then break
253 params.add "{param.variable_name}: {param.return_type.objc_to_nit_type}"
254 end
255
256 var params_with_par = ""
257 if params.not_empty then params_with_par = "({params.join(", ")})"
258
259 # Return
260 var ret = ""
261 if method.return_type != "void" and fun_keyword != "new" then
262 ret = ": {method.return_type.objc_to_nit_type}"
263 end
264
265 file.write """
266
267 {{{method.doc}}}
268 {{{c}}}{{{fun_keyword}}} {{{name}}}{{{params_with_par}}}{{{ret}}} in "ObjC" `{
269 """
270 end
271
272 # Write a combined call to alloc and to a constructor/method
273 private fun write_objc_init_call(class_name: String, method: ObjcMethod, file: Writer)
274 do
275 # Method name and other params
276 var params = new Array[String]
277 for param in method.params do
278 if not param.is_single then
279 params.add "{param.name}: {param.variable_name}"
280 else params.add param.name
281 end
282
283 var c = method.comment_str
284
285 file.write """
286 {{{c}}} return [[{{{class_name}}} alloc] {{{params.join(" ")}}}];
287 {{{c}}}`}
288 """
289 end
290
291 private fun write_objc_method_call(method: ObjcMethod, file: Writer)
292 do
293 # Is there a value to return?
294 var ret = ""
295 if method.return_type != "void" then ret = "return "
296
297 # Method name and other params
298 var params = new Array[String]
299 for param in method.params do
300 if not param.is_single then
301 params.add "{param.name}: {param.variable_name}"
302 else params.add param.name
303 end
304
305 # Receiver, instance or class
306 var recv = "self"
307 if method.is_class_property then recv = method.objc_class.name
308
309 var c = method.comment_str
310
311 file.write """
312 {{{c}}} {{{ret}}}[{{{recv}}} {{{params.join(" ")}}}];
313 {{{c}}}`}
314 """
315 end
316 end
317
318 redef class Text
319 # Nit equivalent to this type
320 private fun objc_to_nit_type: String
321 do
322 var types = sys.objc_to_nit_types
323
324 if types.has_key(self) then
325 return types[self]
326 else
327 return to_s
328 end
329 end
330 end
331
332 redef class ObjcProperty
333 private fun comment_str: String do if is_commented then
334 return "#"
335 else return ""
336
337 # Full documentation to be generated for the Nit code
338 private fun doc: String is abstract
339 end
340
341 redef class ObjcMethod
342 private fun indent: String do return if is_class_property then "" else "\t"
343
344 redef fun comment_str do return indent + super
345
346 redef fun doc
347 do
348 var recv = if is_class_property then objc_class.name else "self"
349 return "{indent}# Wraps: `[{recv} {params.join(" ")}]`"
350 end
351 end
352
353 redef class ObjcAttribute
354 redef fun doc do return "\t# Wraps: `{objc_class.name}.{name}`"
355 end