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