debugger: Added dynamic evaluation of a Nit module.
[nit.git] / src / modelbuilder.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012 Jean Privat <jean@pryen.org>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # Load nit source files and build the associated model
18 #
19 # FIXME better doc
20 #
21 # FIXME split this module into submodules
22 # FIXME add missing error checks
23 module modelbuilder
24
25 import parser
26 import model
27 import poset
28 import opts
29 import toolcontext
30 import phase
31
32 private import more_collections
33
34 ###
35
36 redef class ToolContext
37 # Option --path
38 var opt_path: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
39
40 # Option --only-metamodel
41 var opt_only_metamodel: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
42
43 # Option --only-parse
44 var opt_only_parse: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
45
46 redef init
47 do
48 super
49 option_context.add_option(opt_path, opt_only_parse, opt_only_metamodel)
50 end
51
52 fun modelbuilder: ModelBuilder do return modelbuilder_real.as(not null)
53 private var modelbuilder_real: nullable ModelBuilder = null
54
55 end
56
57 # A model builder knows how to load nit source files and build the associated model
58 class ModelBuilder
59 # The model where new modules, classes and properties are added
60 var model: Model
61
62 # The toolcontext used to control the interaction with the user (getting options and displaying messages)
63 var toolcontext: ToolContext
64
65 # Run phases on all loaded modules
66 fun run_phases
67 do
68 var mmodules = model.mmodules.to_a
69 model.mmodule_importation_hierarchy.sort(mmodules)
70 var nmodules = new Array[AModule]
71 for mm in mmodules do
72 nmodules.add(mmodule2nmodule[mm])
73 end
74 toolcontext.run_phases(nmodules)
75
76 if toolcontext.opt_only_metamodel.value then
77 self.toolcontext.info("*** ONLY METAMODEL", 1)
78 exit(0)
79 end
80 end
81
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)
85 do
86 self.model = model
87 self.toolcontext = toolcontext
88 assert toolcontext.modelbuilder_real == null
89 toolcontext.modelbuilder_real = self
90
91 # Setup the paths value
92 paths.append(toolcontext.opt_path.value)
93
94 var path_env = "NIT_PATH".environ
95 if not path_env.is_empty then
96 paths.append(path_env.split_with(':'))
97 end
98
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)
103 end
104
105 var libname = "{sys.program_name.dirname}/../lib"
106 if libname.file_exists then paths.add(libname.simplify_path)
107 end
108
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.
114 #
115 # Note: class and property model element are not analysed.
116 fun parse(modules: Sequence[String]): Array[MModule]
117 do
118 var time0 = get_time
119 # Parse and recursively load
120 self.toolcontext.info("*** PARSE ***", 1)
121 var mmodules = new Array[MModule]
122 for a in modules do
123 var nmodule = self.load_module(null, a)
124 if nmodule == null then continue # Skip error
125 mmodules.add(nmodule.mmodule.as(not null))
126 end
127 var time1 = get_time
128 self.toolcontext.info("*** END PARSE: {time1-time0} ***", 2)
129
130 self.toolcontext.check_errors
131
132 if toolcontext.opt_only_parse.value then
133 self.toolcontext.info("*** ONLY PARSE...", 1)
134 exit(0)
135 end
136
137 return mmodules
138 end
139
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
146 do
147 var classes = model.get_mclasses_by_name(name)
148 if classes == null then
149 return null
150 end
151
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
156 if res == null then
157 res = mclass
158 else
159 error(anode, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
160 return null
161 end
162 end
163 return res
164 end
165
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
173 do
174 var props = self.model.get_mproperties_by_name(name)
175 if props == null then
176 return null
177 end
178
179 var cache = self.try_get_mproperty_by_name2_cache[mmodule, mtype, name]
180 if cache != null then return cache
181
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
187 if res == null then
188 res = mprop
189 else
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
193 # we keep res
194 else if mproptype.is_subtype(mmodule, null, restype) then
195 res = mprop
196 else
197 if ress == null then ress = new Array[MProperty]
198 ress.add(mprop)
199 end
200 end
201 end
202 if ress != null then
203 var restype = res.intro_mclassdef.bound_mtype
204 for mprop in ress do
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}")
208 return null
209 end
210 end
211 end
212
213 self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res
214 return res
215 end
216
217 private var try_get_mproperty_by_name2_cache: HashMap3[MModule, MType, String, nullable MProperty] = new HashMap3[MModule, MType, String, nullable MProperty]
218
219
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
222 do
223 return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name)
224 end
225
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]
233
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
240 do
241 var origmmodule = mmodule
242 var modules = model.get_mmodules_by_name(name)
243
244 var tries = new Array[String]
245
246 var lastmodule = mmodule
247 while mmodule != null do
248 var dirname = mmodule.location.file.filename.dirname
249
250 # Determine the owner
251 var owner: nullable MModule
252 if dirname.basename("") != mmodule.name then
253 owner = mmodule.direct_owner
254 else
255 owner = mmodule
256 end
257
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
262 return candidate
263 end
264 end
265 end
266
267 # Second, try the directory to find a file
268 var try_file = dirname + "/" + name + ".nit"
269 tries.add try_file
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)
274 end
275
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)
282 end
283
284 lastmodule = mmodule
285 mmodule = mmodule.direct_owner
286 end
287
288 if modules != null then
289 for candidate in modules do
290 if candidate.direct_owner == null then
291 return candidate
292 end
293 end
294 end
295
296 # Look at some known directories
297 var lookpaths = self.paths
298
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
304 end
305 if not lookpaths.has(dirname) then
306 lookpaths = lookpaths.to_a
307 lookpaths.add(dirname)
308 end
309 end
310
311 var candidate: nullable String = null
312 for dirname in lookpaths do
313 var try_file = (dirname + "/" + name + ".nit").simplify_path
314 tries.add try_file
315 if try_file.file_exists then
316 if candidate == null then
317 candidate = try_file
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}")
324 end
325 end
326 end
327 try_file = (dirname + "/" + name + "/" + name + ".nit").simplify_path
328 if try_file.file_exists then
329 if candidate == null then
330 candidate = try_file
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}")
337 end
338 end
339 end
340 end
341 if candidate == null then
342 if origmmodule != null then
343 error(anode, "Error: cannot find module {name} from {origmmodule}. tried {tries.join(", ")}")
344 else
345 error(anode, "Error: cannot find module {name}. tried {tries.join(", ")}")
346 end
347 return null
348 end
349 var res = self.load_module(mmodule, candidate)
350 if res == null then return null # Forward error
351 return res.mmodule.as(not null)
352 end
353
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
358 end
359 return path
360 end
361
362 # loaded module by absolute path
363 private var loaded_nmodules = new HashMap[String, AModule]
364
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
369 do
370 if filename.file_extension != "nit" then
371 self.toolcontext.error(null, "Error: file {filename} is not a valid nit module.")
372 return null
373 end
374 if not filename.file_exists then
375 self.toolcontext.error(null, "Error: file {filename} not found.")
376 return null
377 end
378
379 var module_path = module_absolute_path(filename)
380 if loaded_nmodules.keys.has(module_path) then
381 return loaded_nmodules[module_path]
382 end
383
384 var x = if owner != null then owner.to_s else "."
385 self.toolcontext.info("load module {filename} in {x}", 2)
386
387 # Load the file
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
392 file.close
393 var mod_name = filename.basename(".nit")
394 return load_module_commons(owner, tree, mod_name)
395 end
396
397 fun load_rt_module(owner: MModule, nmodule: AModule, mod_name: String): nullable AModule
398 do
399 # Create the module
400 var mmodule = new MModule(model, owner, mod_name, nmodule.location)
401 nmodule.mmodule = mmodule
402 nmodules.add(nmodule)
403 self.mmodule2nmodule[mmodule] = nmodule
404
405 var imported_modules = new Array[MModule]
406
407 imported_modules.add(owner)
408 mmodule.set_visibility_for(owner, intrude_visibility)
409
410 mmodule.set_imported_mmodules(imported_modules)
411
412 return nmodule
413 end
414
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
419 do
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)
426 return null
427 end
428
429 # Check the module name
430 var decl = nmodule.n_moduledecl
431 if decl == null then
432 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
433 else
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}")
437 end
438 end
439
440 # Create the module
441 var mmodule = new MModule(model, owner, mod_name, nmodule.location)
442 nmodule.mmodule = mmodule
443 nmodules.add(nmodule)
444 self.mmodule2nmodule[mmodule] = nmodule
445
446 build_module_importation(nmodule)
447
448 return nmodule
449 end
450
451 # Analysis the module importation and fill the module_importation_hierarchy
452 private fun build_module_importation(nmodule: AModule)
453 do
454 if nmodule.is_importation_done then return
455 nmodule.is_importation_done = true
456 var mmodule = nmodule.mmodule.as(not null)
457 var stdimport = true
458 var imported_modules = new Array[MModule]
459 for aimport in nmodule.n_imports do
460 stdimport = false
461 if not aimport isa AStdImport then
462 continue
463 end
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.")
472 return
473 end
474 mmodule.set_visibility_for(sup, mvisibility)
475 end
476 if stdimport then
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)
482 end
483 end
484 self.toolcontext.info("{mmodule} imports {imported_modules.join(", ")}", 3)
485 mmodule.set_imported_mmodules(imported_modules)
486 end
487
488 # All the loaded modules
489 var nmodules: Array[AModule] = new Array[AModule]
490
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]
494
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)
498 do
499 self.toolcontext.error(n.hot_location, text)
500 end
501
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)
505 do
506 self.toolcontext.warning(n.hot_location, text)
507 end
508
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
511 do
512 var res = mmodule.try_get_primitive_method(name, recv)
513 if res == null then
514 self.toolcontext.fatal_error(n.hot_location, "Fatal Error: {recv} must have a property named {name}.")
515 abort
516 end
517 return res
518 end
519 end
520
521 redef class AStdImport
522 # The imported module once determined
523 var mmodule: nullable MModule = null
524 end
525
526 redef class AModule
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
531 end
532
533 redef class AVisibility
534 # The visibility level associated with the AST node class
535 fun mvisibility: MVisibility is abstract
536 end
537 redef class AIntrudeVisibility
538 redef fun mvisibility do return intrude_visibility
539 end
540 redef class APublicVisibility
541 redef fun mvisibility do return public_visibility
542 end
543 redef class AProtectedVisibility
544 redef fun mvisibility do return protected_visibility
545 end
546 redef class APrivateVisibility
547 redef fun mvisibility do return private_visibility
548 end