README: document nit_env.sh
[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 frontend
29 import rapid_type_analysis
30 import template
31
32 # A Nit module
33 #
34 # TODO add more features and move to lib
35 class NitModule
36 super Template
37
38 var header: nullable Writable = null
39
40 # The module's name
41 var name: Writable
42
43 # Imports from this module
44 var imports = new Array[Writable]
45
46 # Main content of this module
47 var content = new Array[Writable]
48
49 redef fun rendering
50 do
51 var header = header
52 if header != null then add header
53
54 var name = name
55 add "module {name}\n\n"
56
57 for i in imports do add "import {i}\n"
58 add "\n"
59
60 for l in content do add "{l}\n"
61 end
62 end
63
64 redef class ToolContext
65 # Where do we put a single result?
66 var opt_output: OptionString = new OptionString("Output file (can also be 'stdout')", "-o", "--output")
67
68 # Where do we put the result?
69 var opt_dir: OptionString = new OptionString("Output directory", "--dir")
70
71 # Depth of the visit and generation
72 var opt_depth = new OptionEnum(["module", "group", "package"],
73 "Depth of the visit and generation", 0, "-d", "--depth")
74
75 redef init
76 do
77 option_context.add_option(opt_output, opt_dir, opt_depth)
78 super
79 end
80 end
81
82 redef class MModule
83 # Get the type of the class `Serializable`
84 var serializable_type: MClassType is lazy do
85 return self.get_primitive_class("Serializable").mclass_type
86 end
87 end
88
89 redef class MType
90 # Is this type fully visible from `mmodule`?
91 fun is_visible_from(mmodule: MModule): Bool is abstract
92 end
93
94 redef class MClassType
95 redef fun is_visible_from(mmodule) do
96 return mmodule.is_visible(mclass.intro_mmodule, mclass.visibility)
97 end
98 end
99
100 redef class MNullableType
101 redef fun is_visible_from(mmodule) do return mtype.is_visible_from(mmodule)
102 end
103
104 redef class MGenericType
105 redef fun is_visible_from(mmodule)
106 do
107 for arg_mtype in arguments do if not arg_mtype.is_visible_from(mmodule) then return false
108 return super
109 end
110 end
111
112 var toolcontext = new ToolContext
113 toolcontext.tooldescription = """
114 Usage: nitserial [OPTION] program.nit [other_program.nit [...]]
115 Generates a serialization support module"""
116
117 toolcontext.process_options(args)
118 var arguments = toolcontext.option_context.rest
119
120 # Check options
121 if toolcontext.opt_output.value != null and toolcontext.opt_dir.value != null then
122 print "Error: cannot use both --dir and --output"
123 exit 1
124 end
125 if arguments.length > 1 and toolcontext.opt_output.value != null then
126 print "Error: --output needs a single source file. Do you prefer --dir?"
127 exit 1
128 end
129
130 var model = new Model
131 var modelbuilder = new ModelBuilder(model, toolcontext)
132
133 var mmodules = modelbuilder.parse_full(arguments)
134 modelbuilder.run_phases
135
136 # Create a distinct support module per target modules
137 for mmodule in mmodules do
138 # Name of the support module
139 var module_name
140
141 # Path to the support module
142 var module_path = toolcontext.opt_output.value
143 if module_path == null then
144 module_name = "{mmodule.name}_serial"
145 module_path = "{module_name}.nit"
146
147 var dir = toolcontext.opt_dir.value
148 if dir != null then module_path = dir.join_path(module_path)
149 else if module_path == "stdout" then
150 module_name = "{mmodule.name}_serial"
151 module_path = null
152 else if module_path.has_suffix(".nit") then
153 module_name = module_path.basename(".nit")
154 else
155 module_name = module_path.basename
156 module_path += ".nit"
157 end
158
159 var target_modules = null
160 var importations = null
161 var mgroup = mmodule.mgroup
162 if toolcontext.opt_depth.value == 1 and mgroup != null then
163 modelbuilder.scan_group mgroup
164 target_modules = mgroup.mmodules
165 else if toolcontext.opt_depth.value == 2 then
166 # package
167 target_modules = new Array[MModule]
168 importations = new Array[MModule]
169 if mgroup != null then
170 for g in mgroup.mpackage.mgroups do
171 target_modules.add_all g.mmodules
172 end
173
174 for g in mgroup.in_nesting.direct_smallers do
175 var dm = g.default_mmodule
176 if dm != null then
177 importations.add dm
178 end
179 end
180
181 for m in mgroup.mmodules do
182 importations.add m
183 end
184 end
185 end
186
187 if target_modules == null then target_modules = [mmodule]
188 if importations == null then importations = target_modules
189
190 var nit_module = new NitModule(module_name)
191 nit_module.header = """
192 # This file is generated by nitserial
193 # Do not modify, but you can redef
194 """
195
196 for importation in importations do
197 nit_module.imports.add importation.name
198 end
199
200 nit_module.imports.add "serialization"
201
202 nit_module.content.add """
203 redef class Deserializer
204 redef fun deserialize_class(name)
205 do"""
206
207 var serializable_type = mmodule.serializable_type
208 var compiled_types = new Array[MType]
209 for m in target_modules do
210 nit_module.content.add """
211 # Module: {{{m.to_s}}}"""
212
213 var rta = modelbuilder.do_rapid_type_analysis(m)
214
215 for mtype in rta.live_types do
216 # We are only interested in instanciated generics, subtypes of Serializable
217 # and which are visible.
218 if mtype isa MGenericType and
219 mtype.is_subtype(m, null, serializable_type) and
220 mtype.is_visible_from(mmodule) and
221 not compiled_types.has(mtype) then
222
223 compiled_types.add mtype
224 nit_module.content.add """
225 if name == \"{{{mtype}}}\" then return new {{{mtype}}}.from_deserializer(self)"""
226 end
227 end
228 end
229
230 nit_module.content.add """
231 return super
232 end
233 end"""
234
235 # Compile support module
236 if module_path != null then
237 # To file
238 nit_module.write_to_file module_path
239 else
240 # To stdout
241 nit_module.write_to stdout
242 end
243 end