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
34 redef class ToolContext
36 readable var _opt_path
: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
38 # Option --only-metamodel
39 readable var _opt_only_metamodel
: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
42 readable var _opt_only_parse
: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
47 option_context
.add_option
(opt_path
, opt_only_parse
, opt_only_metamodel
)
50 fun modelbuilder
: ModelBuilder do return modelbuilder_real
.as(not null)
51 private var modelbuilder_real
: nullable ModelBuilder = null
55 # A model builder knows how to load nit source files and build the associated model
57 # The model where new modules, classes and properties are added
60 # The toolcontext used to control the interaction with the user (getting options and displaying messages)
61 var toolcontext
: ToolContext
63 # Run phases on all loaded modules
66 var mmodules
= model
.mmodules
.to_a
67 model
.mmodule_importation_hierarchy
.sort
(mmodules
)
68 var nmodules
= new Array[AModule]
70 nmodules
.add
(mmodule2nmodule
[mm
])
72 toolcontext
.run_phases
(nmodules
)
75 # Instantiate a modelbuilder for a model and a toolcontext
76 # Important, the options of the toolcontext must be correctly set (parse_option already called)
77 init(model
: Model, toolcontext
: ToolContext)
80 self.toolcontext
= toolcontext
81 assert toolcontext
.modelbuilder_real
== null
82 toolcontext
.modelbuilder_real
= self
84 # Setup the paths value
85 paths
.append
(toolcontext
.opt_path
.value
)
87 var path_env
= "NIT_PATH".environ
88 if not path_env
.is_empty
then
89 paths
.append
(path_env
.split_with
(':'))
92 path_env
= "NIT_DIR".environ
93 if not path_env
.is_empty
then
94 var libname
= "{path_env}/lib"
95 if libname
.file_exists
then paths
.add
(libname
)
98 var libname
= "{sys.program_name.dirname}/../lib"
99 if libname
.file_exists
then paths
.add
(libname
.simplify_path
)
102 # Load a bunch of modules.
103 # `modules' can contains filenames or module names.
104 # Imported modules are automatically loaded and modelized.
105 # The result is the corresponding model elements.
106 # Errors and warnings are printed with the toolcontext.
108 # Note: class and property model element are not analysed.
109 fun parse
(modules
: Sequence[String]): Array[MModule]
112 # Parse and recursively load
113 self.toolcontext
.info
("*** PARSE ***", 1)
114 var mmodules
= new Array[MModule]
116 var nmodule
= self.load_module
(null, a
)
117 if nmodule
== null then continue # Skip error
118 mmodules
.add
(nmodule
.mmodule
.as(not null))
121 self.toolcontext
.info
("*** END PARSE: {time1-time0} ***", 2)
123 self.toolcontext
.check_errors
127 # Return a class named `name' visible by the module `mmodule'.
128 # Visibility in modules is correctly handled.
129 # If no such a class exists, then null is returned.
130 # If more than one class exists, then an error on `anode' is displayed and null is returned.
131 # FIXME: add a way to handle class name conflict
132 fun try_get_mclass_by_name
(anode
: ANode, mmodule
: MModule, name
: String): nullable MClass
134 var classes
= model
.get_mclasses_by_name
(name
)
135 if classes
== null then
139 var res
: nullable MClass = null
140 for mclass
in classes
do
141 if not mmodule
.in_importation
<= mclass
.intro_mmodule
then continue
142 if not mmodule
.is_visible
(mclass
.intro_mmodule
, mclass
.visibility
) then continue
146 error
(anode
, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
153 # Return a property named `name' on the type `mtype' visible in the module `mmodule'.
154 # Visibility in modules is correctly handled.
155 # Protected properties are returned (it is up to the caller to check and reject protected properties).
156 # If no such a property exists, then null is returned.
157 # If more than one property exists, then an error on `anode' is displayed and null is returned.
158 # FIXME: add a way to handle property name conflict
159 fun try_get_mproperty_by_name2
(anode
: ANode, mmodule
: MModule, mtype
: MType, name
: String): nullable MProperty
161 var props
= self.model
.get_mproperties_by_name
(name
)
162 if props
== null then
166 var cache
= self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
]
167 if cache
!= null then return cache
169 var res
: nullable MProperty = null
170 var ress
: nullable Array[MProperty] = null
171 for mprop
in props
do
172 if not mtype
.has_mproperty
(mmodule
, mprop
) then continue
173 if not mmodule
.is_visible
(mprop
.intro_mclassdef
.mmodule
, mprop
.visibility
) then continue
177 var restype
= res
.intro_mclassdef
.bound_mtype
178 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
179 if restype
.is_subtype
(mmodule
, null, mproptype
) then
181 else if mproptype
.is_subtype
(mmodule
, null, restype
) then
184 if ress
== null then ress
= new Array[MProperty]
190 var restype
= res
.intro_mclassdef
.bound_mtype
192 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
193 if not restype
.is_subtype
(mmodule
, null, mproptype
) then
194 self.error
(anode
, "Ambigous property name '{name}' for {mtype}; conflict between {mprop.full_name} and {res.full_name}")
200 self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
] = res
204 private var try_get_mproperty_by_name2_cache
: HashMap3[MModule, MType, String, nullable MProperty] = new HashMap3[MModule, MType, String, nullable MProperty]
207 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
208 fun try_get_mproperty_by_name
(anode
: ANode, mclassdef
: MClassDef, name
: String): nullable MProperty
210 return try_get_mproperty_by_name2
(anode
, mclassdef
.mmodule
, mclassdef
.bound_mtype
, name
)
213 # The list of directories to search for top level modules
214 # The list is initially set with :
215 # * the toolcontext --path option
216 # * the NIT_PATH environment variable
217 # * some heuristics including the NIT_DIR environment variable and the progname of the process
218 # Path can be added (or removed) by the client
219 var paths
: Array[String] = new Array[String]
221 # Get a module by its short name; if required, the module is loaded, parsed and its hierarchies computed.
222 # If `mmodule' is set, then the module search starts from it up to the top level (see `paths');
223 # if `mmodule' is null then the module is searched in the top level only.
224 # If no module exists or there is a name conflict, then an error on `anode' is displayed and null is returned.
225 # FIXME: add a way to handle module name conflict
226 fun get_mmodule_by_name
(anode
: ANode, mmodule
: nullable MModule, name
: String): nullable MModule
228 var origmmodule
= mmodule
229 var modules
= model
.get_mmodules_by_name
(name
)
231 var tries
= new Array[String]
233 var lastmodule
= mmodule
234 while mmodule
!= null do
235 var dirname
= mmodule
.location
.file
.filename
.dirname
237 # Determine the owner
238 var owner
: nullable MModule
239 if dirname
.basename
("") != mmodule
.name
then
240 owner
= mmodule
.direct_owner
245 # First, try the already known nested modules
246 if modules
!= null then
247 for candidate
in modules
do
248 if candidate
.direct_owner
== owner
then
254 # Second, try the directory to find a file
255 var try_file
= dirname
+ "/" + name
+ ".nit"
257 if try_file
.file_exists
then
258 var res
= self.load_module
(owner
, try_file
.simplify_path
)
259 if res
== null then return null # Forward error
260 return res
.mmodule
.as(not null)
263 # Third, try if the requested module is itself an owner
264 try_file
= dirname
+ "/" + name
+ "/" + name
+ ".nit"
265 if try_file
.file_exists
then
266 var res
= self.load_module
(owner
, try_file
.simplify_path
)
267 if res
== null then return null # Forward error
268 return res
.mmodule
.as(not null)
272 mmodule
= mmodule
.direct_owner
275 if modules
!= null then
276 for candidate
in modules
do
277 if candidate
.direct_owner
== null then
283 # Look at some known directories
284 var lookpaths
= self.paths
286 # Look in the directory of the last module also (event if not in the path)
287 if lastmodule
!= null then
288 var dirname
= lastmodule
.location
.file
.filename
.dirname
289 if dirname
.basename
("") == lastmodule
.name
then
290 dirname
= dirname
.dirname
292 if not lookpaths
.has
(dirname
) then
293 lookpaths
= lookpaths
.to_a
294 lookpaths
.add
(dirname
)
298 var candidate
: nullable String = null
299 for dirname
in lookpaths
do
300 var try_file
= (dirname
+ "/" + name
+ ".nit").simplify_path
302 if try_file
.file_exists
then
303 if candidate
== null then
305 else if candidate
!= try_file
then
306 error
(anode
, "Error: conflicting module file for {name}: {candidate} {try_file}")
309 try_file
= (dirname
+ "/" + name
+ "/" + name
+ ".nit").simplify_path
310 if try_file
.file_exists
then
311 if candidate
== null then
313 else if candidate
!= try_file
then
314 error
(anode
, "Error: conflicting module file for {name}: {candidate} {try_file}")
318 if candidate
== null then
319 if origmmodule
!= null then
320 error
(anode
, "Error: cannot find module {name} from {origmmodule}. tried {tries.join(", ")}")
322 error
(anode
, "Error: cannot find module {name}. tried {tries.join(", ")}")
326 var res
= self.load_module
(mmodule
, candidate
)
327 if res
== null then return null # Forward error
328 return res
.mmodule
.as(not null)
331 # Try to load a module using a path.
332 # Display an error if there is a problem (IO / lexer / parser) and return null
333 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
334 fun load_module
(owner
: nullable MModule, filename
: String): nullable AModule
336 if not filename
.file_exists
then
337 self.toolcontext
.error
(null, "Error: file {filename} not found.")
341 var x
= if owner
!= null then owner
.to_s
else "."
342 self.toolcontext
.info
("load module {filename} in {x}", 2)
345 var file
= new IFStream.open
(filename
)
346 var lexer
= new Lexer(new SourceFile(filename
, file
))
347 var parser
= new Parser(lexer
)
348 var tree
= parser
.parse
351 # Handle lexer and parser error
352 var nmodule
= tree
.n_base
353 if nmodule
== null then
354 var neof
= tree
.n_eof
355 assert neof
isa AError
356 error
(neof
, neof
.message
)
360 # Check the module name
361 var mod_name
= filename
.basename
(".nit")
362 var decl
= nmodule
.n_moduledecl
364 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
366 var decl_name
= decl
.n_name
.n_id
.text
367 if decl_name
!= mod_name
then
368 error
(decl
.n_name
, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
373 var mmodule
= new MModule(model
, owner
, mod_name
, nmodule
.location
)
374 nmodule
.mmodule
= mmodule
375 nmodules
.add
(nmodule
)
376 self.mmodule2nmodule
[mmodule
] = nmodule
378 build_module_importation
(nmodule
)
383 # Analysis the module importation and fill the module_importation_hierarchy
384 private fun build_module_importation
(nmodule
: AModule)
386 if nmodule
.is_importation_done
then return
387 nmodule
.is_importation_done
= true
388 var mmodule
= nmodule
.mmodule
.as(not null)
390 var imported_modules
= new Array[MModule]
391 for aimport
in nmodule
.n_imports
do
393 if not aimport
isa AStdImport then
396 var mod_name
= aimport
.n_name
.n_id
.text
397 var sup
= self.get_mmodule_by_name
(aimport
.n_name
, mmodule
, mod_name
)
398 if sup
== null then continue # Skip error
399 imported_modules
.add
(sup
)
400 var mvisibility
= aimport
.n_visibility
.mvisibility
401 mmodule
.set_visibility_for
(sup
, mvisibility
)
404 var mod_name
= "standard"
405 var sup
= self.get_mmodule_by_name
(nmodule
, null, mod_name
)
406 if sup
!= null then # Skip error
407 imported_modules
.add
(sup
)
408 mmodule
.set_visibility_for
(sup
, public_visibility
)
411 self.toolcontext
.info
("{mmodule} imports {imported_modules.join(", ")}", 3)
412 mmodule
.set_imported_mmodules
(imported_modules
)
415 # All the loaded modules
416 var nmodules
: Array[AModule] = new Array[AModule]
418 # Register the nmodule associated to each mmodule
419 # FIXME: why not refine the MModule class with a nullable attribute?
420 var mmodule2nmodule
: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
422 # Helper function to display an error on a node.
423 # Alias for `self.toolcontext.error(n.hot_location, text)'
424 fun error
(n
: ANode, text
: String)
426 self.toolcontext
.error
(n
.hot_location
, text
)
429 # Helper function to display a warning on a node.
430 # Alias for: `self.toolcontext.warning(n.hot_location, text)'
431 fun warning
(n
: ANode, text
: String)
433 self.toolcontext
.warning
(n
.hot_location
, text
)
436 # Force to get the primitive method named `name' on the type `recv' or do a fatal error on `n'
437 fun force_get_primitive_method
(n
: ANode, name
: String, recv
: MType, mmodule
: MModule): MMethod
439 var res
= mmodule
.try_get_primitive_method
(name
, recv
)
441 self.toolcontext
.fatal_error
(n
.hot_location
, "Fatal Error: {recv} must have a property named {name}.")
449 # The associated MModule once build by a `ModelBuilder'
450 var mmodule
: nullable MModule
451 # Flag that indicate if the importation is already completed
452 var is_importation_done
: Bool = false
455 redef class AVisibility
456 # The visibility level associated with the AST node class
457 fun mvisibility
: MVisibility is abstract
459 redef class AIntrudeVisibility
460 redef fun mvisibility
do return intrude_visibility
462 redef class APublicVisibility
463 redef fun mvisibility
do return public_visibility
465 redef class AProtectedVisibility
466 redef fun mvisibility
do return protected_visibility
468 redef class APrivateVisibility
469 redef fun mvisibility
do return private_visibility