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 (model-only) to serialize Nit objects
20 module serialization_model_phase
22 private import parser_util
24 private import annotation
25 intrude import literal
27 redef class ToolContext
29 # Apply the annotation `serialize_as`
30 var serialization_phase_rename
: Phase = new SerializationPhaseRename(self, null)
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
])
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
45 redef class ADefinition
47 redef fun is_serialize
do
48 return get_annotations
("serialize").not_empty
or
49 get_annotations
("auto_serializable").not_empty
52 redef fun is_noserialize
do
53 return get_annotations
("noserialize").not_empty
57 private class SerializationPhaseRename
60 redef fun process_annotated_node
(node
, nat
)
62 var text
= nat
.n_atid
.n_id
.text
63 if text
!= "serialize_as" then return
65 if not node
isa AAttrPropdef then
66 toolcontext
.error
(node
.location
,
67 "Syntax Error: annotation `{text}` applies only to attributes.")
71 # Can't use `arg_as_string` because it needs the literal phase
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.")
79 var t
= args
.first
.collect_text
80 var val
= t
.substring
(1, t
.length-2
)
81 node
.serialize_name
= val
85 private class SerializationPhasePreModel
88 redef fun process_annotated_node
(node
, nat
)
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
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}`")
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}`.")
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`.")
108 else if node
.as(Prod).get_annotations
(text
).length
> 1 then
109 toolcontext
.warning
(node
.location
, "useless-{text}",
110 "Warning: duplicated annotation `{text}`.")
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
121 else if up
.is_serialize
then
124 else if up
.is_noserialize
then
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}`.")
140 redef fun process_nclassdef
(nclassdef
)
142 if not nclassdef
isa AStdClassdef then return
144 var serialize_by_default
= nclassdef
.how_serialize
146 if serialize_by_default
!= null then
148 # Add `super Serializable`
149 var sc
= toolcontext
.parse_superclass
("Serializable")
150 sc
.location
= nclassdef
.location
151 nclassdef
.n_propdefs
.add sc
154 var per_attribute
= not serialize_by_default
155 generate_serialization_method
(nclassdef
, per_attribute
)
156 generate_deserialization_init
(nclassdef
)
160 redef fun process_nmodule
(nmodule
)
162 # Clear the cache of constructors to review before adding to it
163 nmodule
.inits_to_retype
.clear
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
)
172 # Implement `core_serialize_to` on `nclassdef`
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)
178 var npropdefs
= nclassdef
.n_propdefs
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
190 var code
= new Array[String]
191 code
.add
"redef fun core_serialize_to(v)"
195 for attribute
in npropdefs
do if attribute
isa AAttrPropdef then
197 # Is `attribute` to be skipped?
198 if (per_attribute
and not attribute
.is_serialize
) or
199 attribute
.is_noserialize
then continue
201 code
.add
" v.serialize_attribute(\"{attribute.serialize_name}\
", {attribute.name})"
206 # Create method Node and add it to the AST
207 npropdefs
.push
(toolcontext
.parse_propdef
(code
.join
("\n")))
210 # Add an empty constructor to the automated nclassdef
212 # Will be filled by `SerializationPhasePostModel`.
213 fun generate_deserialization_init
(nclassdef
: AClassdef)
215 var npropdefs
= nclassdef
.n_propdefs
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
228 redef init from_deserializer(v) do abort"""
230 var npropdef
= toolcontext
.parse_propdef
(code
).as(AMethPropdef)
231 npropdefs
.add npropdef
232 nclassdef
.parent
.as(AModule).inits_to_retype
.add npropdef
235 # Add an empty `Deserializer::deserialize_class_intern`
237 # Will be filled by `SerializationPhasePostModel`.
238 fun generate_deserialization_method
(nmodule
: AModule, nclassdefs
: Array[AStdClassdef])
240 var code
= new Array[String]
242 var deserializer_nclassdef
= nmodule
.deserializer_nclassdef
243 var deserializer_npropdef
244 if deserializer_nclassdef
== null then
246 code
.add
"redef class Deserializer"
247 deserializer_npropdef
= null
249 deserializer_npropdef
= deserializer_nclassdef
.deserializer_npropdef
252 if deserializer_npropdef
== null then
253 # create the property
254 code
.add
" redef fun deserialize_class_intern(name) do abort"
256 toolcontext
.error
(deserializer_npropdef
.location
, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
260 if deserializer_nclassdef
== null then
262 nmodule
.n_classdefs
.add toolcontext
.parse_classdef
(code
.join
("\n"))
264 deserializer_nclassdef
.n_propdefs
.add
(toolcontext
.parse_propdef
(code
.join
("\n")))
269 redef class AAttrPropdef
270 private fun name
: String do return n_id2
.text
272 # Name of this attribute in the serialized format
273 private var serialize_name
: String = name
is lazy
277 private fun type_name
: String
279 var name
= n_qid
.n_id
.text
281 if n_kwnullable
!= null then name
= "nullable {name}"
284 if not types
.is_empty
then
285 var params
= new Array[String]
286 for t
in types
do params
.add
(t
.type_name
)
287 return "{name}[{params.join(", ")}]"
293 private fun deserializer_nclassdef
: nullable AStdClassdef
295 for nclassdef
in n_classdefs
do
296 if not nclassdef
isa AStdClassdef then continue
297 var n_qid
= nclassdef
.n_qid
298 if n_qid
!= null and n_qid
.n_id
.text
== "Deserializer" then return nclassdef
304 private var inits_to_retype
= new Array[AMethPropdef]
306 redef fun is_serialize
308 var n_moduledecl
= n_moduledecl
309 return n_moduledecl
!= null and n_moduledecl
.is_serialize
312 # `AStdClassdef` marked as serializable, itself or one of theur attribute
313 private var auto_serializable_nclassdefs
: Array[AStdClassdef] is lazy
do
314 var array
= new Array[AStdClassdef]
315 for nclassdef
in n_classdefs
do
316 if nclassdef
isa AStdClassdef and nclassdef
.how_serialize
!= null then
324 redef class AStdClassdef
325 private fun deserializer_npropdef
: nullable AMethPropdef
327 for npropdef
in n_propdefs
do if npropdef
isa AMethPropdef then
328 var id
= npropdef
.n_methid
329 if id
isa AIdMethid and id
.n_id
.text
== "deserialize_class_intern" then
337 # Is this classed marked `serialize`? in part or fully?
339 # This method returns 3 possible values:
340 # * `null`, this class is not to be serialized.
341 # * `true`, the attributes of this class are to be serialized by default.
342 # * `false`, the attributes of this class are to be serialized on demand only.
343 fun how_serialize
: nullable Bool
345 # Is there a declaration on the classdef or the module?
346 var serialize
= is_serialize
348 if not serialize
and not is_noserialize
then
349 # Is the module marked serialize?
350 serialize
= parent
.as(AModule).is_serialize
353 if serialize
then return true
355 if not serialize
then
356 # Is there an attribute marked serialize?
357 for npropdef
in n_propdefs
do
358 if npropdef
.is_serialize
then