X-Git-Url: http://nitlanguage.org diff --git a/src/loader.nit b/src/loader.nit index 8106e20..65e7c67 100644 --- a/src/loader.nit +++ b/src/loader.nit @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Load nit source files and build the associated model +# Loading of Nit source files module loader import modelbuilder_base @@ -60,7 +60,7 @@ redef class ModelBuilder # The result is the corresponding model elements. # Errors and warnings are printed with the toolcontext. # - # Note: class and property model element are not analysed. + # Note: class and property model elements are not analysed. fun parse(modules: Sequence[String]): Array[MModule] do var time0 = get_time @@ -70,6 +70,64 @@ 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) + + mmodules.add(nmodule.mmodule.as(not null)) + end + var time1 = get_time + self.toolcontext.info("*** END PARSE: {time1-time0} ***", 2) + + self.toolcontext.check_errors + + if toolcontext.opt_only_parse.value then + self.toolcontext.info("*** ONLY PARSE...", 1) + exit(0) + 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] + visit_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) + + res.add(nmodule.mmodule.as(not null)) + end + end + return res + end + + # Load a bunch of modules and groups. + # Each name can be a module or a group. + # If it is a group then recursively all its modules are parsed. + # 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] + for a in names do + var mgroup = self.get_mgroup(a) + if mgroup != null then + mmodules.add_all parse_group(mgroup) + continue + end + var nmodule = self.load_module(a) + if nmodule == null then continue # Skip error + # Load imported module + build_module_importation(nmodule) + mmodules.add(nmodule.mmodule.as(not null)) end var time1 = get_time @@ -86,15 +144,16 @@ redef class ModelBuilder end # The list of directories to search for top level modules - # The list is initially set with : + # The list is initially set with: + # # * the toolcontext --path option # * the NIT_PATH environment variable # * `toolcontext.nit_dir` # Path can be added (or removed) by the client var paths = new Array[String] - # Like (an used by) `get_mmodule_by_name` but just return the ModulePath - private fun search_mmodule_by_name(anode: nullable ANode, mgroup: nullable MGroup, name: String): nullable ModulePath + # Like (and used by) `get_mmodule_by_name` but just return the ModulePath + fun search_mmodule_by_name(anode: nullable ANode, mgroup: nullable MGroup, name: String): nullable ModulePath do # First, look in groups var c = mgroup @@ -125,7 +184,7 @@ redef class ModelBuilder # Look at some known directories var lookpaths = self.paths - # Look in the directory of the group project also (even if not explicitely in the path) + # Look in the directory of the group project also (even if not explicitly in the path) if mgroup != null then # path of the root group var dirname = mgroup.mproject.root.filepath @@ -161,6 +220,8 @@ redef class ModelBuilder if path == null then return null # Forward error var res = self.load_module(path.filepath) if res == null then return null # Forward error + # Load imported module + build_module_importation(res) return res.mmodule.as(not null) end @@ -201,20 +262,28 @@ redef class ModelBuilder return identify_file(candidate) end - # cache for `identify_file` by realpath - private var identified_files = new HashMap[String, nullable ModulePath] + # Cache for `identify_file` by realpath + private var identified_files_by_path = new HashMap[String, nullable ModulePath] + + # All the currently identified modules. + # See `identify_file`. + var identified_files = new Array[ModulePath] # Identify a source file # Load the associated project and groups if required - private fun identify_file(path: String): nullable ModulePath + # + # Silently return `null` if `path` is not a valid module path. + fun identify_file(path: String): nullable ModulePath do # special case for not a nit file if path.file_extension != "nit" then - # search in known -I paths - var res = search_module_in_paths(null, path, self.paths) - if res != null then return res + # search dirless files in known -I paths + if path.dirname == "" then + var res = search_module_in_paths(null, path, self.paths) + if res != null then return res + end - # Found nothins? maybe it is a group... + # Found nothing? maybe it is a group... var candidate = null if path.file_exists then var mgroup = get_mgroup(path) @@ -225,7 +294,6 @@ redef class ModelBuilder end if candidate == null then - toolcontext.error(null, "Error: cannot find module `{path}`.") return null end path = candidate @@ -234,7 +302,7 @@ redef class ModelBuilder # Fast track, the path is already known var pn = path.basename(".nit") var rp = module_absolute_path(path) - if identified_files.has_key(rp) then return identified_files[rp] + if identified_files_by_path.has_key(rp) then return identified_files_by_path[rp] # Search for a group var mgrouppath = path.join_path("..").simplify_path @@ -252,17 +320,31 @@ redef class ModelBuilder var res = new ModulePath(pn, path, mgroup) mgroup.module_paths.add(res) - identified_files[rp] = res + identified_files_by_path[rp] = res + identified_files.add(res) return res end - # groups by path + # Groups by path private var mgroups = new HashMap[String, nullable MGroup] - # return the mgroup associated to a directory path - # if the directory is not a group null is returned - private fun get_mgroup(dirpath: String): nullable MGroup + # Return the mgroup associated to a directory path. + # If the directory is not a group null is returned. + # + # Note: `paths` is also used to look for mgroups + fun get_mgroup(dirpath: String): nullable MGroup do + if not dirpath.file_exists then do + for p in paths do + var try = p / dirpath + if try.file_exists then + dirpath = try + break label + end + end + return null + end label + var rdp = module_absolute_path(dirpath) if mgroups.has_key(rdp) then return mgroups[rdp] @@ -304,7 +386,7 @@ redef class ModelBuilder if not readme.file_exists then readme = dirpath2.join_path("README") if readme.file_exists then var mdoc = new MDoc - var s = new IFStream.open(readme) + var s = new FileReader.open(readme) while not s.eof do mdoc.content.add(s.read_line) end @@ -316,6 +398,17 @@ redef class ModelBuilder return mgroup end + # Force the identification of all ModulePath of the group and sub-groups. + fun visit_group(mgroup: MGroup) do + var p = mgroup.filepath + for f in p.files do + var fp = p/f + var g = get_mgroup(fp) + if g != null then visit_group(g) + identify_file(fp) + end + end + # Transform relative paths (starting with '../') into absolute paths private fun module_absolute_path(path: String): String do return getcwd.join_path(path).simplify_path @@ -337,7 +430,7 @@ redef class ModelBuilder self.toolcontext.info("load module {filename}", 2) # Load the file - var file = new IFStream.open(filename) + var file = new FileReader.open(filename) var lexer = new Lexer(new SourceFile(filename, file)) var parser = new Parser(lexer) var tree = parser.parse @@ -355,14 +448,20 @@ redef class ModelBuilder return nmodule end - # Try to load a module and its imported modules using a path. - # Display an error if there is a problem (IO / lexer / parser / importation) and return null + # Try to load a module using a path. + # Display an error if there is a problem (IO / lexer / parser) and return null. # Note: usually, you do not need this method, use `get_mmodule_by_name` instead. + # + # The MModule is created however, the importation is not performed, + # therefore you should call `build_module_importation`. fun load_module(filename: String): nullable AModule do # Look for the module var file = identify_file(filename) - if file == null then return null # forward error + if file == null then + toolcontext.error(null, "Error: cannot find module `{filename}`.") + return null + end # Already known and loaded? then return it var mmodule = file.mmodule @@ -382,14 +481,11 @@ redef class ModelBuilder # Update the file information file.mmodule = mmodule - # Load imported module - build_module_importation(nmodule) - return nmodule end # Injection of a new module without source. - # Used by the interpreted + # Used by the interpreter. fun load_rt_module(parent: nullable MModule, nmodule: AModule, mod_name: String): nullable AModule do # Create the module @@ -418,22 +514,40 @@ redef class ModelBuilder do # Check the module name var decl = nmodule.n_moduledecl - if decl == null then - #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY - else + if decl != null then var decl_name = decl.n_name.n_id.text if decl_name != mod_name then error(decl.n_name, "Error: module name missmatch; declared {decl_name} file named {mod_name}") end end + # Check for conflicting module names in the project + if mgroup != null then + var others = model.get_mmodules_by_name(mod_name) + if others != null then for other in others do + if other.mgroup!= null and other.mgroup.mproject == mgroup.mproject then + var node: ANode + if decl == null then node = nmodule else node = decl.n_name + error(node, "Error: A module named `{other.full_name}` already exists at {other.location}") + break + end + end + end + # Create the module var mmodule = new MModule(model, mgroup, mod_name, nmodule.location) nmodule.mmodule = mmodule nmodules.add(nmodule) self.mmodule2nmodule[mmodule] = nmodule + var source = nmodule.location.file + if source != null then + assert source.mmodule == null + source.mmodule = mmodule + end + if decl != null then + # Extract documentation var ndoc = decl.n_doc if ndoc != null then var mdoc = ndoc.to_mdoc @@ -442,13 +556,17 @@ redef class ModelBuilder 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 end return mmodule end - # Analysis the module importation and fill the module_importation_hierarchy - private fun build_module_importation(nmodule: AModule) + # 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. + fun build_module_importation(nmodule: AModule) do if nmodule.is_importation_done then return nmodule.is_importation_done = true @@ -521,12 +639,22 @@ redef class ModelBuilder var nmodules = new Array[AModule] # Register the nmodule associated to each mmodule - # FIXME: why not refine the `MModule` class with a nullable attribute? - var mmodule2nmodule = new HashMap[MModule, AModule] + # + # Public clients need to use `mmodule2node` to access stuff. + private var mmodule2nmodule = new HashMap[MModule, AModule] + + # Retrieve the associated AST node of a mmodule. + # This method is used to associate model entity with syntactic entities. + # + # If the module is not associated with a node, returns null. + fun mmodule2node(mmodule: MModule): nullable AModule + do + return mmodule2nmodule.get_or_null(mmodule) + end end -# placeholder to a module file identified but not always loaded in a project -private class ModulePath +# File-system location of a module (file) that is identified but not always loaded. +class ModulePath # The name of the module # (it's the basename of the filepath) var name: String @@ -544,8 +672,29 @@ private class ModulePath end redef class MGroup - # modules paths associated with the group - private var module_paths = new Array[ModulePath] + # Modules paths associated with the group + var module_paths = new Array[ModulePath] + + # Is the group interesting for a final user? + # + # Groups are mandatory in the model but for simple projects they are not + # always interesting. + # + # A interesting group has, at least, one of the following true: + # + # * it has 2 modules or more + # * it has a subgroup + # * it has a documentation + fun is_interesting: Bool + do + return module_paths.length > 1 or mmodules.length > 1 or not in_nesting.direct_smallers.is_empty or mdoc != null + end + +end + +redef class SourceFile + # Associated mmodule, once created + var mmodule: nullable MModule = null end redef class AStdImport