Merge: doc: fixed some typos and other misc. corrections
[nit.git] / src / nitserial.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # Serialization support compiler, a tool to support deserialization of live generic types
18 #
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.
23 #
24 # Because the generation is limited to the visible types, a module author might want
25 # generate and include its own serialization support module.
26 module nitserial
27
28 import template
29 import gen_nit
30
31 import frontend
32 import rapid_type_analysis
33
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")
37
38 # Where do we put the result?
39 var opt_dir: OptionString = new OptionString("Output directory", "--dir")
40
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")
44
45 redef init
46 do
47 option_context.add_option(opt_output, opt_dir, opt_depth)
48 super
49 end
50 end
51
52 redef class MModule
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
56 end
57 end
58
59 redef class MType
60 # List classes composing this type
61 private fun related_mclasses(mmodule: MModule): Array[MClass] is abstract
62 end
63
64 redef class MClassType
65 redef fun related_mclasses(mmodule) do return [mclass]
66 end
67
68 redef class MProxyType
69 redef fun related_mclasses(mmodule) do return undecorate.related_mclasses(mmodule)
70 end
71
72 redef class MGenericType
73 redef fun related_mclasses(mmodule)
74 do
75 var mods = super
76 for arg_mtype in arguments do mods.add_all(arg_mtype.related_mclasses(mmodule))
77 return mods
78 end
79 end
80
81 var toolcontext = new ToolContext
82 toolcontext.tooldescription = """
83 Usage: nitserial [OPTION] program.nit [other_program.nit [...]]
84 Generates a serialization support module"""
85
86 toolcontext.process_options(args)
87 var arguments = toolcontext.option_context.rest
88
89 # Check options
90 if toolcontext.opt_output.value != null and toolcontext.opt_dir.value != null then
91 print "Error: cannot use both --dir and --output"
92 exit 1
93 end
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?"
96 exit 1
97 end
98
99 var model = new Model
100 var modelbuilder = new ModelBuilder(model, toolcontext)
101
102 var mmodules = modelbuilder.parse_full(arguments)
103 modelbuilder.run_phases
104
105 # Create a distinct support module per target modules
106 for mmodule in mmodules do
107 # Name of the support module
108 var module_name
109
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"
115
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"
120 module_path = null
121 else if module_path.has_suffix(".nit") then
122 module_name = module_path.basename(".nit")
123 else
124 module_name = module_path.basename
125 module_path += ".nit"
126 end
127
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
135 # package
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
141 end
142
143 for g in mgroup.in_nesting.direct_smallers do
144 var dm = g.default_mmodule
145 if dm != null then
146 importations.add dm
147 end
148 end
149
150 for m in mgroup.mmodules do
151 importations.add m
152 end
153 end
154 end
155
156 if target_modules == null then target_modules = [mmodule]
157 if importations == null then importations = target_modules
158
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
165 """
166
167 for importation in importations do
168 nit_module.imports.add importation.name
169 end
170
171 nit_module.imports.add "serialization"
172
173 nit_module.content.add """
174 redef class Deserializer
175 redef fun deserialize_class(name)
176 do"""
177
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}}}"""
183
184 var rta = modelbuilder.do_rapid_type_analysis(m)
185
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
193
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
200
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
204 end
205
206 nit_module.imports.add "intrude import {to_import}"
207 end
208 end
209
210 compiled_types.add mtype
211 nit_module.content.add """
212 if name == \"{{{mtype}}}\" then return new {{{mtype}}}.from_deserializer(self)"""
213 end
214 end
215 end
216
217 nit_module.content.add """
218 return super
219 end
220 end"""
221
222 # Compile support module
223 if module_path != null then
224 # To file
225 nit_module.write_to_file module_path
226 else
227 # To stdout
228 nit_module.write_to stdout
229 end
230 end