# 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
# 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
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)
+
+ 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
- mmodules.add(nmodule.mmodule.as(not null))
+ # 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)
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
# 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
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
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`.
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
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
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
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)
# 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
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
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
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
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
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.
#
# 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
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
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
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
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)
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)
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
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
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:
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