X-Git-Url: http://nitlanguage.org diff --git a/src/loader.nit b/src/loader.nit index 8106e20..0e6878b 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,7 +70,109 @@ redef class ModelBuilder for a in modules do var nmodule = self.load_module(a) if nmodule == null then continue # Skip error - mmodules.add(nmodule.mmodule.as(not null)) + # Load imported module + build_module_importation(nmodule) + var mmodule = nmodule.mmodule + if mmodule == null then continue # skip error + mmodules.add mmodule + 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) + 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: + # + # * a path to a module, a group or a directory of projects. + # * 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 projects then all the modules of all projects 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] + for a in names do + # Case of a group + var mgroup = self.get_mgroup(a) + if mgroup != null then + mmodules.add_all parse_group(mgroup) + continue + end + + # Case of a directory that is not a group + var stat = a.to_path.stat + if stat != null and stat.is_dir then + self.toolcontext.info("look in directory {a}", 2) + var fs = a.files + # Try each entry as a group or a module + for f in fs do + var af = a/f + mgroup = get_mgroup(af) + if mgroup != null then + mmodules.add_all parse_group(mgroup) + 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 + mmodules.add mmodule + else + self.toolcontext.info("ignore file {af}", 2) + end + end + 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 + mmodules.add mmodule end var time1 = get_time self.toolcontext.info("*** END PARSE: {time1-time0} ***", 2) @@ -86,15 +188,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 +228,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 @@ -142,9 +245,9 @@ redef class ModelBuilder if candidate == null then if mgroup != null then - error(anode, "Error: cannot find module {name} from {mgroup.name}. tried {lookpaths.join(", ")}") + error(anode, "Error: cannot find module `{name}` from `{mgroup.name}`. Tried: {lookpaths.join(", ")}.") else - error(anode, "Error: cannot find module {name}. tried {lookpaths.join(", ")}") + error(anode, "Error: cannot find module `{name}`. Tried: {lookpaths.join(", ")}.") end return null end @@ -161,7 +264,9 @@ 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 - return res.mmodule.as(not null) + # Load imported module + build_module_importation(res) + return res.mmodule end # Search a module `name` from path `lookpaths`. @@ -179,7 +284,7 @@ redef class ModelBuilder var abs_candidate = module_absolute_path(candidate) var abs_try_file = module_absolute_path(try_file) if abs_candidate != abs_try_file then - toolcontext.error(location, "Error: conflicting module file for {name}: {candidate} {try_file}") + toolcontext.error(location, "Error: conflicting module file for `{name}`: `{candidate}` `{try_file}`") end end end @@ -192,7 +297,7 @@ redef class ModelBuilder var abs_candidate = module_absolute_path(candidate) var abs_try_file = module_absolute_path(try_file) if abs_candidate != abs_try_file then - toolcontext.error(location, "Error: conflicting module file for {name}: {candidate} {try_file}") + toolcontext.error(location, "Error: conflicting module file for `{name}`: `{candidate}` `{try_file}`") end end end @@ -201,20 +306,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 +338,6 @@ redef class ModelBuilder end if candidate == null then - toolcontext.error(null, "Error: cannot find module `{path}`.") return null end path = candidate @@ -234,7 +346,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,28 +364,45 @@ 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] end - # Hack, a group is determined by: + # Hack, a group is determined by one of the following: # * the presence of a honomymous nit file # * the fact that the directory is named `src` + # * the fact that there is a sub-directory named `src` var pn = rdp.basename(".nit") var mp = dirpath.join_path(pn + ".nit").simplify_path + # dirpath2 is the root directory + # dirpath is the src subdirectory directory, if any, else it is the same that dirpath2 var dirpath2 = dirpath if not mp.file_exists then if pn == "src" then @@ -281,12 +410,17 @@ redef class ModelBuilder dirpath2 = rdp.dirname pn = dirpath2.basename("") else - return null + # Check a `src` subdirectory + dirpath = dirpath2 / "src" + if not dirpath.file_exists then + # All rules failed, so return null + return null + end end end # check parent directory - var parentpath = dirpath.join_path("..").simplify_path + var parentpath = dirpath2.join_path("..").simplify_path var parent = get_mgroup(parentpath) var mgroup @@ -300,22 +434,47 @@ redef class ModelBuilder mgroup = new MGroup(pn, parent.mproject, parent) toolcontext.info("found sub group `{mgroup.full_name}` at {dirpath}", 2) end - var readme = dirpath2.join_path("README.md") + + # search documentation + # in src first so the documentation of the project code can be distinct for the documentation of the project usage + var readme = dirpath.join_path("README.md") + if not readme.file_exists then readme = dirpath.join_path("README") + if not readme.file_exists then readme = dirpath2.join_path("README.md") 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) - while not s.eof do - mdoc.content.add(s.read_line) - end + var mdoc = load_markdown(readme) mgroup.mdoc = mdoc mdoc.original_mentity = mgroup end + mgroup.filepath = dirpath - mgroups[rdp] = mgroup + mgroups[module_absolute_path(dirpath)] = mgroup + mgroups[module_absolute_path(dirpath2)] = mgroup return mgroup end + # Load a markdown file as a documentation object + fun load_markdown(filepath: String): MDoc + do + var mdoc = new MDoc(new Location(new SourceFile.from_string(filepath, ""),0,0,0,0)) + var s = new FileReader.open(filepath) + while not s.eof do + mdoc.content.add(s.read_line) + end + return mdoc + 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 @@ -326,18 +485,18 @@ redef class ModelBuilder fun load_module_ast(filename: String): nullable AModule do if filename.file_extension != "nit" then - self.toolcontext.error(null, "Error: file {filename} is not a valid nit module.") + self.toolcontext.error(null, "Error: file `{filename}` is not a valid nit module.") return null end if not filename.file_exists then - self.toolcontext.error(null, "Error: file {filename} not found.") + self.toolcontext.error(null, "Error: file `{filename}` not found.") return null end 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 +514,44 @@ 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 + # Remove Nit source files from a list of arguments. + # + # Items of `args` that can be loaded as a nit file will be removed from `args` and returned. + fun filter_nit_source(args: Array[String]): Array[String] + do + var keep = new Array[String] + var res = new Array[String] + for a in args do + var l = identify_file(a) + if l == null then + keep.add a + else + res.add a + end + end + args.clear + args.add_all(keep) + return res + end + + # 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 + if filename.file_exists then + toolcontext.error(null, "Error: `{filename}` is not a Nit source file.") + else + toolcontext.error(null, "Error: cannot find module `{filename}`.") + end + return null + end # Already known and loaded? then return it var mmodule = file.mmodule @@ -382,14 +571,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,12 +604,23 @@ 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}") + error(decl.n_name, "Error: module name mismatch; 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 @@ -433,7 +630,14 @@ redef class ModelBuilder 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 +646,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 @@ -464,24 +672,33 @@ redef class ModelBuilder if aimport.n_name.n_quad != null then mgroup = null # Start from top level for grp in aimport.n_name.n_path do var path = search_mmodule_by_name(grp, mgroup, grp.text) - if path == null then return # Skip error + if path == null then + nmodule.mmodule = null # invalidate the module + return # Skip error + end mgroup = path.mgroup end var mod_name = aimport.n_name.n_id.text var sup = self.get_mmodule_by_name(aimport.n_name, mgroup, mod_name) - if sup == null then continue # Skip error + if sup == null then + nmodule.mmodule = null # invalidate the module + continue # Skip error + end aimport.mmodule = sup imported_modules.add(sup) var mvisibility = aimport.n_visibility.mvisibility if mvisibility == protected_visibility then error(aimport.n_visibility, "Error: only properties can be protected.") + nmodule.mmodule = null # invalidate the module return end if sup == mmodule then - error(aimport.n_name, "Error: Dependency loop in module {mmodule}.") + error(aimport.n_name, "Error: dependency loop in module {mmodule}.") + nmodule.mmodule = null # invalidate the module end if sup.in_importation < mmodule then - error(aimport.n_name, "Error: Dependency loop between modules {mmodule} and {sup}.") + error(aimport.n_name, "Error: dependency loop between modules {mmodule} and {sup}.") + nmodule.mmodule = null # invalidate the module return end mmodule.set_visibility_for(sup, mvisibility) @@ -489,7 +706,9 @@ redef class ModelBuilder if stdimport then var mod_name = "standard" var sup = self.get_mmodule_by_name(nmodule, null, mod_name) - if sup != null then # Skip error + if sup == null then + nmodule.mmodule = null # invalidate the module + else # Skip error imported_modules.add(sup) mmodule.set_visibility_for(sup, public_visibility) end @@ -521,12 +740,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 +773,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