X-Git-Url: http://nitlanguage.org diff --git a/src/loader.nit b/src/loader.nit index 02fc621..312d02b 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 @@ -72,8 +72,107 @@ redef class ModelBuilder 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) - mmodules.add(nmodule.mmodule.as(not null)) + 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) @@ -89,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 @@ -128,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 @@ -166,7 +266,7 @@ redef class ModelBuilder if res == null then return null # Forward error # Load imported module build_module_importation(res) - return res.mmodule.as(not null) + return res.mmodule end # Search a module `name` from path `lookpaths`. @@ -206,22 +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 # # Silently return `null` if `path` is not a valid module path. - private fun identify_file(path: String): nullable ModulePath + 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) @@ -240,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 @@ -258,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 @@ -287,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 @@ -306,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 @@ -343,7 +496,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 @@ -361,7 +514,27 @@ redef class ModelBuilder return nmodule end - # Try to load a module modules using a path. + # 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. # @@ -372,7 +545,11 @@ redef class ModelBuilder # Look for the module var file = identify_file(filename) if file == null then - toolcontext.error(null, "Error: cannot find module `{filename}`.") + 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 @@ -398,7 +575,7 @@ redef class ModelBuilder 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 @@ -427,22 +604,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 @@ -451,12 +646,14 @@ 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 + # 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) @@ -532,12 +729,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 @@ -555,12 +762,12 @@ 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 + # 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: @@ -575,6 +782,11 @@ redef class MGroup end +redef class SourceFile + # Associated mmodule, once created + var mmodule: nullable MModule = null +end + redef class AStdImport # The imported module once determined var mmodule: nullable MModule = null