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
)
74 if toolcontext
.opt_only_metamodel
.value
then
75 self.toolcontext
.info
("*** ONLY METAMODEL", 1)
80 # Instantiate a modelbuilder for a model and a toolcontext
81 # Important, the options of the toolcontext must be correctly set (parse_option already called)
82 init(model
: Model, toolcontext
: ToolContext)
85 self.toolcontext
= toolcontext
86 assert toolcontext
.modelbuilder_real
== null
87 toolcontext
.modelbuilder_real
= self
89 # Setup the paths value
90 paths
.append
(toolcontext
.opt_path
.value
)
92 var path_env
= "NIT_PATH".environ
93 if not path_env
.is_empty
then
94 paths
.append
(path_env
.split_with
(':'))
97 path_env
= "NIT_DIR".environ
98 if not path_env
.is_empty
then
99 var libname
= "{path_env}/lib"
100 if libname
.file_exists
then paths
.add
(libname
)
103 var libname
= "{sys.program_name.dirname}/../lib"
104 if libname
.file_exists
then paths
.add
(libname
.simplify_path
)
107 # Load a bunch of modules.
108 # `modules' can contains filenames or module names.
109 # Imported modules are automatically loaded and modelized.
110 # The result is the corresponding model elements.
111 # Errors and warnings are printed with the toolcontext.
113 # Note: class and property model element are not analysed.
114 fun parse
(modules
: Sequence[String]): Array[MModule]
117 # Parse and recursively load
118 self.toolcontext
.info
("*** PARSE ***", 1)
119 var mmodules
= new Array[MModule]
121 var nmodule
= self.load_module
(null, a
)
122 if nmodule
== null then continue # Skip error
123 mmodules
.add
(nmodule
.mmodule
.as(not null))
126 self.toolcontext
.info
("*** END PARSE: {time1-time0} ***", 2)
128 self.toolcontext
.check_errors
130 if toolcontext
.opt_only_parse
.value
then
131 self.toolcontext
.info
("*** ONLY PARSE...", 1)
138 # Return a class named `name' visible by the module `mmodule'.
139 # Visibility in modules is correctly handled.
140 # If no such a class exists, then null is returned.
141 # If more than one class exists, then an error on `anode' is displayed and null is returned.
142 # FIXME: add a way to handle class name conflict
143 fun try_get_mclass_by_name
(anode
: ANode, mmodule
: MModule, name
: String): nullable MClass
145 var classes
= model
.get_mclasses_by_name
(name
)
146 if classes
== null then
150 var res
: nullable MClass = null
151 for mclass
in classes
do
152 if not mmodule
.in_importation
<= mclass
.intro_mmodule
then continue
153 if not mmodule
.is_visible
(mclass
.intro_mmodule
, mclass
.visibility
) then continue
157 error
(anode
, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
164 # Return a property named `name' on the type `mtype' visible in the module `mmodule'.
165 # Visibility in modules is correctly handled.
166 # Protected properties are returned (it is up to the caller to check and reject protected properties).
167 # If no such a property exists, then null is returned.
168 # If more than one property exists, then an error on `anode' is displayed and null is returned.
169 # FIXME: add a way to handle property name conflict
170 fun try_get_mproperty_by_name2
(anode
: ANode, mmodule
: MModule, mtype
: MType, name
: String): nullable MProperty
172 var props
= self.model
.get_mproperties_by_name
(name
)
173 if props
== null then
177 var cache
= self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
]
178 if cache
!= null then return cache
180 var res
: nullable MProperty = null
181 var ress
: nullable Array[MProperty] = null
182 for mprop
in props
do
183 if not mtype
.has_mproperty
(mmodule
, mprop
) then continue
184 if not mmodule
.is_visible
(mprop
.intro_mclassdef
.mmodule
, mprop
.visibility
) then continue
188 var restype
= res
.intro_mclassdef
.bound_mtype
189 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
190 if restype
.is_subtype
(mmodule
, null, mproptype
) then
192 else if mproptype
.is_subtype
(mmodule
, null, restype
) then
195 if ress
== null then ress
= new Array[MProperty]
201 var restype
= res
.intro_mclassdef
.bound_mtype
203 var mproptype
= mprop
.intro_mclassdef
.bound_mtype
204 if not restype
.is_subtype
(mmodule
, null, mproptype
) then
205 self.error
(anode
, "Ambigous property name '{name}' for {mtype}; conflict between {mprop.full_name} and {res.full_name}")
211 self.try_get_mproperty_by_name2_cache
[mmodule
, mtype
, name
] = res
215 private var try_get_mproperty_by_name2_cache
: HashMap3[MModule, MType, String, nullable MProperty] = new HashMap3[MModule, MType, String, nullable MProperty]
218 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
219 fun try_get_mproperty_by_name
(anode
: ANode, mclassdef
: MClassDef, name
: String): nullable MProperty
221 return try_get_mproperty_by_name2
(anode
, mclassdef
.mmodule
, mclassdef
.bound_mtype
, name
)
224 # The list of directories to search for top level modules
225 # The list is initially set with :
226 # * the toolcontext --path option
227 # * the NIT_PATH environment variable
228 # * some heuristics including the NIT_DIR environment variable and the progname of the process
229 # Path can be added (or removed) by the client
230 var paths
: Array[String] = new Array[String]
232 # Get a module by its short name; if required, the module is loaded, parsed and its hierarchies computed.
233 # If `mmodule' is set, then the module search starts from it up to the top level (see `paths');
234 # if `mmodule' is null then the module is searched in the top level only.
235 # If no module exists or there is a name conflict, then an error on `anode' is displayed and null is returned.
236 # FIXME: add a way to handle module name conflict
237 fun get_mmodule_by_name
(anode
: ANode, mmodule
: nullable MModule, name
: String): nullable MModule
239 var origmmodule
= mmodule
240 var modules
= model
.get_mmodules_by_name
(name
)
242 var tries
= new Array[String]
244 var lastmodule
= mmodule
245 while mmodule
!= null do
246 var dirname
= mmodule
.location
.file
.filename
.dirname
248 # Determine the owner
249 var owner
: nullable MModule
250 if dirname
.basename
("") != mmodule
.name
then
251 owner
= mmodule
.direct_owner
256 # First, try the already known nested modules
257 if modules
!= null then
258 for candidate
in modules
do
259 if candidate
.direct_owner
== owner
then
265 # Second, try the directory to find a file
266 var try_file
= dirname
+ "/" + name
+ ".nit"
268 if try_file
.file_exists
then
269 var res
= self.load_module
(owner
, try_file
.simplify_path
)
270 if res
== null then return null # Forward error
271 return res
.mmodule
.as(not null)
274 # Third, try if the requested module is itself an owner
275 try_file
= dirname
+ "/" + name
+ "/" + name
+ ".nit"
276 if try_file
.file_exists
then
277 var res
= self.load_module
(owner
, try_file
.simplify_path
)
278 if res
== null then return null # Forward error
279 return res
.mmodule
.as(not null)
283 mmodule
= mmodule
.direct_owner
286 if modules
!= null then
287 for candidate
in modules
do
288 if candidate
.direct_owner
== null then
294 # Look at some known directories
295 var lookpaths
= self.paths
297 # Look in the directory of the last module also (event if not in the path)
298 if lastmodule
!= null then
299 var dirname
= lastmodule
.location
.file
.filename
.dirname
300 if dirname
.basename
("") == lastmodule
.name
then
301 dirname
= dirname
.dirname
303 if not lookpaths
.has
(dirname
) then
304 lookpaths
= lookpaths
.to_a
305 lookpaths
.add
(dirname
)
309 var candidate
: nullable String = null
310 for dirname
in lookpaths
do
311 var try_file
= (dirname
+ "/" + name
+ ".nit").simplify_path
313 if try_file
.file_exists
then
314 if candidate
== null then
316 else if candidate
!= try_file
then
317 error
(anode
, "Error: conflicting module file for {name}: {candidate} {try_file}")
320 try_file
= (dirname
+ "/" + name
+ "/" + name
+ ".nit").simplify_path
321 if try_file
.file_exists
then
322 if candidate
== null then
324 else if candidate
!= try_file
then
325 error
(anode
, "Error: conflicting module file for {name}: {candidate} {try_file}")
329 if candidate
== null then
330 if origmmodule
!= null then
331 error
(anode
, "Error: cannot find module {name} from {origmmodule}. tried {tries.join(", ")}")
333 error
(anode
, "Error: cannot find module {name}. tried {tries.join(", ")}")
337 var res
= self.load_module
(mmodule
, candidate
)
338 if res
== null then return null # Forward error
339 return res
.mmodule
.as(not null)
342 # Try to load a module using a path.
343 # Display an error if there is a problem (IO / lexer / parser) and return null
344 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
345 fun load_module
(owner
: nullable MModule, filename
: String): nullable AModule
347 if not filename
.file_exists
then
348 self.toolcontext
.error
(null, "Error: file {filename} not found.")
352 var x
= if owner
!= null then owner
.to_s
else "."
353 self.toolcontext
.info
("load module {filename} in {x}", 2)
356 var file
= new IFStream.open
(filename
)
357 var lexer
= new Lexer(new SourceFile(filename
, file
))
358 var parser
= new Parser(lexer
)
359 var tree
= parser
.parse
362 # Handle lexer and parser error
363 var nmodule
= tree
.n_base
364 if nmodule
== null then
365 var neof
= tree
.n_eof
366 assert neof
isa AError
367 error
(neof
, neof
.message
)
371 # Check the module name
372 var mod_name
= filename
.basename
(".nit")
373 var decl
= nmodule
.n_moduledecl
375 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
377 var decl_name
= decl
.n_name
.n_id
.text
378 if decl_name
!= mod_name
then
379 error
(decl
.n_name
, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
384 var mmodule
= new MModule(model
, owner
, mod_name
, nmodule
.location
)
385 nmodule
.mmodule
= mmodule
386 nmodules
.add
(nmodule
)
387 self.mmodule2nmodule
[mmodule
] = nmodule
389 build_module_importation
(nmodule
)
394 # Analysis the module importation and fill the module_importation_hierarchy
395 private fun build_module_importation
(nmodule
: AModule)
397 if nmodule
.is_importation_done
then return
398 nmodule
.is_importation_done
= true
399 var mmodule
= nmodule
.mmodule
.as(not null)
401 var imported_modules
= new Array[MModule]
402 for aimport
in nmodule
.n_imports
do
404 if not aimport
isa AStdImport then
407 var mod_name
= aimport
.n_name
.n_id
.text
408 var sup
= self.get_mmodule_by_name
(aimport
.n_name
, mmodule
, mod_name
)
409 if sup
== null then continue # Skip error
410 imported_modules
.add
(sup
)
411 var mvisibility
= aimport
.n_visibility
.mvisibility
412 mmodule
.set_visibility_for
(sup
, mvisibility
)
415 var mod_name
= "standard"
416 var sup
= self.get_mmodule_by_name
(nmodule
, null, mod_name
)
417 if sup
!= null then # Skip error
418 imported_modules
.add
(sup
)
419 mmodule
.set_visibility_for
(sup
, public_visibility
)
422 self.toolcontext
.info
("{mmodule} imports {imported_modules.join(", ")}", 3)
423 mmodule
.set_imported_mmodules
(imported_modules
)
426 # All the loaded modules
427 var nmodules
: Array[AModule] = new Array[AModule]
429 # Register the nmodule associated to each mmodule
430 # FIXME: why not refine the MModule class with a nullable attribute?
431 var mmodule2nmodule
: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
433 # Helper function to display an error on a node.
434 # Alias for `self.toolcontext.error(n.hot_location, text)'
435 fun error
(n
: ANode, text
: String)
437 self.toolcontext
.error
(n
.hot_location
, text
)
440 # Helper function to display a warning on a node.
441 # Alias for: `self.toolcontext.warning(n.hot_location, text)'
442 fun warning
(n
: ANode, text
: String)
444 self.toolcontext
.warning
(n
.hot_location
, text
)
447 # Force to get the primitive method named `name' on the type `recv' or do a fatal error on `n'
448 fun force_get_primitive_method
(n
: ANode, name
: String, recv
: MType, mmodule
: MModule): MMethod
450 var res
= mmodule
.try_get_primitive_method
(name
, recv
)
452 self.toolcontext
.fatal_error
(n
.hot_location
, "Fatal Error: {recv} must have a property named {name}.")
460 # The associated MModule once build by a `ModelBuilder'
461 var mmodule
: nullable MModule
462 # Flag that indicate if the importation is already completed
463 var is_importation_done
: Bool = false
466 redef class AVisibility
467 # The visibility level associated with the AST node class
468 fun mvisibility
: MVisibility is abstract
470 redef class AIntrudeVisibility
471 redef fun mvisibility
do return intrude_visibility
473 redef class APublicVisibility
474 redef fun mvisibility
do return public_visibility
476 redef class AProtectedVisibility
477 redef fun mvisibility
do return protected_visibility
479 redef class APrivateVisibility
480 redef fun mvisibility
do return private_visibility