modelize_property: Split model build of AAttrPropdef
[nit.git] / src / frontend / serialization_model_phase.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Jean-Philippe Caissy <jpcaissy@piji.ca>
4 # Copyright 2013 Guillaume Auger <jeho@resist.ca>
5 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18
19 # Phase generating methods (model-only) to serialize Nit objects
20 module serialization_model_phase
21
22 private import parser_util
23 import modelize
24 private import annotation
25 intrude import literal
26
27 redef class ToolContext
28
29 # Apply the annotation `serialize_as`
30 var serialization_phase_rename: Phase = new SerializationPhaseRename(self, null)
31
32 # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
33 var serialization_phase_pre_model: Phase = new SerializationPhasePreModel(self,
34 [serialization_phase_rename])
35 end
36
37 redef class ANode
38 # Is this node annotated to be made serializable?
39 private fun is_serialize: Bool do return false
40
41 # Is this node annotated to not be made serializable?
42 private fun is_noserialize: Bool do return false
43 end
44
45 redef class ADefinition
46
47 redef fun is_serialize do
48 return get_annotations("serialize").not_empty or
49 get_annotations("auto_serializable").not_empty
50 end
51
52 redef fun is_noserialize do
53 return get_annotations("noserialize").not_empty
54 end
55 end
56
57 private class SerializationPhaseRename
58 super Phase
59
60 redef fun process_annotated_node(node, nat)
61 do
62 var text = nat.n_atid.n_id.text
63 if text != "serialize_as" then return
64
65 if not node isa AAttrPropdef then
66 toolcontext.error(node.location,
67 "Syntax Error: annotation `{text}` applies only to attributes.")
68 return
69 end
70
71 # Can't use `arg_as_string` because it needs the literal phase
72 var args = nat.n_args
73 if args.length != 1 or not args.first isa AStringFormExpr then
74 toolcontext.error(node.location,
75 "Syntax Error: annotation `{text}` expects a single string literal as argument.")
76 return
77 end
78
79 var t = args.first.collect_text
80 var val = t.substring(1, t.length-2)
81 node.serialize_name = val
82 end
83 end
84
85 private class SerializationPhasePreModel
86 super Phase
87
88 redef fun process_annotated_node(node, nat)
89 do
90 # Skip if we are not interested
91 var text = nat.n_atid.n_id.text
92 var serialize = text == "auto_serializable" or text == "serialize"
93 var noserialize = text == "noserialize"
94 if not (serialize or noserialize) then return
95
96 # Check legality of annotation
97 if node isa AModuledecl then
98 if noserialize then toolcontext.error(node.location, "Syntax Error: superfluous use of `{text}`, by default a module is `{text}`")
99 return
100 else if not (node isa AStdClassdef or node isa AAttrPropdef) then
101 toolcontext.error(node.location,
102 "Syntax Error: only a class, a module or an attribute can be annotated with `{text}`.")
103 return
104 else if serialize and node.is_noserialize then
105 toolcontext.error(node.location,
106 "Syntax Error: an entity cannot be both `{text}` and `noserialize`.")
107 return
108 else if node.as(Prod).get_annotations(text).length > 1 then
109 toolcontext.warning(node.location, "useless-{text}",
110 "Warning: duplicated annotation `{text}`.")
111 end
112
113 # Check the `serialize` state of the parent
114 if not node isa AModuledecl then
115 var up_serialize = false
116 var up: nullable ANode = node
117 while up != null do
118 up = up.parent
119 if up == null then
120 break
121 else if up.is_serialize then
122 up_serialize = true
123 break
124 else if up.is_noserialize then
125 break
126 end
127 end
128
129 # Check for useless double declarations
130 if serialize and up_serialize then
131 toolcontext.warning(node.location, "useless-serialize",
132 "Warning: superfluous use of `{text}`.")
133 else if noserialize and not up_serialize then
134 toolcontext.warning(node.location, "useless-noserialize",
135 "Warning: superfluous use of `{text}`.")
136 end
137 end
138 end
139
140 redef fun process_nclassdef(nclassdef)
141 do
142 if not nclassdef isa AStdClassdef then return
143
144 var serialize_by_default = nclassdef.how_serialize
145
146 if serialize_by_default != null then
147
148 # Add `super Serializable`
149 var sc = toolcontext.parse_superclass("Serializable")
150 sc.location = nclassdef.location
151 nclassdef.n_propdefs.add sc
152
153 # Add services
154 var per_attribute = not serialize_by_default
155 generate_serialization_method(nclassdef, per_attribute)
156 generate_deserialization_init(nclassdef)
157 end
158 end
159
160 redef fun process_nmodule(nmodule)
161 do
162 # Clear the cache of constructors to review before adding to it
163 nmodule.inits_to_retype.clear
164
165 # collect all classes
166 var auto_serializable_nclassdefs = nmodule.auto_serializable_nclassdefs
167 if not auto_serializable_nclassdefs.is_empty then
168 generate_deserialization_method(nmodule, auto_serializable_nclassdefs)
169 end
170 end
171
172 # Implement `core_serialize_to` on `nclassdef`
173 #
174 # Are attributes serialized on demand `per_attribute` with `serialize`?
175 # Otherwise they are serialized by default, and we check instead for `noserialize`.
176 fun generate_serialization_method(nclassdef: AClassdef, per_attribute: Bool)
177 do
178 var npropdefs = nclassdef.n_propdefs
179
180 # Do not insert a `core_serialize_to` if it already exists
181 for npropdef in npropdefs do
182 if npropdef isa AMethPropdef then
183 var methid = npropdef.n_methid
184 if methid != null and methid.collect_text == "core_serialize_to" then
185 return
186 end
187 end
188 end
189
190 var code = new Array[String]
191 code.add "redef fun core_serialize_to(v)"
192 code.add "do"
193 code.add " super"
194
195 for attribute in npropdefs do if attribute isa AAttrPropdef then
196
197 # Is `attribute` to be skipped?
198 if (per_attribute and not attribute.is_serialize) or
199 attribute.is_noserialize then continue
200
201 code.add " v.serialize_attribute(\"{attribute.serialize_name}\", {attribute.name})"
202 end
203
204 code.add "end"
205
206 # Create method Node and add it to the AST
207 npropdefs.push(toolcontext.parse_propdef(code.join("\n")))
208 end
209
210 # Add an empty constructor to the automated nclassdef
211 #
212 # Will be filled by `SerializationPhasePostModel`.
213 fun generate_deserialization_init(nclassdef: AClassdef)
214 do
215 var npropdefs = nclassdef.n_propdefs
216
217 # Do not insert a `from_deserializer` if it already exists
218 for npropdef in npropdefs do
219 if npropdef isa AMethPropdef then
220 var methid = npropdef.n_methid
221 if methid != null and methid.collect_text == "from_deserializer" then
222 return
223 end
224 end
225 end
226
227 var code = """
228 redef init from_deserializer(v) do abort"""
229
230 var npropdef = toolcontext.parse_propdef(code).as(AMethPropdef)
231 npropdefs.add npropdef
232 nclassdef.parent.as(AModule).inits_to_retype.add npropdef
233 end
234
235 # Add an empty `Deserializer::deserialize_class_intern`
236 #
237 # Will be filled by `SerializationPhasePostModel`.
238 fun generate_deserialization_method(nmodule: AModule, nclassdefs: Array[AStdClassdef])
239 do
240 var code = new Array[String]
241
242 var deserializer_nclassdef = nmodule.deserializer_nclassdef
243 var deserializer_npropdef
244 if deserializer_nclassdef == null then
245 # create the class
246 code.add "redef class Deserializer"
247 deserializer_npropdef = null
248 else
249 deserializer_npropdef = deserializer_nclassdef.deserializer_npropdef
250 end
251
252 if deserializer_npropdef == null then
253 # create the property
254 code.add " redef fun deserialize_class_intern(name) do abort"
255 else
256 toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
257 return
258 end
259
260 if deserializer_nclassdef == null then
261 code.add "end"
262 nmodule.n_classdefs.add toolcontext.parse_classdef(code.join("\n"))
263 else
264 deserializer_nclassdef.n_propdefs.add(toolcontext.parse_propdef(code.join("\n")))
265 end
266 end
267 end
268
269 redef class AAttrPropdef
270 # Name of this attribute in the serialized format
271 private var serialize_name: String = name is lazy
272 end
273
274 redef class AType
275 private fun type_name: String
276 do
277 var name = n_qid.n_id.text
278
279 if n_kwnullable != null then name = "nullable {name}"
280
281 var types = n_types
282 if not types.is_empty then
283 var params = new Array[String]
284 for t in types do params.add(t.type_name)
285 return "{name}[{params.join(", ")}]"
286 else return name
287 end
288 end
289
290 redef class AModule
291 private fun deserializer_nclassdef: nullable AStdClassdef
292 do
293 for nclassdef in n_classdefs do
294 if not nclassdef isa AStdClassdef then continue
295 var n_qid = nclassdef.n_qid
296 if n_qid != null and n_qid.n_id.text == "Deserializer" then return nclassdef
297 end
298
299 return null
300 end
301
302 private var inits_to_retype = new Array[AMethPropdef]
303
304 redef fun is_serialize
305 do
306 var n_moduledecl = n_moduledecl
307 return n_moduledecl != null and n_moduledecl.is_serialize
308 end
309
310 # `AStdClassdef` marked as serializable, itself or one of theur attribute
311 private var auto_serializable_nclassdefs: Array[AStdClassdef] is lazy do
312 var array = new Array[AStdClassdef]
313 for nclassdef in n_classdefs do
314 if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
315 array.add nclassdef
316 end
317 end
318 return array
319 end
320 end
321
322 redef class AStdClassdef
323 private fun deserializer_npropdef: nullable AMethPropdef
324 do
325 for npropdef in n_propdefs do if npropdef isa AMethPropdef then
326 var id = npropdef.n_methid
327 if id isa AIdMethid and id.n_id.text == "deserialize_class_intern" then
328 return npropdef
329 end
330 end
331
332 return null
333 end
334
335 # Is this classed marked `serialize`? in part or fully?
336 #
337 # This method returns 3 possible values:
338 # * `null`, this class is not to be serialized.
339 # * `true`, the attributes of this class are to be serialized by default.
340 # * `false`, the attributes of this class are to be serialized on demand only.
341 fun how_serialize: nullable Bool
342 do
343 # Is there a declaration on the classdef or the module?
344 var serialize = is_serialize
345
346 if not serialize and not is_noserialize then
347 # Is the module marked serialize?
348 serialize = parent.as(AModule).is_serialize
349 end
350
351 if serialize then return true
352
353 if not serialize then
354 # Is there an attribute marked serialize?
355 for npropdef in n_propdefs do
356 if npropdef.is_serialize then
357 return false
358 end
359 end
360 end
361
362 return null
363 end
364 end