X-Git-Url: http://nitlanguage.org diff --git a/src/loader.nit b/src/loader.nit index 9062d5d..967d335 100644 --- a/src/loader.nit +++ b/src/loader.nit @@ -15,6 +15,26 @@ # limitations under the License. # Loading of Nit source files +# +# The loader takes care of looking for module and projects in the file system, and the associated case of errors. +# The loading requires several steps: +# +# Identify: create an empty model entity associated to a name or a file path. +# Identification is used for instance when names are given in the command line. +# See `identify_module` and `identify_group`. +# +# Scan: visit directories and identify their contents. +# Scanning is done to enable the searching of modules in projects. +# See `scan_group` and `scan_full`. +# +# Parse: load the AST and associate it with the model entity. +# See `MModule::parse`. +# +# Import: means recursively load modules imported by a module. +# See `build_module_importation`. +# +# Load: means doing the full sequence: identify, parse and import. +# See `ModelBuilder::parse`, `ModelBuilder::parse_full`, `MModule::load` `ModelBuilder::load_module. module loader import modelbuilder_base @@ -22,13 +42,13 @@ import ini redef class ToolContext # Option --path - var opt_path = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path") + var opt_path = new OptionArray("Add an additional include path (may be used more than once)", "-I", "--path") # Option --only-metamodel var opt_only_metamodel = new OptionBool("Stop after meta-model processing", "--only-metamodel") # Option --only-parse - var opt_only_parse = new OptionBool("Only proceed to parse step of loaders", "--only-parse") + var opt_only_parse = new OptionBool("Only proceed to parse files", "--only-parse") redef init do @@ -73,8 +93,6 @@ redef class ModelBuilder for a in modules do var nmodule = self.load_module(a) if nmodule == null then continue # Skip error - # Load imported module - build_module_importation(nmodule) var mmodule = nmodule.mmodule if mmodule == null then continue # skip error mmodules.add mmodule @@ -86,58 +104,24 @@ redef class ModelBuilder if toolcontext.opt_only_parse.value then self.toolcontext.info("*** ONLY PARSE...", 1) - exit(0) + self.toolcontext.quit end return mmodules.to_a end - # Load recursively all modules of the group `mgroup`. - # See `parse` for details. - fun parse_group(mgroup: MGroup): Array[MModule] - do - var res = new Array[MModule] - scan_group(mgroup) - for mg in mgroup.in_nesting.smallers do - for mp in mg.module_paths do - var nmodule = self.load_module(mp.filepath) - if nmodule == null then continue # Skip error - # Load imported module - build_module_importation(nmodule) - var mmodule = nmodule.mmodule - if mmodule == null then continue # Skip error - res.add mmodule - end - end - return res - end - - # Load a bunch of modules and groups. - # - # Each name can be: + # Identify a bunch of modules and groups. # - # * a path to a module, a group or a directory of packages. - # * a short name of a module or a group that are looked in the `paths` (-I) - # - # Then, for each entry, if it is: - # - # * a module, then is it parser and returned. - # * a group then recursively all its modules are parsed. - # * a directory of packages then all the modules of all packages are parsed. - # * else an error is displayed. - # - # See `parse` for details. - fun parse_full(names: Sequence[String]): Array[MModule] + # This does the same as `parse_full` but does only the identification (cf. `identify_module`) + fun scan_full(names: Sequence[String]): Array[MModule] do - var time0 = get_time - # Parse and recursively load - self.toolcontext.info("*** PARSE ***", 1) - var mmodules = new ArraySet[MModule] + var mmodules = new Array[MModule] for a in names do # Case of a group (root or sub-directory) var mgroup = self.identify_group(a) if mgroup != null then - mmodules.add_all parse_group(mgroup) + scan_group(mgroup) + for mg in mgroup.in_nesting.smallers do mmodules.add_all mg.mmodules continue end @@ -146,21 +130,18 @@ redef class ModelBuilder if stat != null and stat.is_dir then self.toolcontext.info("look in directory {a}", 2) var fs = a.files + alpha_comparator.sort(fs) # Try each entry as a group or a module for f in fs do var af = a/f mgroup = identify_group(af) if mgroup != null then - mmodules.add_all parse_group(mgroup) + scan_group(mgroup) + for mg in mgroup.in_nesting.smallers do mmodules.add_all mg.mmodules continue end - var mp = identify_file(af) - if mp != null then - var nmodule = self.load_module(af) - if nmodule == null then continue # Skip error - build_module_importation(nmodule) - var mmodule = nmodule.mmodule - if mmodule == null then continue # Skip error + var mmodule = identify_module(af) + if mmodule != null then mmodules.add mmodule else self.toolcontext.info("ignore file {af}", 2) @@ -169,12 +150,46 @@ redef class ModelBuilder continue end - var nmodule = self.load_module(a) - if nmodule == null then continue # Skip error - # Load imported module - build_module_importation(nmodule) - var mmodule = nmodule.mmodule - if mmodule == null then continue # Skip error + var mmodule = identify_module(a) + if mmodule == null then + if a.file_exists then + toolcontext.error(null, "Error: `{a}` is not a Nit source file.") + else + toolcontext.error(null, "Error: cannot find module `{a}`.") + end + continue + end + + mmodules.add mmodule + end + return mmodules + end + + # Load a bunch of modules and groups. + # + # Each name can be: + # + # * a path to a module, a group or a directory of packages. + # * a short name of a module or a group that are looked in the `paths` (-I) + # + # Then, for each entry, if it is: + # + # * a module, then is it parsed and returned. + # * a group then recursively all its modules are parsed. + # * a directory of packages then all the modules of all packages are parsed. + # * else an error is displayed. + # + # See `parse` for details. + fun parse_full(names: Sequence[String]): Array[MModule] + do + var time0 = get_time + # Parse and recursively load + self.toolcontext.info("*** PARSE ***", 1) + var mmodules = new ArraySet[MModule] + var scans = scan_full(names) + for mmodule in scans do + var ast = mmodule.load(self) + if ast == null then continue # Skip error mmodules.add mmodule end var time1 = get_time @@ -184,7 +199,7 @@ redef class ModelBuilder if toolcontext.opt_only_parse.value then self.toolcontext.info("*** ONLY PARSE...", 1) - exit(0) + self.toolcontext.quit end return mmodules.to_a @@ -228,7 +243,9 @@ redef class ModelBuilder end end - var candidate = search_module_in_paths(anode.hot_location, name, lookpaths) + var loc = null + if anode != null then loc = anode.hot_location + var candidate = search_module_in_paths(loc, name, lookpaths) if candidate == null then if mgroup != null then @@ -364,9 +381,9 @@ redef class ModelBuilder if mgroup == null then # singleton package - var mpackage = new MPackage(pn, model) - mgroup = new MGroup(pn, mpackage, null) # same name for the root group - mgroup.filepath = path + var loc = new Location.opaque_file(path) + var mpackage = new MPackage(pn, model, loc) + mgroup = new MGroup(pn, loc, mpackage, null) # same name for the root group mpackage.root = mgroup toolcontext.info("found singleton package `{pn}` at {path}", 2) @@ -378,10 +395,8 @@ redef class ModelBuilder end end - var src = new SourceFile.from_string(path, "") - var loc = new Location(src, 0, 0, 0, 0) + var loc = new Location.opaque_file(path) var res = new MModule(model, mgroup, pn, loc) - res.filepath = path identified_modules_by_path[rp] = res identified_modules_by_path[path] = res @@ -466,17 +481,18 @@ redef class ModelBuilder end end + var loc = new Location.opaque_file(dirpath) var mgroup if parent == null then # no parent, thus new package if ini != null then pn = ini["package.name"] or else pn - var mpackage = new MPackage(pn, model) - mgroup = new MGroup(pn, mpackage, null) # same name for the root group + var mpackage = new MPackage(pn, model, loc) + mgroup = new MGroup(pn, loc, mpackage, null) # same name for the root group mpackage.root = mgroup toolcontext.info("found package `{mpackage}` at {dirpath}", 2) mpackage.ini = ini else - mgroup = new MGroup(pn, parent.mpackage, parent) + mgroup = new MGroup(pn, loc, parent.mpackage, parent) toolcontext.info("found sub group `{mgroup.full_name}` at {dirpath}", 2) end @@ -490,7 +506,6 @@ redef class ModelBuilder mdoc.original_mentity = mgroup end - mgroup.filepath = dirpath mgroups[rdp] = mgroup return mgroup end @@ -516,12 +531,12 @@ redef class ModelBuilder return mdoc end - # Force the identification of all ModulePath of the group and sub-groups in the file system. + # Force the identification of all MModule of the group and sub-groups in the file system. # # When a group is scanned, its sub-groups hierarchy is filled (see `MGroup::in_nesting`) - # and the potential modules (and nested modules) are identified (see `MGroup::module_paths`). + # and the potential modules (and nested modules) are identified (see `MGroup::modules`). # - # Basically, this recursively call `get_mgroup` and `identify_file` on each directory entry. + # Basically, this recursively call `identify_group` and `identify_module` on each directory entry. # # No-op if the group was already scanned (see `MGroup::scanned`). fun scan_group(mgroup: MGroup) do @@ -530,7 +545,9 @@ redef class ModelBuilder var p = mgroup.filepath # a virtual group has nothing to scan if p == null then return - for f in p.files do + var files = p.files + alpha_comparator.sort(files) + for f in files do var fp = p/f var g = identify_group(fp) # Recursively scan for groups of the same package @@ -549,6 +566,10 @@ redef class ModelBuilder # Try to load a module AST using a path. # Display an error if there is a problem (IO / lexer / parser) and return null + # + # The AST is loaded as is total independence of the model and its entities. + # + # AST are not cached or reused thus a new AST is returned on success. fun load_module_ast(filename: String): nullable AModule do if not filename.has_suffix(".nit") then @@ -589,6 +610,11 @@ redef class ModelBuilder var keep = new Array[String] var res = new Array[String] for a in args do + var stat = a.to_path.stat + if stat != null and stat.is_dir then + res.add a + continue + end var l = identify_module(a) if l == null then keep.add a @@ -634,6 +660,7 @@ redef class ModelBuilder var mmodule = new MModule(model, mgroup, mod_name, nmodule.location) nmodule.mmodule = mmodule nmodules.add(nmodule) + parsed_modules.add mmodule self.mmodule2nmodule[mmodule] = nmodule if parent!= null then @@ -692,8 +719,6 @@ redef class ModelBuilder var mdoc = ndoc.to_mdoc mmodule.mdoc = mdoc mdoc.original_mentity = mmodule - else - advice(decl, "missing-doc", "Documentation warning: Undocumented module `{mmodule}`") end # Is the module a test suite? mmodule.is_test_suite = not decl.get_annotations("test_suite").is_empty @@ -780,7 +805,10 @@ redef class ModelBuilder # Analyze the module importation and fill the module_importation_hierarchy # - # Unless you used `load_module`, the importation is already done and this method does a no-op. + # If the importation was already done (`nmodule.is_importation_done`), this method does a no-op. + # + # REQUIRE `nmodule.mmodule != null` + # ENSURE `nmodule.is_importation_done` fun build_module_importation(nmodule: AModule) do if nmodule.is_importation_done then return @@ -864,7 +892,7 @@ redef class ModelBuilder end # The rule - var rule = new Array[Object] + var rule = new Array[MModule] # First element is the goal, thus rule.add suppath @@ -922,14 +950,11 @@ redef class ModelBuilder # It means that the first module is the module to automatically import. # The remaining modules are the conditions of the rule. # - # Each module is either represented by a MModule (if the module is already loaded) - # or by a ModulePath (if the module is not yet loaded). - # # Rules are declared by `build_module_importation` and are applied by `apply_conditional_importations` # (and `build_module_importation` that calls it). # # TODO (when the loader will be rewritten): use a better representation and move up rules in the model. - private var conditional_importations = new Array[SequenceRead[Object]] + private var conditional_importations = new Array[SequenceRead[MModule]] # Extends the current importations according to imported rules about conditional importation fun apply_conditional_importations(mmodule: MModule) @@ -943,24 +968,16 @@ redef class ModelBuilder for ci in conditional_importations do # Check conditions for i in [1..ci.length[ do - var rule_element = ci[i] - # An element of a rule is either a MModule or a ModulePath - # We need the mmodule to resonate on the importation - var m - if rule_element isa MModule then - m = rule_element - else - abort - end + var m = ci[i] # Is imported? if not mmodule.in_importation.greaters.has(m) then continue label end # Still here? It means that all conditions modules are loaded and imported # Identify the module to automatically import - var suppath = ci.first.as(ModulePath) - var sup = load_module_path(suppath) - if sup == null then continue + var sup = ci.first + var ast = sup.load(self) + if ast == null then continue # Do nothing if already imported if mmodule.in_importation.greaters.has(sup) then continue label @@ -997,9 +1014,6 @@ redef class ModelBuilder end redef class MModule - # The path of the module source - var filepath: nullable String = null - # Force the parsing of the module using `modelbuilder`. # # If the module was already parsed, the existing ASI is returned. @@ -1023,6 +1037,7 @@ redef class MModule # build the mmodule nmodule.mmodule = self + self.location = nmodule.location modelbuilder.build_a_mmodule(mgroup, nmodule) modelbuilder.parsed_modules.add self @@ -1037,6 +1052,7 @@ redef class MModule var nmodule = parse(modelbuilder) if nmodule == null then return null + modelbuilder.build_module_importation(nmodule) return nmodule end end