1 # This file is part of NIT ( http://www.nitlanguage.org ).
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>
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
11 # http://www.apache.org/licenses/LICENSE-2.0
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.
19 # Phase generating methods to serialize Nit objects to different formats
20 module serialization_phase
22 private import parser_util
24 private import annotation
26 redef class ToolContext
28 # Apply the annotation `serialize_as`
29 var serialization_phase_rename
: Phase = new SerializationPhaseRename(self, null)
31 # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
32 var serialization_phase_pre_model
: Phase = new SerializationPhasePreModel(self,
33 [serialization_phase_rename
])
35 # The second phase of the serialization
36 var serialization_phase_post_model
: Phase = new SerializationPhasePostModel(self,
37 [modelize_class_phase
, serialization_phase_pre_model
])
39 private fun place_holder_type_name
: String do return "PlaceHolderTypeWhichShouldNotExist"
43 # Is this node annotated to be made serializable?
44 private fun is_serialize
: Bool do return false
46 # Is this node annotated to not be made serializable?
47 private fun is_noserialize
: Bool do return false
49 private fun accept_precise_type_visitor
(v
: PreciseTypeVisitor) do visit_all
(v
)
52 redef class ADefinition
54 redef fun is_serialize
do
55 return get_annotations
("serialize").not_empty
or
56 get_annotations
("auto_serializable").not_empty
59 redef fun is_noserialize
do
60 return get_annotations
("noserialize").not_empty
64 private class SerializationPhaseRename
67 redef fun process_annotated_node
(node
, nat
)
69 var text
= nat
.n_atid
.n_id
.text
70 if text
!= "serialize_as" then return
72 if not node
isa AAttrPropdef then
73 toolcontext
.error
(node
.location
,
74 "Syntax Error: annotation `{text}` applies only to attributes.")
78 # Can't use `arg_as_string` because it needs the literal phase
80 if args
.length
!= 1 or not args
.first
isa AStringFormExpr then
81 toolcontext
.error
(node
.location
,
82 "Syntax Error: annotation `{text}` expects a single string literal as argument.")
86 var t
= args
.first
.collect_text
87 var val
= t
.substring
(1, t
.length-2
)
88 node
.serialize_name
= val
92 private class SerializationPhasePreModel
95 redef fun process_annotated_node
(node
, nat
)
97 # Skip if we are not interested
98 var text
= nat
.n_atid
.n_id
.text
99 var serialize
= text
== "auto_serializable" or text
== "serialize"
100 var noserialize
= text
== "noserialize"
101 if not (serialize
or noserialize
) then return
103 # Check legality of annotation
104 if node
isa AModuledecl then
105 if noserialize
then toolcontext
.error
(node
.location
, "Syntax Error: superfluous use of `{text}`, by default a module is `{text}`")
107 else if not (node
isa AStdClassdef or node
isa AAttrPropdef) then
108 toolcontext
.error
(node
.location
,
109 "Syntax Error: only a class, a module or an attribute can be annotated with `{text}`.")
111 else if serialize
and node
.is_noserialize
then
112 toolcontext
.error
(node
.location
,
113 "Syntax Error: an entity cannot be both `{text}` and `noserialize`.")
115 else if node
.as(Prod).get_annotations
(text
).length
> 1 then
116 toolcontext
.warning
(node
.location
, "useless-{text}",
117 "Warning: duplicated annotation `{text}`.")
120 # Check the `serialize` state of the parent
121 if not node
isa AModuledecl then
122 var up_serialize
= false
123 var up
: nullable ANode = node
128 else if up
.is_serialize
then
131 else if up
.is_noserialize
then
136 # Check for useless double declarations
137 if serialize
and up_serialize
then
138 toolcontext
.warning
(node
.location
, "useless-serialize",
139 "Warning: superfluous use of `{text}`.")
140 else if noserialize
and not up_serialize
then
141 toolcontext
.warning
(node
.location
, "useless-noserialize",
142 "Warning: superfluous use of `{text}`.")
147 redef fun process_nclassdef
(nclassdef
)
149 if not nclassdef
isa AStdClassdef then return
151 var serialize_by_default
= nclassdef
.how_serialize
153 if serialize_by_default
!= null then
155 # Add `super Serializable`
156 var sc
= toolcontext
.parse_superclass
("Serializable")
157 sc
.location
= nclassdef
.location
158 nclassdef
.n_propdefs
.add sc
161 var per_attribute
= not serialize_by_default
162 generate_serialization_method
(nclassdef
, per_attribute
)
163 generate_deserialization_init
(nclassdef
, per_attribute
)
167 redef fun process_nmodule
(nmodule
)
169 # Clear the cache of constructors to review before adding to it
170 nmodule
.inits_to_retype
.clear
172 # collect all classes
173 var auto_serializable_nclassdefs
= new Array[AStdClassdef]
174 for nclassdef
in nmodule
.n_classdefs
do
175 if nclassdef
isa AStdClassdef and nclassdef
.how_serialize
!= null then
176 auto_serializable_nclassdefs
.add nclassdef
180 if not auto_serializable_nclassdefs
.is_empty
then
181 generate_deserialization_method
(nmodule
, auto_serializable_nclassdefs
)
185 fun generate_serialization_method
(nclassdef
: AClassdef, per_attribute
: Bool)
187 var npropdefs
= nclassdef
.n_propdefs
189 var code
= new Array[String]
190 code
.add
"redef fun core_serialize_to(v)"
194 for attribute
in npropdefs
do if attribute
isa AAttrPropdef then
196 # Is `attribute` to be skipped?
197 if (per_attribute
and not attribute
.is_serialize
) or
198 attribute
.is_noserialize
then continue
200 code
.add
" v.serialize_attribute(\"{attribute.serialize_name}\
", {attribute.name})"
205 # Create method Node and add it to the AST
206 npropdefs
.push
(toolcontext
.parse_propdef
(code
.join
("\n")))
209 # Add a constructor to the automated nclassdef
210 fun generate_deserialization_init
(nclassdef
: AClassdef, per_attribute
: Bool)
212 var npropdefs
= nclassdef
.n_propdefs
214 # Do not insert a `from_deserializer` if it already exists
215 for npropdef
in npropdefs
do
216 if npropdef
isa AMethPropdef then
217 var methid
= npropdef
.n_methid
218 if methid
!= null and methid
.collect_text
== "from_deserializer" then
224 var code
= new Array[String]
226 redef init from_deserializer(v: Deserializer)
229 v.notify_of_creation self
232 for attribute
in npropdefs
do if attribute
isa AAttrPropdef then
234 # Is `attribute` to be skipped?
235 if (per_attribute
and not attribute
.is_serialize
) or
236 attribute
.is_noserialize
then continue
238 var n_type
= attribute
.n_type
240 if n_type
== null then
241 # Use a place holder, we will replace it with the inferred type after the model phases
242 type_name
= toolcontext
.place_holder_type_name
244 type_name
= n_type
.type_name
246 var name
= attribute
.name
249 var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
250 if not {{{name}}} isa {{{type_name}}} then
251 # Check if it was a subjectent error
252 v.errors.add new AttributeTypeError("TODO remove this arg on c_src regen",
253 self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name}}}")
255 # Clear subjacent error
256 if v.keep_going == false then return
258 self.{{{name}}} = {{{name}}}
265 var npropdef
= toolcontext
.parse_propdef
(code
.join
("\n")).as(AMethPropdef)
266 npropdefs
.add npropdef
267 nclassdef
.parent
.as(AModule).inits_to_retype
.add npropdef
270 # Added to the abstract serialization service
271 fun generate_deserialization_method
(nmodule
: AModule, nclassdefs
: Array[AStdClassdef])
273 var code
= new Array[String]
275 var deserializer_nclassdef
= nmodule
.deserializer_nclassdef
276 var deserializer_npropdef
277 if deserializer_nclassdef
== null then
279 code
.add
"redef class Deserializer"
280 deserializer_npropdef
= null
282 deserializer_npropdef
= deserializer_nclassdef
.deserializer_npropdef
285 if deserializer_npropdef
== null then
286 # create the property
287 code
.add
" redef fun deserialize_class_intern(name)"
290 toolcontext
.error
(deserializer_npropdef
.location
, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
294 for nclassdef
in nclassdefs
do
295 var name
= nclassdef
.n_qid
.n_id
.text
296 if nclassdef
.n_formaldefs
.is_empty
and
297 nclassdef
.n_classkind
isa AConcreteClasskind then
299 code
.add
" if name == \"{name}\
" then return new {name}.from_deserializer(self)"
303 code
.add
" return super"
306 if deserializer_nclassdef
== null then
308 nmodule
.n_classdefs
.add toolcontext
.parse_classdef
(code
.join
("\n"))
310 deserializer_nclassdef
.n_propdefs
.add
(toolcontext
.parse_propdef
(code
.join
("\n")))
315 private class SerializationPhasePostModel
318 redef fun process_nmodule
(nmodule
)
320 for npropdef
in nmodule
.inits_to_retype
do
321 var mpropdef
= npropdef
.mpropdef
322 if mpropdef
== null then continue # skip error
323 var v
= new PreciseTypeVisitor(npropdef
, mpropdef
.mclassdef
, toolcontext
)
324 npropdef
.accept_precise_type_visitor v
329 # Visitor on generated constructors to replace the expected type of deserialized attributes
330 private class PreciseTypeVisitor
333 var npropdef
: AMethPropdef
334 var mclassdef
: MClassDef
335 var toolcontext
: ToolContext
337 redef fun visit
(n
) do n
.accept_precise_type_visitor
(self)
341 redef fun accept_precise_type_visitor
(v
)
343 if n_type
.collect_text
!= v
.toolcontext
.place_holder_type_name
then return
345 var attr_name
= "_" + n_expr
.collect_text
346 for mattrdef
in v
.mclassdef
.mpropdefs
do
347 if mattrdef
isa MAttributeDef and mattrdef
.name
== attr_name
then
348 var new_ntype
= v
.toolcontext
.parse_something
(mattrdef
.static_mtype
.to_s
)
349 n_type
.replace_with new_ntype
356 redef class AAttrPropdef
357 private fun name
: String do return n_id2
.text
359 # Name of this attribute in the serialized format
360 private var serialize_name
: String = name
is lazy
364 private fun type_name
: String
366 var name
= n_qid
.n_id
.text
368 if n_kwnullable
!= null then name
= "nullable {name}"
371 if not types
.is_empty
then
372 var params
= new Array[String]
373 for t
in types
do params
.add
(t
.type_name
)
374 return "{name}[{params.join(", ")}]"
380 private fun deserializer_nclassdef
: nullable AStdClassdef
382 for nclassdef
in n_classdefs
do
383 if nclassdef
isa AStdClassdef and nclassdef
.n_qid
.n_id
.text
== "Deserializer" then
391 private var inits_to_retype
= new Array[AMethPropdef]
393 redef fun is_serialize
do return n_moduledecl
!= null and n_moduledecl
.is_serialize
396 redef class AStdClassdef
397 private fun deserializer_npropdef
: nullable AMethPropdef
399 for npropdef
in n_propdefs
do if npropdef
isa AMethPropdef then
400 var id
= npropdef
.n_methid
401 if id
isa AIdMethid and id
.n_id
.text
== "deserialize_class_intern" then
409 # Is this classed marked `serialize`? in part or fully?
411 # This method returns 3 possible values:
412 # * `null`, this class is not to be serialized.
413 # * `true`, the attributes of this class are to be serialized by default.
414 # * `false`, the attributes of this class are to be serialized on demand only.
415 fun how_serialize
: nullable Bool
417 # Is there a declaration on the classdef or the module?
418 var serialize
= is_serialize
420 if not serialize
and not is_noserialize
then
421 # Is the module marked serialize?
422 serialize
= parent
.as(AModule).is_serialize
425 if serialize
then return true
427 if not serialize
then
428 # Is there an attribute marked serialize?
429 for npropdef
in n_propdefs
do
430 if npropdef
.is_serialize
then