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