contrib/objcwrapper: intro `ObjcMethod::is_init`
[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 var commented_methods = new Array[ObjcMethod]
95 file.write "extern class " + classe.name + """ in "ObjC" `{ """ + classe.name + """ * `}\n"""
96 for super_name in classe.super_names do
97 file.write """ super """ + super_name + "\n"
98 end
99 if classe.super_names.is_empty then file.write """ super NSObject\n"""
100 write_constructor(classe, file)
101 file.write "\n"
102 for attribute in classe.attributes do
103 write_attribute(attribute, file)
104 end
105 for method in classe.methods do
106 if method.is_commented then
107 commented_methods.add(method)
108 else
109 if not opt_init_as_methods.value and method.params.first.name.has("init") then continue
110 file.write """ """
111 write_method(method, file)
112 file.write """ in "ObjC" `{\n """
113 write_objc_method_call(method, file)
114 file.write """ `}"""
115 if method != classe.methods.last then file.write "\n\n"
116 end
117 end
118 for commented_method in commented_methods do
119 if commented_method == commented_methods.first then file.write "\n"
120 file.write """ #"""
121 write_method(commented_method, file)
122 if commented_method != commented_methods.last then file.write "\n"
123 end
124 file.write "\nend\n"
125 end
126
127 private fun write_constructor(classe: ObjcClass, file: Writer)
128 do
129 if not opt_init_as_methods.value then
130 for method in classe.methods do
131 if method.params.first.name.has("init") and not method.is_commented then
132 file.write """\n """
133 if method.params.first.name == "init" then
134 file.write "new"
135 else
136 write_method(method, file)
137 end
138 file.write """ in "ObjC" `{\n"""
139 write_objc_init_call(classe.name, method, file)
140 file.write """ `}\n"""
141 end
142 end
143 else
144 file.write """\n new in "ObjC"`{\n"""
145 file.write """ return [""" + classe.name + " alloc];\n"
146 file.write """ `}\n"""
147 end
148 end
149
150 private fun write_attribute(attribute: ObjcAttribute, file: Writer)
151 do
152 write_attribute_getter(attribute, file)
153 # TODO write_attribute_setter if there is no `readonly` annotation
154 file.write "\n"
155 end
156
157 private fun write_attribute_getter(attribute: ObjcAttribute, file: Writer)
158 do
159 file.write """ fun """ + attribute.name.to_snake_case + ": " + attribute.return_type.to_nit_type
160 file.write """ in "ObjC" `{\n"""
161 file.write """ return [self """ + attribute.name + "];\n"
162 file.write """ `}\n"""
163 end
164
165 private fun write_attribute_setter(attribute: ObjcAttribute, file: Writer)
166 do
167 file.write """ fun """ + attribute.name.to_snake_case + "=(value: " + attribute.return_type.to_nit_type + ")"
168 file.write " in \"ObjC\" `\{\n"
169 file.write """ self.""" + attribute.name + " = value;\n"
170 file.write """ `}\n"""
171 end
172
173 private fun write_method(method: ObjcMethod, file: Writer)
174 do
175 var name = ""
176 for param in method.params do
177 name += param.name[0].to_upper.to_s + param.name.substring_from(1)
178 name = name.to_snake_case
179 end
180 if name.has("init") and not opt_init_as_methods.value then
181 file.write "new "
182 else
183 if opt_init_as_methods.value and name == "init" then name = "init_0"
184 file.write "fun "
185 end
186 file.write name
187 for param in method.params do
188 if param == method.params.first and not param.is_single then
189 file.write "(" + param.variable_name + ": " + param.return_type.to_nit_type
190 end
191 if param != method.params.first and not param.is_single then
192 file.write ", " + param.variable_name + ": " + param.return_type.to_nit_type
193 end
194 if param == method.params.last and not param.is_single then
195 file.write ")"
196 end
197 end
198 if method.return_type != "void" and not method.params.first.name.has("init") then
199 file.write ": " + method.return_type.to_nit_type
200 end
201 end
202
203 private fun write_objc_init_call(classe_name: String, method: ObjcMethod, file: Writer)
204 do
205 file.write """ return [[""" + classe_name + " alloc] "
206 for param in method.params do
207 if not param.is_single then
208 file.write param.name + ":" + param.variable_name
209 if not param == method.params.last then file.write " "
210 else
211 file.write param.name
212 end
213 end
214 file.write "];\n"
215 end
216
217 private fun write_objc_method_call(method: ObjcMethod, file: Writer)
218 do
219 if method.return_type != "void" then file.write "return "
220 file.write "[self "
221 for param in method.params do
222 if not param.is_single then
223 file.write param.name + ":" + param.variable_name
224 if not param == method.params.last then file.write " "
225 else
226 file.write param.name
227 end
228 end
229 file.write "];\n"
230 end
231 end
232
233 redef class Text
234 # Nit equivalent to this type
235 private fun to_nit_type: String
236 do
237 var types = sys.nit_to_java_types
238
239 if types.has_key(self) then
240 return types[self]
241 else
242 return to_s
243 end
244 end
245 end