private import annotation
redef class ToolContext
+
+ # Apply the annotation `serialize_as`
+ var serialization_phase_rename: Phase = new SerializationPhaseRename(self, null)
+
# Generate serialization and deserialization methods on `auto_serializable` annotated classes.
- var serialization_phase_pre_model: Phase = new SerializationPhasePreModel(self, null)
+ var serialization_phase_pre_model: Phase = new SerializationPhasePreModel(self,
+ [serialization_phase_rename])
# The second phase of the serialization
var serialization_phase_post_model: Phase = new SerializationPhasePostModel(self,
end
end
-# TODO add annotations on attributes (volatile, sensitive or do_not_serialize?)
+private class SerializationPhaseRename
+ super Phase
+
+ redef fun process_annotated_node(node, nat)
+ do
+ var text = nat.n_atid.n_id.text
+ if text != "serialize_as" then return
+
+ if not node isa AAttrPropdef then
+ toolcontext.error(node.location,
+ "Syntax Error: annotation `{text}` applies only to attributes.")
+ return
+ end
+
+ # Can't use `arg_as_string` because it needs the literal phase
+ var args = nat.n_args
+ if args.length != 1 or not args.first isa AStringFormExpr then
+ toolcontext.error(node.location,
+ "Syntax Error: annotation `{text}` expects a single string literal as argument.")
+ return
+ end
+
+ var t = args.first.collect_text
+ var val = t.substring(1, t.length-2)
+ node.serialize_name = val
+ end
+end
+
private class SerializationPhasePreModel
super Phase
if not node isa AModuledecl then
var up_serialize = false
var up: nullable ANode = node
- loop
+ while up != null do
up = up.parent
if up == null then
break
do
if not nclassdef isa AStdClassdef then return
- # Is there a declaration on the classdef or the module?
- var serialize = nclassdef.is_serialize
+ var serialize_by_default = nclassdef.how_serialize
- if not serialize and not nclassdef.is_noserialize then
- # Is the module marked serialize?
- serialize = nclassdef.parent.as(AModule).is_serialize
- end
+ if serialize_by_default != null then
- var per_attribute = false
- if not serialize then
- # Is there an attribute marked serialize?
- for npropdef in nclassdef.n_propdefs do
- if npropdef.is_serialize then
- serialize = true
- per_attribute = true
- break
- end
- end
- end
-
- if serialize then
# Add `super Serializable`
var sc = toolcontext.parse_superclass("Serializable")
sc.location = nclassdef.location
nclassdef.n_propdefs.add sc
# Add services
+ var per_attribute = not serialize_by_default
generate_serialization_method(nclassdef, per_attribute)
generate_deserialization_init(nclassdef, per_attribute)
end
# collect all classes
var auto_serializable_nclassdefs = new Array[AStdClassdef]
for nclassdef in nmodule.n_classdefs do
- if nclassdef isa AStdClassdef and nclassdef.is_serialize then
+ if nclassdef isa AStdClassdef and nclassdef.how_serialize != null then
auto_serializable_nclassdefs.add nclassdef
end
end
if (per_attribute and not attribute.is_serialize) or
attribute.is_noserialize then continue
- var name = attribute.name
- code.add " v.serialize_attribute(\"{name}\", {name})"
+ code.add " v.serialize_attribute(\"{attribute.serialize_name}\", {attribute.name})"
end
code.add "end"
do
var npropdefs = nclassdef.n_propdefs
+ # Do not insert a `from_deserializer` if it already exists
+ for npropdef in npropdefs do
+ if npropdef isa AMethPropdef then
+ var methid = npropdef.n_methid
+ if methid != null and methid.collect_text == "from_deserializer" then
+ return
+ end
+ end
+ end
+
var code = new Array[String]
- code.add "redef init from_deserializer(v: Deserializer)"
- code.add "do"
- code.add " super"
- code.add " v.notify_of_creation self"
+ code.add """
+redef init from_deserializer(v: Deserializer)
+do
+ super
+ v.notify_of_creation self
+"""
for attribute in npropdefs do if attribute isa AAttrPropdef then
var n_type = attribute.n_type
var type_name
+ var type_name_pretty
if n_type == null then
- # Use a place holder, we will replace it with the infered type after the model phases
+ # Use a place holder, we will replace it with the inferred type after the model phases
type_name = toolcontext.place_holder_type_name
+ type_name_pretty = "Unknown type"
else
type_name = n_type.type_name
+ type_name_pretty = type_name
end
var name = attribute.name
- code.add ""
- code.add "\tvar {name} = v.deserialize_attribute(\"{name}\")"
- code.add "\tassert {name} isa {type_name} else print \"Unsupported type for `\{class_name\}::{name}`, got '\{{name}.class_name\}'; expected {type_name}\""
- code.add "\tself.{name} = {name}"
+ if type_name == "nullable Object" then
+ # Don't type check
+ code.add """
+ var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
+"""
+ else code.add """
+ var {{{name}}} = v.deserialize_attribute("{{{attribute.serialize_name}}}")
+ if not {{{name}}} isa {{{type_name}}} then
+ # Check if it was a subjectent error
+ v.errors.add new AttributeTypeError(self, "{{{attribute.serialize_name}}}", {{{name}}}, "{{{type_name_pretty}}}")
+
+ # Clear subjacent error
+ if v.keep_going == false then return
+ else
+ self.{{{name}}} = {{{name}}}
+ end
+"""
end
code.add "end"
if deserializer_npropdef == null then
# create the property
- code.add " redef fun deserialize_class(name)"
+ code.add " redef fun deserialize_class_intern(name)"
code.add " do"
else
- toolcontext.error(deserializer_npropdef.location, "Error: you cannot define `Deserializer::deserialize_class` in a module where you use `auto_serializable`.")
+ toolcontext.error(deserializer_npropdef.location, "Error: `Deserializer::deserialize_class_intern` is generated and must not be defined, use `deserialize_class` instead.")
return
end
for nclassdef in nclassdefs do
- var name = nclassdef.n_id.text
+ var n_qid = nclassdef.n_qid
+ if n_qid == null then continue
+ var name = n_qid.n_id.text
if nclassdef.n_formaldefs.is_empty and
- not nclassdef.n_classkind isa AAbstractClasskind then
+ nclassdef.n_classkind isa AConcreteClasskind then
code.add " if name == \"{name}\" then return new {name}.from_deserializer(self)"
end
end
redef class AAttrPropdef
- private fun name: String
- do
- return n_id2.text
- end
+ private fun name: String do return n_id2.text
+
+ # Name of this attribute in the serialized format
+ private var serialize_name: String = name is lazy
end
redef class AType
private fun type_name: String
do
- var name = n_id.text
+ var name = n_qid.n_id.text
if n_kwnullable != null then name = "nullable {name}"
private fun deserializer_nclassdef: nullable AStdClassdef
do
for nclassdef in n_classdefs do
- if nclassdef isa AStdClassdef and nclassdef.n_id.text == "Deserialization" then
- return nclassdef
- end
+ if not nclassdef isa AStdClassdef then continue
+ var n_qid = nclassdef.n_qid
+ if n_qid != null and n_qid.n_id.text == "Deserializer" then return nclassdef
end
return null
private var inits_to_retype = new Array[AMethPropdef]
- redef fun is_serialize do return n_moduledecl != null and n_moduledecl.is_serialize
+ redef fun is_serialize
+ do
+ var n_moduledecl = n_moduledecl
+ return n_moduledecl != null and n_moduledecl.is_serialize
+ end
end
redef class AStdClassdef
do
for npropdef in n_propdefs do if npropdef isa AMethPropdef then
var id = npropdef.n_methid
- if id isa AIdMethid and id.n_id.text == "deserialize_class" then
+ if id isa AIdMethid and id.n_id.text == "deserialize_class_intern" then
return npropdef
end
end
return null
end
+
+ # Is this classed marked `serialize`? in part or fully?
+ #
+ # This method returns 3 possible values:
+ # * `null`, this class is not to be serialized.
+ # * `true`, the attributes of this class are to be serialized by default.
+ # * `false`, the attributes of this class are to be serialized on demand only.
+ fun how_serialize: nullable Bool
+ do
+ # Is there a declaration on the classdef or the module?
+ var serialize = is_serialize
+
+ if not serialize and not is_noserialize then
+ # Is the module marked serialize?
+ serialize = parent.as(AModule).is_serialize
+ end
+
+ if serialize then return true
+
+ if not serialize then
+ # Is there an attribute marked serialize?
+ for npropdef in n_propdefs do
+ if npropdef.is_serialize then
+ return false
+ end
+ end
+ end
+
+ return null
+ end
end