1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2012 Jean Privat <jean@pryen.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Load nit source files and build the associated model
21 # FIXME split this module into submodules
22 # FIXME add missing error checks
32 private import more_collections
36 redef class ToolContext
38 var opt_path
: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
40 # Option --only-metamodel
41 var opt_only_metamodel
: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
44 var opt_only_parse
: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
49 option_context
.add_option
(opt_path
, opt_only_parse
, opt_only_metamodel
)
52 fun modelbuilder
: ModelBuilder do return modelbuilder_real
.as(not null)
53 private var modelbuilder_real
: nullable ModelBuilder = null
57 # A model builder knows how to load nit source files and build the associated model
59 # The model where new modules, classes and properties are added
62 # The toolcontext used to control the interaction with the user (getting options and displaying messages)
63 var toolcontext
: ToolContext
65 # Run phases on all loaded modules
68 var mmodules
= model
.mmodules
.to_a
69 model
.mmodule_importation_hierarchy
.sort
(mmodules
)
70 var nmodules
= new Array[AModule]
72 nmodules
.add
(mmodule2nmodule
[mm
])
74 toolcontext
.run_phases
(nmodules
)
76 if toolcontext
.opt_only_metamodel
.value
then
77 self.toolcontext
.info
("*** ONLY METAMODEL", 1)
82 # Instantiate a modelbuilder for a model and a toolcontext
83 # Important, the options of the toolcontext must be correctly set (parse_option already called)
84 init(model
: Model, toolcontext
: ToolContext)
87 self.toolcontext
= toolcontext
88 assert toolcontext
.modelbuilder_real
== null
89 toolcontext
.modelbuilder_real
= self
91 # Setup the paths value
92 paths
.append
(toolcontext
.opt_path
.value
)
94 var path_env
= "NIT_PATH".environ
95 if not path_env
.is_empty
then
96 paths
.append
(path_env
.split_with
(':'))
99 path_env
= "NIT_DIR".environ
100 if not path_env
.is_empty
then
101 var libname
= "{path_env}/lib"
102 if libname
.file_exists
then paths
.add
(libname
)
105 var libname
= "{sys.program_name.dirname}/../lib"
106 if libname
.file_exists
then paths
.add
(libname
.simplify_path
)
109 # Load a bunch of modules.
110 # `modules` can contains filenames or module names.
111 # Imported modules are automatically loaded and modelized.
112 # The result is the corresponding model elements.
113 # Errors and warnings are printed with the toolcontext.
115 # Note: class and property model element are not analysed.
116 fun parse
(modules
: Sequence[String]): Array[MModule]
119 # Parse and recursively load
120 self.toolcontext
.info
("*** PARSE ***", 1)
121 var mmodules
= new Array[MModule]
123 var nmodule
= self.load_module
(null, a
)
124 if nmodule
== null then continue # Skip error
125 mmodules
.add
(nmodule
.mmodule
.as(not null))
128 self.toolcontext
.info
("*** END PARSE: {time1-time0} ***", 2)
130 self.toolcontext
.check_errors
132 if toolcontext
.opt_only_parse
.value
then
133 self.toolcontext
.info
("*** ONLY PARSE...", 1)
140 # Return a class named `name` visible by the module `mmodule`.
141 # Visibility in modules is correctly handled.
142 # If no such a class exists, then null is returned.
143 # If more than one class exists, then an error on `anode` is displayed and null is returned.
144 # FIXME: add a way to handle class name conflict
145 fun try_get_mclass_by_name
(anode
: ANode, mmodule
: MModule, name
: String): nullable MClass
147 var classes
= model
.get_mclasses_by_name
(name
)
148 if classes
== null then
152 var res
: nullable MClass = null
153 for mclass
in classes
do
154 if not mmodule
.in_importation
<= mclass
.intro_mmodule
then continue
155 if not mmodule
.is_visible
(mclass
.intro_mmodule
, mclass
.visibility
) then continue
159 error
(anode
, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
166 # Return a property named `name` on the type `mtype` visible in the module `mmodule`.
167 # Visibility in modules is correctly handled.
168 # Protected properties are returned (it is up to the caller to check and reject protected properties).
169 # If no such a property exists, then null is returned.
170 # If more than one property exists, then an error on `anode` is displayed and null is returned.
171 # FIXME: add a way to handle property name conflict
172 fun try_get_mproperty_by_name2
(anode
: ANode, mmodule
: MModule, mtype
: MType, name
: String): nullable MProperty
174 var props
= self.model
.get_mproperties_by_name
(name
)
175 if props
== null then
179 var cache
= self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
]
180 if cache
!= null then return cache
182 var res
: nullable MProperty = null
183 var ress
: nullable Array[MProperty] = null
184 for mprop
in props
do
185 if not mtype
.has_mproperty
(mmodule
, mprop
) then continue
186 if not mmodule
.is_visible
(mprop
.intro_mclassdef
.mmodule
, mprop
.visibility
) then continue
190 var restype
= res
.intro_mclassdef
.bound_mtype
191 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
192 if restype
.is_subtype
(mmodule
, null, mproptype
) then
194 else if mproptype
.is_subtype
(mmodule
, null, restype
) then
197 if ress
== null then ress
= new Array[MProperty]
203 var restype
= res
.intro_mclassdef
.bound_mtype
205 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
206 if not restype
.is_subtype
(mmodule
, null, mproptype
) then
207 self.error
(anode
, "Ambigous property name '{name}' for {mtype}; conflict between {mprop.full_name} and {res.full_name}")
213 self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
] = res
217 private var try_get_mproperty_by_name2_cache
: HashMap3[MModule, MType, String, nullable MProperty] = new HashMap3[MModule, MType, String, nullable MProperty]
220 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
221 fun try_get_mproperty_by_name
(anode
: ANode, mclassdef
: MClassDef, name
: String): nullable MProperty
223 return try_get_mproperty_by_name2
(anode
, mclassdef
.mmodule
, mclassdef
.bound_mtype
, name
)
226 # The list of directories to search for top level modules
227 # The list is initially set with :
228 # * the toolcontext --path option
229 # * the NIT_PATH environment variable
230 # * some heuristics including the NIT_DIR environment variable and the progname of the process
231 # Path can be added (or removed) by the client
232 var paths
: Array[String] = new Array[String]
234 # Get a module by its short name; if required, the module is loaded, parsed and its hierarchies computed.
235 # If `mmodule` is set, then the module search starts from it up to the top level (see `paths`);
236 # if `mmodule` is null then the module is searched in the top level only.
237 # If no module exists or there is a name conflict, then an error on `anode` is displayed and null is returned.
238 # FIXME: add a way to handle module name conflict
239 fun get_mmodule_by_name
(anode
: ANode, mmodule
: nullable MModule, name
: String): nullable MModule
241 var origmmodule
= mmodule
242 var modules
= model
.get_mmodules_by_name
(name
)
244 var tries
= new Array[String]
246 var lastmodule
= mmodule
247 while mmodule
!= null do
248 var dirname
= mmodule
.location
.file
.filename
.dirname
250 # Determine the owner
251 var owner
: nullable MModule
252 if dirname
.basename
("") != mmodule
.name
then
253 owner
= mmodule
.direct_owner
258 # First, try the already known nested modules
259 if modules
!= null then
260 for candidate
in modules
do
261 if candidate
.direct_owner
== owner
then
267 # Second, try the directory to find a file
268 var try_file
= dirname
+ "/" + name
+ ".nit"
270 if try_file
.file_exists
then
271 var res
= self.load_module
(owner
, try_file
.simplify_path
)
272 if res
== null then return null # Forward error
273 return res
.mmodule
.as(not null)
276 # Third, try if the requested module is itself an owner
277 try_file
= dirname
+ "/" + name
+ "/" + name
+ ".nit"
278 if try_file
.file_exists
then
279 var res
= self.load_module
(owner
, try_file
.simplify_path
)
280 if res
== null then return null # Forward error
281 return res
.mmodule
.as(not null)
285 mmodule
= mmodule
.direct_owner
288 if modules
!= null then
289 for candidate
in modules
do
290 if candidate
.direct_owner
== null then
296 # Look at some known directories
297 var lookpaths
= self.paths
299 # Look in the directory of the last module also (event if not in the path)
300 if lastmodule
!= null then
301 var dirname
= lastmodule
.location
.file
.filename
.dirname
302 if dirname
.basename
("") == lastmodule
.name
then
303 dirname
= dirname
.dirname
305 if not lookpaths
.has
(dirname
) then
306 lookpaths
= lookpaths
.to_a
307 lookpaths
.add
(dirname
)
311 var candidate
: nullable String = null
312 for dirname
in lookpaths
do
313 var try_file
= (dirname
+ "/" + name
+ ".nit").simplify_path
315 if try_file
.file_exists
then
316 if candidate
== null then
318 else if candidate
!= try_file
then
319 # try to disambiguate conflicting modules
320 var abs_candidate
= module_absolute_path
(candidate
)
321 var abs_try_file
= module_absolute_path
(try_file
)
322 if abs_candidate
!= abs_try_file
then
323 error
(anode
, "Error: conflicting module file for {name}: {candidate} {try_file}")
327 try_file
= (dirname
+ "/" + name
+ "/" + name
+ ".nit").simplify_path
328 if try_file
.file_exists
then
329 if candidate
== null then
331 else if candidate
!= try_file
then
332 # try to disambiguate conflicting modules
333 var abs_candidate
= module_absolute_path
(candidate
)
334 var abs_try_file
= module_absolute_path
(try_file
)
335 if abs_candidate
!= abs_try_file
then
336 error
(anode
, "Error: conflicting module file for {name}: {candidate} {try_file}")
341 if candidate
== null then
342 if origmmodule
!= null then
343 error
(anode
, "Error: cannot find module {name} from {origmmodule}. tried {tries.join(", ")}")
345 error
(anode
, "Error: cannot find module {name}. tried {tries.join(", ")}")
349 var res
= self.load_module
(mmodule
, candidate
)
350 if res
== null then return null # Forward error
351 return res
.mmodule
.as(not null)
354 # Transform relative paths (starting with '../') into absolute paths
355 private fun module_absolute_path
(path
: String): String do
356 if path
.has_prefix
("..") then
357 return getcwd
.join_path
(path
).simplify_path
362 # loaded module by absolute path
363 private var loaded_nmodules
= new HashMap[String, AModule]
365 # Try to load a module using a path.
366 # Display an error if there is a problem (IO / lexer / parser) and return null
367 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
368 fun load_module
(owner
: nullable MModule, filename
: String): nullable AModule
370 if filename
.file_extension
!= "nit" then
371 self.toolcontext
.error
(null, "Error: file {filename} is not a valid nit module.")
374 if not filename
.file_exists
then
375 self.toolcontext
.error
(null, "Error: file {filename} not found.")
379 var module_path
= module_absolute_path
(filename
)
380 if loaded_nmodules
.keys
.has
(module_path
) then
381 return loaded_nmodules
[module_path
]
384 var x
= if owner
!= null then owner
.to_s
else "."
385 self.toolcontext
.info
("load module {filename} in {x}", 2)
388 var file
= new IFStream.open
(filename
)
389 var lexer
= new Lexer(new SourceFile(filename
, file
))
390 var parser
= new Parser(lexer
)
391 var tree
= parser
.parse
393 var mod_name
= filename
.basename
(".nit")
394 return load_module_commons
(owner
, tree
, mod_name
)
397 fun load_rt_module
(owner
: MModule, nmodule
: AModule, mod_name
: String): nullable AModule
400 var mmodule
= new MModule(model
, owner
, mod_name
, nmodule
.location
)
401 nmodule
.mmodule
= mmodule
402 nmodules
.add
(nmodule
)
403 self.mmodule2nmodule
[mmodule
] = nmodule
405 var imported_modules
= new Array[MModule]
407 imported_modules
.add
(owner
)
408 mmodule
.set_visibility_for
(owner
, intrude_visibility
)
410 mmodule
.set_imported_mmodules
(imported_modules
)
415 # Try to load a module using a path.
416 # Display an error if there is a problem (IO / lexer / parser) and return null
417 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
418 private fun load_module_commons
(owner
: nullable MModule, tree
: Start, mod_name
: String): nullable AModule
420 # Handle lexer and parser error
421 var nmodule
= tree
.n_base
422 if nmodule
== null then
423 var neof
= tree
.n_eof
424 assert neof
isa AError
425 error
(neof
, neof
.message
)
429 # Check the module name
430 var decl
= nmodule
.n_moduledecl
432 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
434 var decl_name
= decl
.n_name
.n_id
.text
435 if decl_name
!= mod_name
then
436 error
(decl
.n_name
, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
441 var mmodule
= new MModule(model
, owner
, mod_name
, nmodule
.location
)
442 nmodule
.mmodule
= mmodule
443 nmodules
.add
(nmodule
)
444 self.mmodule2nmodule
[mmodule
] = nmodule
446 build_module_importation
(nmodule
)
451 # Analysis the module importation and fill the module_importation_hierarchy
452 private fun build_module_importation
(nmodule
: AModule)
454 if nmodule
.is_importation_done
then return
455 nmodule
.is_importation_done
= true
456 var mmodule
= nmodule
.mmodule
.as(not null)
458 var imported_modules
= new Array[MModule]
459 for aimport
in nmodule
.n_imports
do
461 if not aimport
isa AStdImport then
464 var mod_name
= aimport
.n_name
.n_id
.text
465 var sup
= self.get_mmodule_by_name
(aimport
.n_name
, mmodule
, mod_name
)
466 if sup
== null then continue # Skip error
467 aimport
.mmodule
= sup
468 imported_modules
.add
(sup
)
469 var mvisibility
= aimport
.n_visibility
.mvisibility
470 if mvisibility
== protected_visibility
then
471 error
(aimport
.n_visibility
, "Error: only properties can be protected.")
474 mmodule
.set_visibility_for
(sup
, mvisibility
)
477 var mod_name
= "standard"
478 var sup
= self.get_mmodule_by_name
(nmodule
, null, mod_name
)
479 if sup
!= null then # Skip error
480 imported_modules
.add
(sup
)
481 mmodule
.set_visibility_for
(sup
, public_visibility
)
484 self.toolcontext
.info
("{mmodule} imports {imported_modules.join(", ")}", 3)
485 mmodule
.set_imported_mmodules
(imported_modules
)
488 # All the loaded modules
489 var nmodules
: Array[AModule] = new Array[AModule]
491 # Register the nmodule associated to each mmodule
492 # FIXME: why not refine the `MModule` class with a nullable attribute?
493 var mmodule2nmodule
: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
495 # Helper function to display an error on a node.
496 # Alias for `self.toolcontext.error(n.hot_location, text)`
497 fun error
(n
: ANode, text
: String)
499 self.toolcontext
.error
(n
.hot_location
, text
)
502 # Helper function to display a warning on a node.
503 # Alias for: `self.toolcontext.warning(n.hot_location, text)`
504 fun warning
(n
: ANode, text
: String)
506 self.toolcontext
.warning
(n
.hot_location
, text
)
509 # Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
510 fun force_get_primitive_method
(n
: ANode, name
: String, recv
: MClass, mmodule
: MModule): MMethod
512 var res
= mmodule
.try_get_primitive_method
(name
, recv
)
514 self.toolcontext
.fatal_error
(n
.hot_location
, "Fatal Error: {recv} must have a property named {name}.")
521 redef class AStdImport
522 # The imported module once determined
523 var mmodule
: nullable MModule = null
527 # The associated MModule once build by a `ModelBuilder`
528 var mmodule
: nullable MModule
529 # Flag that indicate if the importation is already completed
530 var is_importation_done
: Bool = false
533 redef class AVisibility
534 # The visibility level associated with the AST node class
535 fun mvisibility
: MVisibility is abstract
537 redef class AIntrudeVisibility
538 redef fun mvisibility
do return intrude_visibility
540 redef class APublicVisibility
541 redef fun mvisibility
do return public_visibility
543 redef class AProtectedVisibility
544 redef fun mvisibility
do return protected_visibility
546 redef class APrivateVisibility
547 redef fun mvisibility
do return private_visibility