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