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 readable var _opt_path
: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
40 # Option --only-metamodel
41 readable var _opt_only_metamodel
: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
44 readable 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
)
77 # Instantiate a modelbuilder for a model and a toolcontext
78 # Important, the options of the toolcontext must be correctly set (parse_option already called)
79 init(model
: Model, toolcontext
: ToolContext)
82 self.toolcontext
= toolcontext
83 assert toolcontext
.modelbuilder_real
== null
84 toolcontext
.modelbuilder_real
= self
86 # Setup the paths value
87 paths
.append
(toolcontext
.opt_path
.value
)
89 var path_env
= "NIT_PATH".environ
90 if not path_env
.is_empty
then
91 paths
.append
(path_env
.split_with
(':'))
94 path_env
= "NIT_DIR".environ
95 if not path_env
.is_empty
then
96 var libname
= "{path_env}/lib"
97 if libname
.file_exists
then paths
.add
(libname
)
100 var libname
= "{sys.program_name.dirname}/../lib"
101 if libname
.file_exists
then paths
.add
(libname
.simplify_path
)
104 # Load a bunch of modules.
105 # `modules' can contains filenames or module names.
106 # Imported modules are automatically loaded and modelized.
107 # The result is the corresponding model elements.
108 # Errors and warnings are printed with the toolcontext.
110 # Note: class and property model element are not analysed.
111 fun parse
(modules
: Sequence[String]): Array[MModule]
114 # Parse and recursively load
115 self.toolcontext
.info
("*** PARSE ***", 1)
116 var mmodules
= new Array[MModule]
118 var nmodule
= self.load_module
(null, a
)
119 if nmodule
== null then continue # Skip error
120 mmodules
.add
(nmodule
.mmodule
.as(not null))
123 self.toolcontext
.info
("*** END PARSE: {time1-time0} ***", 2)
125 self.toolcontext
.check_errors
129 # Return a class named `name' visible by the module `mmodule'.
130 # Visibility in modules is correctly handled.
131 # If no such a class exists, then null is returned.
132 # If more than one class exists, then an error on `anode' is displayed and null is returned.
133 # FIXME: add a way to handle class name conflict
134 fun try_get_mclass_by_name
(anode
: ANode, mmodule
: MModule, name
: String): nullable MClass
136 var classes
= model
.get_mclasses_by_name
(name
)
137 if classes
== null then
141 var res
: nullable MClass = null
142 for mclass
in classes
do
143 if not mmodule
.in_importation
<= mclass
.intro_mmodule
then continue
144 if not mmodule
.is_visible
(mclass
.intro_mmodule
, mclass
.visibility
) then continue
148 error
(anode
, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
155 # Return a property named `name' on the type `mtype' visible in the module `mmodule'.
156 # Visibility in modules is correctly handled.
157 # Protected properties are returned (it is up to the caller to check and reject protected properties).
158 # If no such a property exists, then null is returned.
159 # If more than one property exists, then an error on `anode' is displayed and null is returned.
160 # FIXME: add a way to handle property name conflict
161 fun try_get_mproperty_by_name2
(anode
: ANode, mmodule
: MModule, mtype
: MType, name
: String): nullable MProperty
163 var props
= self.model
.get_mproperties_by_name
(name
)
164 if props
== null then
168 var cache
= self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
]
169 if cache
!= null then return cache
171 var res
: nullable MProperty = null
172 var ress
: nullable Array[MProperty] = null
173 for mprop
in props
do
174 if not mtype
.has_mproperty
(mmodule
, mprop
) then continue
175 if not mmodule
.is_visible
(mprop
.intro_mclassdef
.mmodule
, mprop
.visibility
) then continue
179 var restype
= res
.intro_mclassdef
.bound_mtype
180 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
181 if restype
.is_subtype
(mmodule
, null, mproptype
) then
183 else if mproptype
.is_subtype
(mmodule
, null, restype
) then
186 if ress
== null then ress
= new Array[MProperty]
192 var restype
= res
.intro_mclassdef
.bound_mtype
194 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
195 if not restype
.is_subtype
(mmodule
, null, mproptype
) then
196 self.error
(anode
, "Ambigous property name '{name}' for {mtype}; conflict between {mprop.full_name} and {res.full_name}")
202 self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
] = res
206 private var try_get_mproperty_by_name2_cache
: HashMap3[MModule, MType, String, nullable MProperty] = new HashMap3[MModule, MType, String, nullable MProperty]
209 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
210 fun try_get_mproperty_by_name
(anode
: ANode, mclassdef
: MClassDef, name
: String): nullable MProperty
212 return try_get_mproperty_by_name2
(anode
, mclassdef
.mmodule
, mclassdef
.bound_mtype
, name
)
215 # The list of directories to search for top level modules
216 # The list is initially set with :
217 # * the toolcontext --path option
218 # * the NIT_PATH environment variable
219 # * some heuristics including the NIT_DIR environment variable and the progname of the process
220 # Path can be added (or removed) by the client
221 var paths
: Array[String] = new Array[String]
223 # Get a module by its short name; if required, the module is loaded, parsed and its hierarchies computed.
224 # If `mmodule' is set, then the module search starts from it up to the top level (see `paths');
225 # if `mmodule' is null then the module is searched in the top level only.
226 # If no module exists or there is a name conflict, then an error on `anode' is displayed and null is returned.
227 # FIXME: add a way to handle module name conflict
228 fun get_mmodule_by_name
(anode
: ANode, mmodule
: nullable MModule, name
: String): nullable MModule
230 var origmmodule
= mmodule
231 var modules
= model
.get_mmodules_by_name
(name
)
233 var tries
= new Array[String]
235 var lastmodule
= mmodule
236 while mmodule
!= null do
237 var dirname
= mmodule
.location
.file
.filename
.dirname
239 # Determine the owner
240 var owner
: nullable MModule
241 if dirname
.basename
("") != mmodule
.name
then
242 owner
= mmodule
.direct_owner
247 # First, try the already known nested modules
248 if modules
!= null then
249 for candidate
in modules
do
250 if candidate
.direct_owner
== owner
then
256 # Second, try the directory to find a file
257 var try_file
= dirname
+ "/" + name
+ ".nit"
259 if try_file
.file_exists
then
260 var res
= self.load_module
(owner
, try_file
.simplify_path
)
261 if res
== null then return null # Forward error
262 return res
.mmodule
.as(not null)
265 # Third, try if the requested module is itself an owner
266 try_file
= dirname
+ "/" + name
+ "/" + name
+ ".nit"
267 if try_file
.file_exists
then
268 var res
= self.load_module
(owner
, try_file
.simplify_path
)
269 if res
== null then return null # Forward error
270 return res
.mmodule
.as(not null)
274 mmodule
= mmodule
.direct_owner
277 if modules
!= null then
278 for candidate
in modules
do
279 if candidate
.direct_owner
== null then
285 # Look at some known directories
286 var lookpaths
= self.paths
288 # Look in the directory of the last module also (event if not in the path)
289 if lastmodule
!= null then
290 var dirname
= lastmodule
.location
.file
.filename
.dirname
291 if dirname
.basename
("") == lastmodule
.name
then
292 dirname
= dirname
.dirname
294 if not lookpaths
.has
(dirname
) then
295 lookpaths
= lookpaths
.to_a
296 lookpaths
.add
(dirname
)
300 var candidate
: nullable String = null
301 for dirname
in lookpaths
do
302 var try_file
= (dirname
+ "/" + name
+ ".nit").simplify_path
304 if try_file
.file_exists
then
305 if candidate
== null then
307 else if candidate
!= try_file
then
308 error
(anode
, "Error: conflicting module file for {name}: {candidate} {try_file}")
311 try_file
= (dirname
+ "/" + name
+ "/" + name
+ ".nit").simplify_path
312 if try_file
.file_exists
then
313 if candidate
== null then
315 else if candidate
!= try_file
then
316 error
(anode
, "Error: conflicting module file for {name}: {candidate} {try_file}")
320 if candidate
== null then
321 if origmmodule
!= null then
322 error
(anode
, "Error: cannot find module {name} from {origmmodule}. tried {tries.join(", ")}")
324 error
(anode
, "Error: cannot find module {name}. tried {tries.join(", ")}")
328 var res
= self.load_module
(mmodule
, candidate
)
329 if res
== null then return null # Forward error
330 return res
.mmodule
.as(not null)
333 # Try to load a module using a path.
334 # Display an error if there is a problem (IO / lexer / parser) and return null
335 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
336 fun load_module
(owner
: nullable MModule, filename
: String): nullable AModule
338 if not filename
.file_exists
then
339 self.toolcontext
.error
(null, "Error: file {filename} not found.")
343 var x
= if owner
!= null then owner
.to_s
else "."
344 self.toolcontext
.info
("load module {filename} in {x}", 2)
347 var file
= new IFStream.open
(filename
)
348 var lexer
= new Lexer(new SourceFile(filename
, file
))
349 var parser
= new Parser(lexer
)
350 var tree
= parser
.parse
353 # Handle lexer and parser error
354 var nmodule
= tree
.n_base
355 if nmodule
== null then
356 var neof
= tree
.n_eof
357 assert neof
isa AError
358 error
(neof
, neof
.message
)
362 # Check the module name
363 var mod_name
= filename
.basename
(".nit")
364 var decl
= nmodule
.n_moduledecl
366 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
368 var decl_name
= decl
.n_name
.n_id
.text
369 if decl_name
!= mod_name
then
370 error
(decl
.n_name
, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
375 var mmodule
= new MModule(model
, owner
, mod_name
, nmodule
.location
)
376 nmodule
.mmodule
= mmodule
377 nmodules
.add
(nmodule
)
378 self.mmodule2nmodule
[mmodule
] = nmodule
380 build_module_importation
(nmodule
)
385 # Analysis the module importation and fill the module_importation_hierarchy
386 private fun build_module_importation
(nmodule
: AModule)
388 if nmodule
.is_importation_done
then return
389 nmodule
.is_importation_done
= true
390 var mmodule
= nmodule
.mmodule
.as(not null)
392 var imported_modules
= new Array[MModule]
393 for aimport
in nmodule
.n_imports
do
395 if not aimport
isa AStdImport then
398 var mod_name
= aimport
.n_name
.n_id
.text
399 var sup
= self.get_mmodule_by_name
(aimport
.n_name
, mmodule
, mod_name
)
400 if sup
== null then continue # Skip error
401 imported_modules
.add
(sup
)
402 var mvisibility
= aimport
.n_visibility
.mvisibility
403 mmodule
.set_visibility_for
(sup
, mvisibility
)
406 var mod_name
= "standard"
407 var sup
= self.get_mmodule_by_name
(nmodule
, null, mod_name
)
408 if sup
!= null then # Skip error
409 imported_modules
.add
(sup
)
410 mmodule
.set_visibility_for
(sup
, public_visibility
)
413 self.toolcontext
.info
("{mmodule} imports {imported_modules.join(", ")}", 3)
414 mmodule
.set_imported_mmodules
(imported_modules
)
417 # All the loaded modules
418 var nmodules
: Array[AModule] = new Array[AModule]
420 # Register the nmodule associated to each mmodule
421 # FIXME: why not refine the MModule class with a nullable attribute?
422 var mmodule2nmodule
: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
424 # Helper function to display an error on a node.
425 # Alias for `self.toolcontext.error(n.hot_location, text)'
426 fun error
(n
: ANode, text
: String)
428 self.toolcontext
.error
(n
.hot_location
, text
)
431 # Helper function to display a warning on a node.
432 # Alias for: `self.toolcontext.warning(n.hot_location, text)'
433 fun warning
(n
: ANode, text
: String)
435 self.toolcontext
.warning
(n
.hot_location
, text
)
438 # Force to get the primitive method named `name' on the type `recv' or do a fatal error on `n'
439 fun force_get_primitive_method
(n
: ANode, name
: String, recv
: MType, mmodule
: MModule): MMethod
441 var res
= mmodule
.try_get_primitive_method
(name
, recv
)
443 self.toolcontext
.fatal_error
(n
.hot_location
, "Fatal Error: {recv} must have a property named {name}.")
451 # The associated MModule once build by a `ModelBuilder'
452 var mmodule
: nullable MModule
453 # Flag that indicate if the importation is already completed
454 var is_importation_done
: Bool = false
457 redef class AVisibility
458 # The visibility level associated with the AST node class
459 fun mvisibility
: MVisibility is abstract
461 redef class AIntrudeVisibility
462 redef fun mvisibility
do return intrude_visibility
464 redef class APublicVisibility
465 redef fun mvisibility
do return public_visibility
467 redef class AProtectedVisibility
468 redef fun mvisibility
do return protected_visibility
470 redef class APrivateVisibility
471 redef fun mvisibility
do return private_visibility