380f61ea2ccb8e4841f1c9337d6c513ae1451ea1
[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 private var nit_to_java_types: Map[String, String] is lazy do
28 var types = new HashMap[String, String]
29 types["char"] = "Byte"
30 types["short"] = "Int"
31 types["short int"] = "Int"
32 types["int"] = "Int"
33 types["long"] = "Int"
34 types["long int"] = "Int"
35 types["long long"] = "Int"
36 types["long long int"] = "Int"
37 types["float"] = "Float"
38 types["double"] = "Float"
39 types["long double"] = "Float"
40
41 types["NSUInteger"] = "Int"
42 types["BOOL"] = "Bool"
43 types["id"] = "NSObject"
44 return types
45 end
46 end
47
48 class CodeGenerator
49 # Merge the calls to `alloc` and `init...` in a single constructor?
50 #
51 # If `true`, also the default behavior, initializing an extern Objective-C object looks like:
52 # ~~~nitish
53 # var o = new NSArray.init_with_array(some_other_array)
54 # ~~~
55 #
56 # If `false`, the object must first be allocated and then initialized.
57 # This is closer to the Objective-C behavior:
58 # ~~~nitish
59 # var o = new NSArray
60 # o.init_with_array(some_other_array)
61 # ~~~
62 var init_with_alloc = true is writable
63
64 # Generate Nit code to wrap `classes`
65 fun generate(classes: Array[ObjcClass])
66 do
67 # Open specified path or stdin
68 var file
69 var path = opt_output.value
70 if path != null then
71 if path.file_extension != "nit" then
72 print_error "Warning: output file path does not end with '.nit'"
73 end
74
75 file = new FileWriter.open(path)
76 else
77 file = stdout
78 end
79
80 # Generate code
81 file.write "import cocoa::foundation\n\n"
82 for classe in classes do
83 write_class(classe, file)
84 end
85
86 if path != null then file.close
87 end
88
89 private fun write_class(classe: ObjcClass, file: Writer)
90 do
91 var commented_methods = new Array[ObjcMethod]
92 file.write "extern class " + classe.name + """ in "ObjC" `{ """ + classe.name + """ * `}\n"""
93 for super_name in classe.super_names do
94 file.write """ super """ + super_name + "\n"
95 end
96 if classe.super_names.is_empty then file.write """ super NSObject\n"""
97 write_constructor(classe, file)
98 file.write "\n"
99 for attribute in classe.attributes do
100 write_attribute(attribute, file)
101 end
102 for method in classe.methods do
103 if method.is_commented then
104 commented_methods.add(method)
105 else
106 if init_with_alloc and method.params.first.name.has("init") then continue
107 file.write """ """
108 write_method(method, file)
109 file.write """ in "ObjC" `{\n """
110 write_objc_method_call(method, file)
111 file.write """ `}"""
112 if method != classe.methods.last then file.write "\n\n"
113 end
114 end
115 for commented_method in commented_methods do
116 if commented_method == commented_methods.first then file.write "\n"
117 file.write """ #"""
118 write_method(commented_method, file)
119 if commented_method != commented_methods.last then file.write "\n"
120 end
121 file.write "\nend\n"
122 end
123
124 private fun write_constructor(classe: ObjcClass, file: Writer)
125 do
126 if init_with_alloc then
127 for method in classe.methods do
128 if method.params.first.name.has("init") and not method.is_commented then
129 file.write """\n """
130 if method.params.first.name == "init" then
131 file.write "new"
132 else
133 write_method(method, file)
134 end
135 file.write """ in "ObjC" `{\n"""
136 write_objc_init_call(classe.name, method, file)
137 file.write """ `}\n"""
138 end
139 end
140 else
141 file.write """\n new in "ObjC"`{\n"""
142 file.write """ return [""" + classe.name + " alloc];\n"
143 file.write """ `}\n"""
144 end
145 end
146
147 private fun write_attribute(attribute: ObjcAttribute, file: Writer)
148 do
149 write_attribute_getter(attribute, file)
150 # TODO write_attribute_setter if there is no `readonly` annotation
151 file.write "\n"
152 end
153
154 private fun write_attribute_getter(attribute: ObjcAttribute, file: Writer)
155 do
156 file.write """ fun """ + attribute.name.to_snake_case + ": " + attribute.return_type.to_nit_type
157 file.write """ in "ObjC" `{\n"""
158 file.write """ return [self """ + attribute.name + "];\n"
159 file.write """ `}\n"""
160 end
161
162 private fun write_attribute_setter(attribute: ObjcAttribute, file: Writer)
163 do
164 file.write """ fun """ + attribute.name.to_snake_case + "=(value: " + attribute.return_type.to_nit_type + ")"
165 file.write " in \"ObjC\" `\{\n"
166 file.write """ self.""" + attribute.name + " = value;\n"
167 file.write """ `}\n"""
168 end
169
170 private fun write_method(method: ObjcMethod, file: Writer)
171 do
172 var name = ""
173 for param in method.params do
174 name += param.name[0].to_upper.to_s + param.name.substring_from(1)
175 name = name.to_snake_case
176 end
177 if name.has("init") and init_with_alloc then
178 file.write "new "
179 else
180 if not init_with_alloc and name == "init" then name = "init_0"
181 file.write "fun "
182 end
183 file.write name
184 for param in method.params do
185 if param == method.params.first and not param.is_single then
186 file.write "(" + param.variable_name + ": " + param.return_type.to_nit_type
187 end
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.last and not param.is_single then
192 file.write ")"
193 end
194 end
195 if method.return_type != "void" and not method.params.first.name.has("init") then
196 file.write ": " + method.return_type.to_nit_type
197 end
198 end
199
200 private fun write_objc_init_call(classe_name: String, method: ObjcMethod, file: Writer)
201 do
202 file.write """ return [[""" + classe_name + " alloc] "
203 for param in method.params do
204 if not param.is_single then
205 file.write param.name + ":" + param.variable_name
206 if not param == method.params.last then file.write " "
207 else
208 file.write param.name
209 end
210 end
211 file.write "];\n"
212 end
213
214 private fun write_objc_method_call(method: ObjcMethod, file: Writer)
215 do
216 if method.return_type != "void" then file.write "return "
217 file.write "[self "
218 for param in method.params do
219 if not param.is_single then
220 file.write param.name + ":" + param.variable_name
221 if not param == method.params.last then file.write " "
222 else
223 file.write param.name
224 end
225 end
226 file.write "];\n"
227 end
228 end
229
230 redef class Text
231 # Nit equivalent to this type
232 private fun to_nit_type: String
233 do
234 var types = sys.nit_to_java_types
235
236 if types.has_key(self) then
237 return types[self]
238 else
239 return to_s
240 end
241 end
242 end