6ca50a2c94ca83dc70c1aeacf05a8d0b26a62d0c
[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 nit_to_java_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 return types
63 end
64 end
65
66 # Wrapper generator
67 class CodeGenerator
68
69 # `ObjcModel` to wrap
70 var model: ObjcModel
71
72 # Generate Nit code to wrap `classes`
73 fun generate
74 do
75 var classes = model.classes
76
77 # Open specified path or stdin
78 var file
79 var path = opt_output.value
80 if path != null then
81 if path.file_extension != "nit" then
82 print_error "Warning: output file path does not end with '.nit'"
83 end
84
85 file = new FileWriter.open(path)
86 else
87 file = stdout
88 end
89
90 # Generate code
91 file.write "import cocoa::foundation\n\n"
92 for classe in classes do
93 write_class(classe, file)
94 end
95
96 if path != null then file.close
97 end
98
99 private fun write_class(classe: ObjcClass, file: Writer)
100 do
101 # Class header
102 file.write """
103
104 extern class {{{classe.name}}} in "ObjC" `{ {{{classe.name}}} * `}
105 """
106
107 # Supers
108 for super_name in classe.super_names do file.write """
109 super {{{super_name}}}
110 """
111 if classe.super_names.is_empty then file.write """
112 super NSObject
113 """
114
115 file.write "\n"
116
117 # Constructor or constructors
118 write_constructors(classe, file)
119
120 # Attributes
121 for attribute in classe.attributes do
122 write_attribute(attribute, file)
123 end
124
125 # Methods
126 for method in classe.methods do
127
128 if not opt_init_as_methods.value and method.is_init then continue
129
130 write_method_signature(method, file)
131 write_objc_method_call(method, file)
132 end
133
134 file.write """
135 end
136 """
137 end
138
139 private fun write_constructors(classe: ObjcClass, file: Writer)
140 do
141 if opt_init_as_methods.value then
142 # A single constructor for `alloc`
143 file.write """
144 new in "ObjC" `{
145 return [{{{classe.name}}} alloc];
146 `}
147
148 """
149 return
150 end
151
152 # A constructor per `init...` method
153 for method in classe.methods do
154 if not method.is_init then continue
155
156 write_method_signature(method, file)
157
158 write_objc_init_call(classe.name, method, file)
159 end
160 end
161
162 private fun write_attribute(attribute: ObjcAttribute, file: Writer)
163 do
164 write_attribute_getter(attribute, file)
165 # TODO write_attribute_setter if there is no `readonly` annotation
166 end
167
168 private fun write_attribute_getter(attribute: ObjcAttribute, file: Writer)
169 do
170 var nit_attr_name = attribute.name.to_snake_case
171 var nit_attr_type = attribute.return_type.to_nit_type
172
173 var c = attribute.comment_str
174
175 file.write """
176 {{{c}}} fun {{{nit_attr_name}}}: {{{nit_attr_type}}} in "ObjC" `{
177 {{{c}}} return [self {{{attribute.name}}}];
178 {{{c}}} `}
179
180 """
181 end
182
183 private fun write_attribute_setter(attribute: ObjcAttribute, file: Writer)
184 do
185 var nit_attr_name = attribute.name.to_snake_case
186 var nit_attr_type = attribute.return_type.to_nit_type
187
188 var c = attribute.comment_str
189
190 file.write """
191 {{{c}}} fun {{{nit_attr_name}}}=(value: {{{nit_attr_type}}}) in "ObjC" `{
192 {{{c}}} return self.{{{attribute.name}}} = value;
193 {{{c}}} `}
194
195 """
196 end
197
198 private fun write_method_signature(method: ObjcMethod, file: Writer)
199 do
200 var c = method.comment_str
201
202 # Build Nit method name
203 var name = ""
204 for param in method.params do
205 name += param.name[0].to_upper.to_s + param.name.substring_from(1)
206 end
207 name = name.to_snake_case
208
209 if name == "init" then name = ""
210
211 # Kind of method
212 var fun_keyword = "fun"
213 if not opt_init_as_methods.value and method.is_init then
214 fun_keyword = "new"
215 end
216
217 # Params
218 var params = new Array[String]
219 for param in method.params do
220 if param.is_single then break
221 params.add "{param.variable_name}: {param.return_type.to_nit_type}"
222 end
223
224 var params_with_par = ""
225 if params.not_empty then params_with_par = "({params.join(", ")})"
226
227 # Return
228 var ret = ""
229 if method.return_type != "void" and fun_keyword != "new" then
230 ret = ": {method.return_type.to_nit_type}"
231 end
232
233 file.write """
234 {{{c}}} {{{fun_keyword}}} {{{name}}}{{{params_with_par}}}{{{ret}}} in "ObjC" `{
235 """
236 end
237
238 # Write a combined call to alloc and to a constructor/method
239 private fun write_objc_init_call(class_name: String, method: ObjcMethod, file: Writer)
240 do
241 # Method name and other params
242 var params = new Array[String]
243 for param in method.params do
244 if not param.is_single then
245 params.add "{param.name}: {param.variable_name}"
246 else params.add param.name
247 end
248
249 var c = method.comment_str
250
251 file.write """
252 {{{c}}} return [[{{{class_name}}} alloc] {{{params.join(" ")}}}];
253 {{{c}}} `}
254
255 """
256 end
257
258 private fun write_objc_method_call(method: ObjcMethod, file: Writer)
259 do
260 # Is there a value to return?
261 var ret = ""
262 if method.return_type != "void" then ret = "return "
263
264 # Method name and other params
265 var params = new Array[String]
266 for param in method.params do
267 if not param.is_single then
268 params.add "{param.name}: {param.variable_name}"
269 else params.add param.name
270 end
271
272 var c = method.comment_str
273
274 file.write """
275 {{{c}}} {{{ret}}}[self {{{params.join(" ")}}}];
276 {{{c}}} `}
277
278 """
279 end
280 end
281
282 redef class Text
283 # Nit equivalent to this type
284 private fun to_nit_type: String
285 do
286 var types = sys.nit_to_java_types
287
288 if types.has_key(self) then
289 return types[self]
290 else
291 return to_s
292 end
293 end
294 end
295
296 redef class Property
297 private fun comment_str: String do if is_commented then
298 return "#"
299 else return ""
300 end