Merge: doc: fixed some typos and other misc. corrections
[nit.git] / src / nitserial.nit
index 5ad0aed..2b347f1 100644 (file)
 #
 # Executing on the module `game_logic` will create the module `game_logic_serial`
 # in the local directory. Mixing the generated module to the main module with
-# `nitg game_logic.nit -m game_logic_serial` will create a program supporting
+# `nitc game_logic.nit -m game_logic_serial` will create a program supporting
 # deserialization of all generic types visible from the main module.
 #
 # Because the generation is limited to the visible types, a module author might want
 # generate and include its own serialization support module.
 module nitserial
 
-import frontend
-import rapid_type_analysis
-import model_utils
 import template
+import gen_nit
 
-# A Nit module
-#
-# TODO add more features and move to lib
-class NitModule
-       super Template
-
-       var header: nullable Streamable = null
-
-       # The module's name
-       var name: Streamable
-
-       # Imports from this module
-       var imports = new Array[Streamable]
-
-       # Main content of this module
-       var content = new Array[Streamable]
-
-       redef fun rendering
-       do
-               var header = header
-               if header != null then add header
-
-               var name = name
-               if name != null then add "module {name}\n\n"
-
-               for i in imports do add "import {i}\n"
-               add "\n"
-
-               for l in content do add "{l}\n"
-       end
-end
+import frontend
+import rapid_type_analysis
 
 redef class ToolContext
        # Where do we put a single result?
@@ -69,40 +38,43 @@ redef class ToolContext
        # Where do we put the result?
        var opt_dir: OptionString = new OptionString("Output directory", "--dir")
 
+       # Depth of the visit and generation
+       var opt_depth = new OptionEnum(["module", "group", "package"],
+               "Depth of the visit and generation", 0, "-d", "--depth")
+
        redef init
        do
-               option_context.add_option(opt_output, opt_dir)
+               option_context.add_option(opt_output, opt_dir, opt_depth)
                super
        end
 end
 
 redef class MModule
        # Get the type of the class `Serializable`
-       fun serializable_type: MClassType is cached do
+       var serializable_type: MClassType is lazy do
                return self.get_primitive_class("Serializable").mclass_type
        end
 end
 
 redef class MType
-       # Is this type fully visible from `mmodule`?
-       fun is_visible_from(mmodule: MModule): Bool is abstract
+       # List classes composing this type
+       private fun related_mclasses(mmodule: MModule): Array[MClass] is abstract
 end
 
 redef class MClassType
-       redef fun is_visible_from(mmodule) do
-               return mmodule.is_visible(mclass.intro_mmodule, public_visibility)
-       end
+       redef fun related_mclasses(mmodule) do return [mclass]
 end
 
-redef class MNullableType
-       redef fun is_visible_from(mmodule) do return mtype.is_visible_from(mmodule)
+redef class MProxyType
+       redef fun related_mclasses(mmodule) do return undecorate.related_mclasses(mmodule)
 end
 
 redef class MGenericType
-       redef fun is_visible_from(mmodule)
+       redef fun related_mclasses(mmodule)
        do
-               for arg_mtype in arguments do if not arg_mtype.is_visible_from(mmodule) then return false
-               return super
+               var mods = super
+               for arg_mtype in arguments do mods.add_all(arg_mtype.related_mclasses(mmodule))
+               return mods
        end
 end
 
@@ -127,13 +99,11 @@ end
 var model = new Model
 var modelbuilder = new ModelBuilder(model, toolcontext)
 
-var mmodules = modelbuilder.parse(arguments)
+var mmodules = modelbuilder.parse_full(arguments)
 modelbuilder.run_phases
 
-# Create a distinct support module per targetted modules
+# Create a distinct support module per target modules
 for mmodule in mmodules do
-       var rta = modelbuilder.do_rapid_type_analysis(mmodule)
-
        # Name of the support module
        var module_name
 
@@ -151,17 +121,53 @@ for mmodule in mmodules do
        else if module_path.has_suffix(".nit") then
                module_name = module_path.basename(".nit")
        else
-               module_name = module_path.basename("")
+               module_name = module_path.basename
                module_path += ".nit"
        end
 
+       var target_modules = null
+       var importations = null
+       var mgroup = mmodule.mgroup
+       if toolcontext.opt_depth.value == 1 and mgroup != null then
+               modelbuilder.scan_group mgroup
+               target_modules = mgroup.mmodules
+       else if toolcontext.opt_depth.value == 2 then
+               # package
+               target_modules = new Array[MModule]
+               importations = new Array[MModule]
+               if mgroup != null then
+                       for g in mgroup.mpackage.mgroups do
+                               target_modules.add_all g.mmodules
+                       end
+
+                       for g in mgroup.in_nesting.direct_smallers do
+                               var dm = g.default_mmodule
+                               if dm != null then
+                                       importations.add dm
+                               end
+                       end
+
+                       for m in mgroup.mmodules do
+                               importations.add m
+                       end
+               end
+       end
+
+       if target_modules == null then target_modules = [mmodule]
+       if importations == null then importations = target_modules
+
        var nit_module = new NitModule(module_name)
+       nit_module.annotations.add """generated"""
+       nit_module.annotations.add """no_warning("property-conflict")"""
        nit_module.header = """
 # This file is generated by nitserial
 # Do not modify, but you can redef
 """
 
-       nit_module.imports.add mmodule.name
+       for importation in importations do
+               nit_module.imports.add importation.name
+       end
+
        nit_module.imports.add "serialization"
 
        nit_module.content.add """
@@ -170,15 +176,41 @@ redef class Deserializer
        do"""
 
        var serializable_type = mmodule.serializable_type
-       for mtype in rta.live_types do
-               # We are only interested in instanciated generics, subtypes of Serializable
-               # and which are visibles.
-               if mtype isa MGenericType and
-                  mtype.is_subtype(mmodule, null, serializable_type) and
-                  mtype.is_visible_from(mmodule) then
-
-                       nit_module.content.add """
+       var compiled_types = new Array[MType]
+       for m in target_modules do
+               nit_module.content.add """
+               # Module: {{{m.to_s}}}"""
+
+               var rta = modelbuilder.do_rapid_type_analysis(m)
+
+               for mtype in rta.live_types do
+                       # We are only interested in instanciated generics, subtypes of Serializable
+                       # and which are visible.
+                       if mtype isa MGenericType and
+                          mtype.is_subtype(m, null, serializable_type) and
+                          mtype.mclass.kind == concrete_kind and
+                          not compiled_types.has(mtype) then
+
+                               # Intrude import the modules declaring private classes
+                               var related_mclasses = mtype.related_mclasses(mmodule)
+                               for mclass in related_mclasses do
+                                       if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then
+                                               var intro_mmodule = mclass.intro_mmodule
+                                               var intro_mgroup = intro_mmodule.mgroup
+
+                                               var to_import = intro_mmodule.full_name
+                                               if intro_mgroup == null or intro_mgroup.default_mmodule == intro_mmodule then
+                                                       to_import = intro_mmodule.name
+                                               end
+
+                                               nit_module.imports.add "intrude import {to_import}"
+                                       end
+                               end
+
+                               compiled_types.add mtype
+                               nit_module.content.add """
                if name == \"{{{mtype}}}\" then return new {{{mtype}}}.from_deserializer(self)"""
+                       end
                end
        end