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
27 # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
28 var serialization_phase_pre_model
: Phase = new SerializationPhasePreModel(self, null)
30 # The second phase of the serialization
31 var serialization_phase_post_model
: Phase = new SerializationPhasePostModel(self,
32 [modelize_class_phase
, serialization_phase_pre_model
])
34 private fun place_holder_type_name
: String do return "PlaceHolderTypeWhichShouldNotExist"
38 # Is this node annotated to be made serializable?
39 private fun is_serialize
: Bool do return false
41 # Is this node annotated to not be made serializable?
42 private fun is_noserialize
: Bool do return false
44 private fun accept_precise_type_visitor
(v
: PreciseTypeVisitor) do visit_all
(v
)
47 redef class ADefinition
49 redef fun is_serialize
do
50 return get_annotations
("serialize").not_empty
or
51 get_annotations
("auto_serializable").not_empty
54 redef fun is_noserialize
do
55 return get_annotations
("noserialize").not_empty
59 # TODO add annotations on attributes (volatile, sensitive or do_not_serialize?)
60 private class SerializationPhasePreModel
63 redef fun process_annotated_node
(node
, nat
)
65 # Skip if we are not interested
66 var text
= nat
.n_atid
.n_id
.text
67 var serialize
= text
== "auto_serializable" or text
== "serialize"
68 var noserialize
= text
== "noserialize"
69 if not (serialize
or noserialize
) then return
71 # Check legality of annotation
72 if node
isa AModuledecl then
73 if noserialize
then toolcontext
.error
(node
.location
, "Syntax Error: superfluous use of `{text}`, by default a module is `{text}`")
75 else if not (node
isa AStdClassdef or node
isa AAttrPropdef) then
76 toolcontext
.error
(node
.location
,
77 "Syntax Error: only a class, a module or an attribute can be annotated with `{text}`.")
79 else if serialize
and node
.is_noserialize
then
80 toolcontext
.error
(node
.location
,
81 "Syntax Error: an entity cannot be both `{text}` and `noserialize`.")
83 else if node
.as(Prod).get_annotations
(text
).length
> 1 then
84 toolcontext
.warning
(node
.location
, "useless-{text}",
85 "Warning: duplicated annotation `{text}`.")
88 # Check the `serialize` state of the parent
89 if not node
isa AModuledecl then
90 var up_serialize
= false
91 var up
: nullable ANode = node
96 else if up
.is_serialize
then
99 else if up
.is_noserialize
then
104 # Check for useless double declarations
105 if serialize
and up_serialize
then
106 toolcontext
.warning
(node
.location
, "useless-serialize",
107 "Warning: superfluous use of `{text}`.")
108 else if noserialize
and not up_serialize
then
109 toolcontext
.warning
(node
.location
, "useless-noserialize",
110 "Warning: superfluous use of `{text}`.")
115 redef fun process_nclassdef
(nclassdef
)
117 if not nclassdef
isa AStdClassdef then return
119 # Is there a declaration on the classdef or the module?
120 var serialize
= nclassdef
.is_serialize
122 if not serialize
and not nclassdef
.is_noserialize
then
123 # Is the module marked serialize?
124 serialize
= nclassdef
.parent
.as(AModule).is_serialize
127 var per_attribute
= false
128 if not serialize
then
129 # Is there an attribute marked serialize?
130 for npropdef
in nclassdef
.n_propdefs
do
131 if npropdef
.is_serialize
then
140 # Add `super Serializable`
141 var sc
= toolcontext
.parse_superclass
("Serializable")
142 sc
.location
= nclassdef
.location
143 nclassdef
.n_propdefs
.add sc
146 generate_serialization_method
(nclassdef
, per_attribute
)
147 generate_deserialization_init
(nclassdef
, per_attribute
)
151 redef fun process_nmodule
(nmodule
)
153 # Clear the cache of constructors to review before adding to it
154 nmodule
.inits_to_retype
.clear
156 # collect all classes
157 var auto_serializable_nclassdefs
= new Array[AStdClassdef]
158 for nclassdef
in nmodule
.n_classdefs
do
159 if nclassdef
isa AStdClassdef and nclassdef
.is_serialize
then
160 auto_serializable_nclassdefs
.add nclassdef
164 if not auto_serializable_nclassdefs
.is_empty
then
165 generate_deserialization_method
(nmodule
, auto_serializable_nclassdefs
)
169 fun generate_serialization_method
(nclassdef
: AClassdef, per_attribute
: Bool)
171 var npropdefs
= nclassdef
.n_propdefs
173 var code
= new Array[String]
174 code
.add
"redef fun core_serialize_to(v)"
178 for attribute
in npropdefs
do if attribute
isa AAttrPropdef then
180 # Is `attribute` to be skipped?
181 if (per_attribute
and not attribute
.is_serialize
) or
182 attribute
.is_noserialize
then continue
184 var name
= attribute
.name
185 code
.add
" v.serialize_attribute(\"{name}\
", {name})"
190 # Create method Node and add it to the AST
191 npropdefs
.push
(toolcontext
.parse_propdef
(code
.join
("\n")))
194 # Add a constructor to the automated nclassdef
195 fun generate_deserialization_init
(nclassdef
: AClassdef, per_attribute
: Bool)
197 var npropdefs
= nclassdef
.n_propdefs
199 var code
= new Array[String]
200 code
.add
"redef init from_deserializer(v: Deserializer)"
203 code
.add
" v.notify_of_creation self"
205 for attribute
in npropdefs
do if attribute
isa AAttrPropdef then
207 # Is `attribute` to be skipped?
208 if (per_attribute
and not attribute
.is_serialize
) or
209 attribute
.is_noserialize
then continue
211 var n_type
= attribute
.n_type
213 if n_type
== null then
214 # Use a place holder, we will replace it with the infered type after the model phases
215 type_name
= toolcontext
.place_holder_type_name
217 type_name
= n_type
.type_name
219 var name
= attribute
.name
222 code
.add
"\tvar {name} = v.deserialize_attribute(\"{name}\
")"
223 code
.add
"\tassert {name} isa {type_name} else print \"Unsupported type for `\{class_name\}::{name}`, got
'\{{name}.class_name\}'; expected
{type_name}\
""
224 code
.add
"\tself.{name} = {name}"
229 var npropdef
= toolcontext
.parse_propdef
(code
.join
("\n")).as(AMethPropdef)
230 npropdefs
.add npropdef
231 nclassdef
.parent
.as(AModule).inits_to_retype
.add npropdef
234 # Added to the abstract serialization service
235 fun generate_deserialization_method
(nmodule
: AModule, nclassdefs
: Array[AStdClassdef])
237 var code
= new Array[String]
239 var deserializer_nclassdef
= nmodule
.deserializer_nclassdef
240 var deserializer_npropdef
241 if deserializer_nclassdef
== null then
243 code
.add
"redef class Deserializer"
244 deserializer_npropdef
= null
246 deserializer_npropdef
= deserializer_nclassdef
.deserializer_npropdef
249 if deserializer_npropdef
== null then
250 # create the property
251 code
.add
" redef fun deserialize_class(name)"
254 toolcontext
.error
(deserializer_npropdef
.location
, "Error: you cannot define `Deserializer::deserialize_class` in a module where you use `auto_serializable`.")
258 for nclassdef
in nclassdefs
do
259 var name
= nclassdef
.n_id
.text
260 if nclassdef
.n_formaldefs
.is_empty
and
261 not nclassdef
.n_classkind
isa AAbstractClasskind then
263 code
.add
" if name == \"{name}\
" then return new {name}.from_deserializer(self)"
267 code
.add
" return super"
270 if deserializer_nclassdef
== null then
272 nmodule
.n_classdefs
.add toolcontext
.parse_classdef
(code
.join
("\n"))
274 deserializer_nclassdef
.n_propdefs
.add
(toolcontext
.parse_propdef
(code
.join
("\n")))
279 private class SerializationPhasePostModel
282 redef fun process_nmodule
(nmodule
)
284 for npropdef
in nmodule
.inits_to_retype
do
285 var mpropdef
= npropdef
.mpropdef
286 if mpropdef
== null then continue # skip error
287 var v
= new PreciseTypeVisitor(npropdef
, mpropdef
.mclassdef
, toolcontext
)
288 npropdef
.accept_precise_type_visitor v
293 # Visitor on generated constructors to replace the expected type of deserialized attributes
294 private class PreciseTypeVisitor
297 var npropdef
: AMethPropdef
298 var mclassdef
: MClassDef
299 var toolcontext
: ToolContext
301 redef fun visit
(n
) do n
.accept_precise_type_visitor
(self)
305 redef fun accept_precise_type_visitor
(v
)
307 if n_type
.collect_text
!= v
.toolcontext
.place_holder_type_name
then return
309 var attr_name
= "_" + n_expr
.collect_text
310 for mattrdef
in v
.mclassdef
.mpropdefs
do
311 if mattrdef
isa MAttributeDef and mattrdef
.name
== attr_name
then
312 var new_ntype
= v
.toolcontext
.parse_something
(mattrdef
.static_mtype
.to_s
)
313 n_type
.replace_with new_ntype
320 redef class AAttrPropdef
321 private fun name
: String
328 private fun type_name
: String
332 if n_kwnullable
!= null then name
= "nullable {name}"
335 if not types
.is_empty
then
336 var params
= new Array[String]
337 for t
in types
do params
.add
(t
.type_name
)
338 return "{name}[{params.join(", ")}]"
344 private fun deserializer_nclassdef
: nullable AStdClassdef
346 for nclassdef
in n_classdefs
do
347 if nclassdef
isa AStdClassdef and nclassdef
.n_id
.text
== "Deserialization" then
355 private var inits_to_retype
= new Array[AMethPropdef]
357 redef fun is_serialize
do return n_moduledecl
!= null and n_moduledecl
.is_serialize
360 redef class AStdClassdef
361 private fun deserializer_npropdef
: nullable AMethPropdef
363 for npropdef
in n_propdefs
do if npropdef
isa AMethPropdef then
364 var id
= npropdef
.n_methid
365 if id
isa AIdMethid and id
.n_id
.text
== "deserialize_class" then