X-Git-Url: http://nitlanguage.org diff --git a/src/loader.nit b/src/loader.nit index d1626cb..125866a 100644 --- a/src/loader.nit +++ b/src/loader.nit @@ -71,10 +71,12 @@ redef class ModelBuilder end var nit_dir = toolcontext.nit_dir - var libname = nit_dir/"lib" - if libname.file_exists then paths.add(libname) - libname = nit_dir/"contrib" - if libname.file_exists then paths.add(libname) + if nit_dir != null then + var libname = nit_dir/"lib" + if libname.file_exists then paths.add(libname) + libname = nit_dir/"contrib" + if libname.file_exists then paths.add(libname) + end end # Load a bunch of modules. @@ -133,6 +135,7 @@ redef class ModelBuilder alpha_comparator.sort(fs) # Try each entry as a group or a module for f in fs do + if f.first == '.' then continue var af = a/f mgroup = identify_group(af) if mgroup != null then @@ -152,7 +155,10 @@ redef class ModelBuilder var mmodule = identify_module(a) if mmodule == null then - if a.file_exists then + var le = last_loader_error + if le != null then + toolcontext.error(null, le) + else 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}`.") @@ -322,6 +328,14 @@ redef class ModelBuilder # A parsed module exists in the model but might be not yet analysed (no importation). var parsed_modules = new Array[MModule] + # Some `loader` services are silent and return `null` on error. + # + # Those services can set `last_loader_error` to precise an specific error message. + # if `last_loader_error == null` then a generic error message can be used. + # + # See `identified_modules` and `identify_group` for details. + var last_loader_error: nullable String = null + # Identify a source file and load the associated package and groups if required. # # This method does what the user expects when giving an argument to a Nit tool. @@ -334,13 +348,16 @@ redef class ModelBuilder # then the main module of the package `digraph` is searched in `paths` and returned. # # Silently return `null` if `path` does not exists or cannot be identified. + # If `null` is returned, `last_loader_error` can be set to a specific error message. # # On success, it returns a module that is possibly not yet parsed (no AST), or not yet analysed (no importation). # If the module was already identified, or loaded, it is returned. fun identify_module(path: String): nullable MModule do + last_loader_error = null + # special case for not a nit file - if not path.has_suffix(".nit") then + if not path.has_suffix(".nit") then do # search dirless files in known -I paths if not path.chars.has('/') then var res = search_module_in_paths(null, path, self.paths) @@ -348,19 +365,66 @@ redef class ModelBuilder end # Found nothing? maybe it is a group... - var candidate = null if path.file_exists then var mgroup = identify_group(path) if mgroup != null then var owner_path = mgroup.filepath.join_path(mgroup.name + ".nit") - if owner_path.file_exists then candidate = owner_path + if owner_path.file_exists then + path = owner_path + break + end end end - if candidate == null then - return null + # Found nothing? maybe it is a qualified name + if path.chars.has(':') then + var ids = path.split("::") + var g = identify_group(ids.first) + if g != null then + scan_group(g) + var ms = g.mmodules_by_name(ids.last) + + # Return exact match + for m in ms do + if m.full_name == path then + return m + end + end + + # Where there is only one or two names `foo::bar` + # then accept module that matches `foo::*::bar` + if ids.length <= 2 then + if ms.length == 1 then return ms.first + if ms.length > 1 then + var l = new Array[String] + for m in ms do + var fp = m.filepath + if fp != null then fp = " ({fp})" else fp = "" + l.add "`{m.full_name}`{fp}" + end + last_loader_error = "Error: conflicting module for `{path}`: {l.join(", ")} " + return null + end + end + + var bests = new BestDistance[String](path.length / 2) + # We found nothing. But propose something in the package? + for sg in g.mpackage.mgroups do + for m in sg.mmodules do + var d = path.levenshtein_distance(m.full_name) + bests.update(d, m.full_name) + end + end + var last_loader_error = "Error: cannot find module `{path}`." + if bests.best_items.not_empty then + last_loader_error += " Did you mean " + bests.best_items.join(", ", " or ") + "?" + end + self.last_loader_error = last_loader_error + return null + end end - path = candidate + + return null end # Does the file exists? @@ -417,12 +481,19 @@ redef class ModelBuilder # Return the mgroup associated to a directory path. # If the directory is not a group null is returned. # + # Silently return `null` if `dirpath` does not exists, is not a directory, + # cannot be identified or cannot be attached to a mpackage. + # If `null` is returned, `last_loader_error` can be set to a specific error message. + # # Note: `paths` is also used to look for mgroups fun identify_group(dirpath: String): nullable MGroup do + # Reset error + last_loader_error = null + var stat = dirpath.file_stat - if stat == null then do + if stat == null or not stat.is_dir then do # search dirless directories in known -I paths if dirpath.chars.has('/') then return null for p in paths do @@ -438,6 +509,7 @@ redef class ModelBuilder # Filter out non-directories if not stat.is_dir then + last_loader_error = "Error: `{dirpath}` is not a directory." return null end @@ -464,6 +536,7 @@ redef class ModelBuilder # The root of the directory hierarchy in the file system. if rdp == "/" then mgroups[rdp] = null + last_loader_error = "Error: `{dirpath}` is not a Nit package." return null end @@ -471,6 +544,7 @@ redef class ModelBuilder if (dirpath/"packages.ini").file_exists then # dirpath cannot be a package since it is a package directory mgroups[rdp] = null + last_loader_error = "Error: `{dirpath}` is not a Nit package." return null end @@ -490,6 +564,7 @@ redef class ModelBuilder if parent == null then # Parent is not a group, thus we are not a group either mgroups[rdp] = null + last_loader_error = "Error: `{dirpath}` is not a Nit package." return null end end @@ -562,6 +637,7 @@ redef class ModelBuilder var files = p.files alpha_comparator.sort(files) for f in files do + if f.first == '.' then continue var fp = p/f var g = identify_group(fp) # Recursively scan for groups of the same package @@ -651,7 +727,10 @@ redef class ModelBuilder # Look for the module var mmodule = identify_module(filename) if mmodule == null then - if filename.file_exists then + var le = last_loader_error + if le != null then + toolcontext.error(null, le) + else 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}`.") @@ -665,7 +744,7 @@ redef class ModelBuilder # Injection of a new module without source. # Used by the interpreter. - fun load_rt_module(parent: nullable MModule, nmodule: AModule, mod_name: String): nullable AModule + fun load_rt_module(parent: nullable MModule, nmodule: AModule, mod_name: String): nullable MModule do # Create the module @@ -682,11 +761,10 @@ redef class ModelBuilder imported_modules.add(parent) mmodule.set_visibility_for(parent, intrude_visibility) mmodule.set_imported_mmodules(imported_modules) - else - build_module_importation(nmodule) end + build_module_importation(nmodule) - return nmodule + return mmodule end # Visit the AST and create the `MModule` object @@ -734,8 +812,8 @@ redef class ModelBuilder mmodule.mdoc = mdoc mdoc.original_mentity = mmodule end - # Is the module a test suite? - mmodule.is_test_suite = not decl.get_annotations("test_suite").is_empty + # Is the module generated? + mmodule.is_generated = not decl.get_annotations("generated").is_empty end end @@ -968,7 +1046,7 @@ redef class ModelBuilder # (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[MModule]] + var conditional_importations = new Array[SequenceRead[MModule]] # Extends the current importations according to imported rules about conditional importation fun apply_conditional_importations(mmodule: MModule) @@ -984,7 +1062,7 @@ redef class ModelBuilder for i in [1..ci.length[ do var m = ci[i] # Is imported? - if not mmodule.in_importation.greaters.has(m) then continue label + if mmodule == m or not mmodule.in_importation.greaters.has(m) then continue label end # Still here? It means that all conditions modules are loaded and imported