1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Serialization support compiler, a tool to support deserialization of live generic types
19 # Executing on the module `game_logic` will create the module `game_logic_serial`
20 # in the local directory. Mixing the generated module to the main module with
21 # `nitc game_logic.nit -m game_logic_serial` will create a program supporting
22 # deserialization of all generic types visible from the main module.
24 # Because the generation is limited to the visible types, a module author might want
25 # generate and include its own serialization support module.
32 import rapid_type_analysis
34 redef class ToolContext
35 # Where do we put a single result?
36 var opt_output
: OptionString = new OptionString("Output file (can also be 'stdout')", "-o", "--output")
38 # Where do we put the result?
39 var opt_dir
: OptionString = new OptionString("Output directory", "--dir")
41 # Depth of the visit and generation
42 var opt_depth
= new OptionEnum(["module", "group", "package"],
43 "Depth of the visit and generation", 0, "-d", "--depth")
47 option_context
.add_option
(opt_output
, opt_dir
, opt_depth
)
53 # Get the type of the class `Serializable`
54 var serializable_type
: MClassType is lazy
do
55 return self.get_primitive_class
("Serializable").mclass_type
60 # List classes composing this type
61 private fun related_mclasses
(mmodule
: MModule): Array[MClass] is abstract
64 redef class MClassType
65 redef fun related_mclasses
(mmodule
) do return [mclass
]
68 redef class MProxyType
69 redef fun related_mclasses
(mmodule
) do return undecorate
.related_mclasses
(mmodule
)
72 redef class MGenericType
73 redef fun related_mclasses
(mmodule
)
76 for arg_mtype
in arguments
do mods
.add_all
(arg_mtype
.related_mclasses
(mmodule
))
81 var toolcontext
= new ToolContext
82 toolcontext
.tooldescription
= """
83 Usage: nitserial [OPTION] program.nit [other_program.nit [...]]
84 Generates a serialization support module"""
86 toolcontext
.process_options
(args
)
87 var arguments
= toolcontext
.option_context
.rest
90 if toolcontext
.opt_output
.value
!= null and toolcontext
.opt_dir
.value
!= null then
91 print
"Error: cannot use both --dir and --output"
94 if arguments
.length
> 1 and toolcontext
.opt_output
.value
!= null then
95 print
"Error: --output needs a single source file. Do you prefer --dir?"
100 var modelbuilder
= new ModelBuilder(model
, toolcontext
)
102 var mmodules
= modelbuilder
.parse_full
(arguments
)
103 modelbuilder
.run_phases
105 # Create a distinct support module per target modules
106 for mmodule
in mmodules
do
107 # Name of the support module
110 # Path to the support module
111 var module_path
= toolcontext
.opt_output
.value
112 if module_path
== null then
113 module_name
= "{mmodule.name}_serial"
114 module_path
= "{module_name}.nit"
116 var dir
= toolcontext
.opt_dir
.value
117 if dir
!= null then module_path
= dir
.join_path
(module_path
)
118 else if module_path
== "stdout" then
119 module_name
= "{mmodule.name}_serial"
121 else if module_path
.has_suffix
(".nit") then
122 module_name
= module_path
.basename
(".nit")
124 module_name
= module_path
.basename
125 module_path
+= ".nit"
128 var target_modules
= null
129 var importations
= null
130 var mgroup
= mmodule
.mgroup
131 if toolcontext
.opt_depth
.value
== 1 and mgroup
!= null then
132 modelbuilder
.scan_group mgroup
133 target_modules
= mgroup
.mmodules
134 else if toolcontext
.opt_depth
.value
== 2 then
136 target_modules
= new Array[MModule]
137 importations
= new Array[MModule]
138 if mgroup
!= null then
139 for g
in mgroup
.mpackage
.mgroups
do
140 target_modules
.add_all g
.mmodules
143 for g
in mgroup
.in_nesting
.direct_smallers
do
144 var dm
= g
.default_mmodule
150 for m
in mgroup
.mmodules
do
156 if target_modules
== null then target_modules
= [mmodule
]
157 if importations
== null then importations
= target_modules
159 var nit_module
= new NitModule(module_name
)
160 nit_module
.annotations
.add
"""generated"""
161 nit_module
.annotations
.add
"""no_warning("property-conflict")"""
162 nit_module
.header
= """
163 # This file is generated by nitserial
164 # Do not modify, but you can redef
167 for importation
in importations
do
168 nit_module
.imports
.add importation
.name
171 nit_module
.imports
.add
"serialization"
173 nit_module
.content
.add
"""
174 redef class Deserializer
175 redef fun deserialize_class(name)
178 var serializable_type
= mmodule
.serializable_type
179 var compiled_types
= new Array[MType]
180 for m
in target_modules
do
181 nit_module
.content
.add
"""
182 # Module: {{{m.to_s}}}"""
184 var rta
= modelbuilder
.do_rapid_type_analysis
(m
)
186 for mtype
in rta
.live_types
do
187 # We are only interested in instanciated generics, subtypes of Serializable
188 # and which are visible.
189 if mtype
isa MGenericType and
190 mtype
.is_subtype
(m
, null, serializable_type
) and
191 mtype
.mclass
.kind
== concrete_kind
and
192 not compiled_types
.has
(mtype
) then
194 # Intrude import the modules declaring private classes
195 var related_mclasses
= mtype
.related_mclasses
(mmodule
)
196 for mclass
in related_mclasses
do
197 if not mmodule
.is_visible
(mclass
.intro_mmodule
, mclass
.visibility
) then
198 var intro_mmodule
= mclass
.intro_mmodule
199 var intro_mgroup
= intro_mmodule
.mgroup
201 var to_import
= intro_mmodule
.full_name
202 if intro_mgroup
== null or intro_mgroup
.default_mmodule
== intro_mmodule
then
203 to_import
= intro_mmodule
.name
206 nit_module
.imports
.add
"intrude import {to_import}"
210 compiled_types
.add mtype
211 nit_module
.content
.add
"""
212 if name == \"{{{mtype}}}\" then return new {{{mtype}}}.from_deserializer(self)"""
217 nit_module
.content
.add
"""
222 # Compile support module
223 if module_path
!= null then
225 nit_module
.write_to_file module_path
228 nit_module
.write_to stdout