modelbuilder: TODO, check for useless importation
[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 # Option --ignore-visibility
47 var opt_ignore_visibility: OptionBool = new OptionBool("Do not check, and produce errors, on visibility issues.", "--ignore-visibility")
48
49 redef init
50 do
51 super
52 option_context.add_option(opt_path, opt_only_parse, opt_only_metamodel, opt_ignore_visibility)
53 end
54
55 fun modelbuilder: ModelBuilder do return modelbuilder_real.as(not null)
56 private var modelbuilder_real: nullable ModelBuilder = null
57
58 # Run `process_mainmodule` on all phases
59 fun run_global_phases(mmodules: Array[MModule])
60 do
61 assert not mmodules.is_empty
62 var mainmodule
63 if mmodules.length == 1 then
64 mainmodule = mmodules.first
65 else
66 # We need a main module, so we build it by importing all modules
67 mainmodule = new MModule(modelbuilder.model, null, mmodules.first.name, new Location(mmodules.first.location.file, 0, 0, 0, 0))
68 mainmodule.is_fictive = true
69 mainmodule.set_imported_mmodules(mmodules)
70 end
71 for phase in phases_list do
72 if phase.disabled then continue
73 phase.process_mainmodule(mainmodule, mmodules)
74 end
75 end
76 end
77
78 redef class Phase
79 # Specific action to execute on the whole program.
80 # Called by the `ToolContext::run_global_phases`.
81 #
82 # `mainmodule` is the main module of the program.
83 # It could be an implicit module (called like the first given_mmodules).
84 #
85 # `given_modules` is the list of explicitely requested modules.
86 # from the command-line for instance.
87 #
88 # REQUIRE: `not given_modules.is_empty`
89 # REQUIRE: `(given_modules.length == 1) == (mainmodule == given_modules.first)`
90 #
91 # @toimplement
92 fun process_mainmodule(mainmodule: MModule, given_mmodules: SequenceRead[MModule]) do end
93 end
94
95
96 # A model builder knows how to load nit source files and build the associated model
97 class ModelBuilder
98 # The model where new modules, classes and properties are added
99 var model: Model
100
101 # The toolcontext used to control the interaction with the user (getting options and displaying messages)
102 var toolcontext: ToolContext
103
104 # Run phases on all loaded modules
105 fun run_phases
106 do
107 var mmodules = model.mmodules.to_a
108 model.mmodule_importation_hierarchy.sort(mmodules)
109 var nmodules = new Array[AModule]
110 for mm in mmodules do
111 nmodules.add(mmodule2nmodule[mm])
112 end
113 toolcontext.run_phases(nmodules)
114
115 if toolcontext.opt_only_metamodel.value then
116 self.toolcontext.info("*** ONLY METAMODEL", 1)
117 exit(0)
118 end
119 end
120
121 # Instantiate a modelbuilder for a model and a toolcontext
122 # Important, the options of the toolcontext must be correctly set (parse_option already called)
123 init(model: Model, toolcontext: ToolContext)
124 do
125 self.model = model
126 self.toolcontext = toolcontext
127 assert toolcontext.modelbuilder_real == null
128 toolcontext.modelbuilder_real = self
129
130 # Setup the paths value
131 paths.append(toolcontext.opt_path.value)
132
133 var path_env = "NIT_PATH".environ
134 if not path_env.is_empty then
135 paths.append(path_env.split_with(':'))
136 end
137
138 var nit_dir = toolcontext.nit_dir
139 if nit_dir != null then
140 var libname = "{nit_dir}/lib"
141 if libname.file_exists then paths.add(libname)
142 end
143 end
144
145 # Load a bunch of modules.
146 # `modules` can contains filenames or module names.
147 # Imported modules are automatically loaded and modelized.
148 # The result is the corresponding model elements.
149 # Errors and warnings are printed with the toolcontext.
150 #
151 # Note: class and property model element are not analysed.
152 fun parse(modules: Sequence[String]): Array[MModule]
153 do
154 var time0 = get_time
155 # Parse and recursively load
156 self.toolcontext.info("*** PARSE ***", 1)
157 var mmodules = new ArraySet[MModule]
158 for a in modules do
159 var nmodule = self.load_module(a)
160 if nmodule == null then continue # Skip error
161 mmodules.add(nmodule.mmodule.as(not null))
162 end
163 var time1 = get_time
164 self.toolcontext.info("*** END PARSE: {time1-time0} ***", 2)
165
166 self.toolcontext.check_errors
167
168 if toolcontext.opt_only_parse.value then
169 self.toolcontext.info("*** ONLY PARSE...", 1)
170 exit(0)
171 end
172
173 return mmodules.to_a
174 end
175
176 # Return a class named `name` visible by the module `mmodule`.
177 # Visibility in modules is correctly handled.
178 # If no such a class exists, then null is returned.
179 # If more than one class exists, then an error on `anode` is displayed and null is returned.
180 # FIXME: add a way to handle class name conflict
181 fun try_get_mclass_by_name(anode: ANode, mmodule: MModule, name: String): nullable MClass
182 do
183 var classes = model.get_mclasses_by_name(name)
184 if classes == null then
185 return null
186 end
187
188 var res: nullable MClass = null
189 for mclass in classes do
190 if not mmodule.in_importation <= mclass.intro_mmodule then continue
191 if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then continue
192 if res == null then
193 res = mclass
194 else
195 error(anode, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
196 return null
197 end
198 end
199 return res
200 end
201
202 # Return a property named `name` on the type `mtype` visible in the module `mmodule`.
203 # Visibility in modules is correctly handled.
204 # Protected properties are returned (it is up to the caller to check and reject protected properties).
205 # If no such a property exists, then null is returned.
206 # If more than one property exists, then an error on `anode` is displayed and null is returned.
207 # FIXME: add a way to handle property name conflict
208 fun try_get_mproperty_by_name2(anode: ANode, mmodule: MModule, mtype: MType, name: String): nullable MProperty
209 do
210 var props = self.model.get_mproperties_by_name(name)
211 if props == null then
212 return null
213 end
214
215 var cache = self.try_get_mproperty_by_name2_cache[mmodule, mtype, name]
216 if cache != null then return cache
217
218 var res: nullable MProperty = null
219 var ress: nullable Array[MProperty] = null
220 for mprop in props do
221 if not mtype.has_mproperty(mmodule, mprop) then continue
222 if not mmodule.is_visible(mprop.intro_mclassdef.mmodule, mprop.visibility) then continue
223 if res == null then
224 res = mprop
225 continue
226 end
227
228 # Two global properties?
229 # First, special case for init, keep the most specific ones
230 if res isa MMethod and mprop isa MMethod and res.is_init and mprop.is_init then
231 var restype = res.intro_mclassdef.bound_mtype
232 var mproptype = mprop.intro_mclassdef.bound_mtype
233 if mproptype.is_subtype(mmodule, null, restype) then
234 # found a most specific constructor, so keep it
235 res = mprop
236 continue
237 end
238 end
239
240 # Ok, just keep all prop in the ress table
241 if ress == null then
242 ress = new Array[MProperty]
243 ress.add(res)
244 end
245 ress.add(mprop)
246 end
247
248 # There is conflict?
249 if ress != null and res isa MMethod and res.is_init then
250 # special case forinit again
251 var restype = res.intro_mclassdef.bound_mtype
252 var ress2 = new Array[MProperty]
253 for mprop in ress do
254 var mproptype = mprop.intro_mclassdef.bound_mtype
255 if not restype.is_subtype(mmodule, null, mproptype) then
256 ress2.add(mprop)
257 else if not mprop isa MMethod or not mprop.is_init then
258 ress2.add(mprop)
259 end
260 end
261 if ress2.is_empty then
262 ress = null
263 else
264 ress = ress2
265 ress.add(res)
266 end
267 end
268
269 if ress != null then
270 assert ress.length > 1
271 var s = new Array[String]
272 for mprop in ress do s.add mprop.full_name
273 self.error(anode, "Ambigous property name '{name}' for {mtype}; conflict between {s.join(" and ")}")
274 end
275
276 self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res
277 return res
278 end
279
280 private var try_get_mproperty_by_name2_cache: HashMap3[MModule, MType, String, nullable MProperty] = new HashMap3[MModule, MType, String, nullable MProperty]
281
282
283 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
284 fun try_get_mproperty_by_name(anode: ANode, mclassdef: MClassDef, name: String): nullable MProperty
285 do
286 return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name)
287 end
288
289 # The list of directories to search for top level modules
290 # The list is initially set with :
291 # * the toolcontext --path option
292 # * the NIT_PATH environment variable
293 # * `toolcontext.nit_dir`
294 # Path can be added (or removed) by the client
295 var paths: Array[String] = new Array[String]
296
297 # Like (an used by) `get_mmodule_by_name` but just return the ModulePath
298 private fun search_mmodule_by_name(anode: ANode, mgroup: nullable MGroup, name: String): nullable ModulePath
299 do
300 # First, look in groups
301 var c = mgroup
302 while c != null do
303 var dirname = c.filepath
304 if dirname == null then break # virtual group
305 if dirname.has_suffix(".nit") then break # singleton project
306
307 # Second, try the directory to find a file
308 var try_file = dirname + "/" + name + ".nit"
309 if try_file.file_exists then
310 var res = self.identify_file(try_file.simplify_path)
311 assert res != null
312 return res
313 end
314
315 # Third, try if the requested module is itself a group
316 try_file = dirname + "/" + name + "/" + name + ".nit"
317 if try_file.file_exists then
318 var res = self.identify_file(try_file.simplify_path)
319 assert res != null
320 return res
321 end
322
323 c = c.parent
324 end
325
326 # Look at some known directories
327 var lookpaths = self.paths
328
329 # Look in the directory of the group project also (even if not explicitely in the path)
330 if mgroup != null then
331 # path of the root group
332 var dirname = mgroup.mproject.root.filepath
333 if dirname != null then
334 dirname = dirname.join_path("..").simplify_path
335 if not lookpaths.has(dirname) and dirname.file_exists then
336 lookpaths = lookpaths.to_a
337 lookpaths.add(dirname)
338 end
339 end
340 end
341
342 var candidate = search_module_in_paths(anode.hot_location, name, lookpaths)
343
344 if candidate == null then
345 if mgroup != null then
346 error(anode, "Error: cannot find module {name} from {mgroup.name}. tried {lookpaths.join(", ")}")
347 else
348 error(anode, "Error: cannot find module {name}. tried {lookpaths.join(", ")}")
349 end
350 return null
351 end
352 return candidate
353 end
354
355 # Get a module by its short name; if required, the module is loaded, parsed and its hierarchies computed.
356 # If `mgroup` is set, then the module search starts from it up to the top level (see `paths`);
357 # if `mgroup` is null then the module is searched in the top level only.
358 # If no module exists or there is a name conflict, then an error on `anode` is displayed and null is returned.
359 fun get_mmodule_by_name(anode: ANode, mgroup: nullable MGroup, name: String): nullable MModule
360 do
361 var path = search_mmodule_by_name(anode, mgroup, name)
362 if path == null then return null # Forward error
363 var res = self.load_module(path.filepath)
364 if res == null then return null # Forward error
365 return res.mmodule.as(not null)
366 end
367
368 # Search a module `name` from path `lookpaths`.
369 # If found, the path of the file is returned
370 private fun search_module_in_paths(location: nullable Location, name: String, lookpaths: Collection[String]): nullable ModulePath
371 do
372 var candidate: nullable String = null
373 for dirname in lookpaths do
374 var try_file = (dirname + "/" + name + ".nit").simplify_path
375 if try_file.file_exists then
376 if candidate == null then
377 candidate = try_file
378 else if candidate != try_file then
379 # try to disambiguate conflicting modules
380 var abs_candidate = module_absolute_path(candidate)
381 var abs_try_file = module_absolute_path(try_file)
382 if abs_candidate != abs_try_file then
383 toolcontext.error(location, "Error: conflicting module file for {name}: {candidate} {try_file}")
384 end
385 end
386 end
387 try_file = (dirname + "/" + name + "/" + name + ".nit").simplify_path
388 if try_file.file_exists then
389 if candidate == null then
390 candidate = try_file
391 else if candidate != try_file then
392 # try to disambiguate conflicting modules
393 var abs_candidate = module_absolute_path(candidate)
394 var abs_try_file = module_absolute_path(try_file)
395 if abs_candidate != abs_try_file then
396 toolcontext.error(location, "Error: conflicting module file for {name}: {candidate} {try_file}")
397 end
398 end
399 end
400 end
401 if candidate == null then return null
402 return identify_file(candidate)
403 end
404
405 # cache for `identify_file` by realpath
406 private var identified_files = new HashMap[String, nullable ModulePath]
407
408 # Identify a source file
409 # Load the associated project and groups if required
410 private fun identify_file(path: String): nullable ModulePath
411 do
412 # special case for not a nit file
413 if path.file_extension != "nit" then
414 # search in known -I paths
415 var res = search_module_in_paths(null, path, self.paths)
416 if res != null then return res
417
418 # Found nothins? maybe it is a group...
419 var candidate = null
420 if path.file_exists then
421 var mgroup = get_mgroup(path)
422 if mgroup != null then
423 var owner_path = mgroup.filepath.join_path(mgroup.name + ".nit")
424 if owner_path.file_exists then candidate = owner_path
425 end
426 end
427
428 if candidate == null then
429 toolcontext.error(null, "Error: cannot find module `{path}`.")
430 return null
431 end
432 path = candidate
433 end
434
435 # Fast track, the path is already known
436 var pn = path.basename(".nit")
437 var rp = module_absolute_path(path)
438 if identified_files.has_key(rp) then return identified_files[rp]
439
440 # Search for a group
441 var mgrouppath = path.join_path("..").simplify_path
442 var mgroup = get_mgroup(mgrouppath)
443
444 if mgroup == null then
445 # singleton project
446 var mproject = new MProject(pn, model)
447 mgroup = new MGroup(pn, mproject, null) # same name for the root group
448 mgroup.filepath = path
449 mproject.root = mgroup
450 toolcontext.info("found project `{pn}` at {path}", 2)
451 end
452
453 var res = new ModulePath(pn, path, mgroup)
454 mgroup.module_paths.add(res)
455
456 identified_files[rp] = res
457 return res
458 end
459
460 # groups by path
461 private var mgroups = new HashMap[String, nullable MGroup]
462
463 # return the mgroup associated to a directory path
464 # if the directory is not a group null is returned
465 private fun get_mgroup(dirpath: String): nullable MGroup
466 do
467 var rdp = module_absolute_path(dirpath)
468 if mgroups.has_key(rdp) then
469 return mgroups[rdp]
470 end
471
472 # Hack, a group is determined by:
473 # * the presence of a honomymous nit file
474 # * the fact that the directory is named `src`
475 var pn = rdp.basename(".nit")
476 var mp = dirpath.join_path(pn + ".nit").simplify_path
477
478 var dirpath2 = dirpath
479 if not mp.file_exists then
480 if pn == "src" then
481 # With a src directory, the group name is the name of the parent directory
482 dirpath2 = rdp.dirname
483 pn = dirpath2.basename("")
484 else
485 return null
486 end
487 end
488
489 # check parent directory
490 var parentpath = dirpath.join_path("..").simplify_path
491 var parent = get_mgroup(parentpath)
492
493 var mgroup
494 if parent == null then
495 # no parent, thus new project
496 var mproject = new MProject(pn, model)
497 mgroup = new MGroup(pn, mproject, null) # same name for the root group
498 mproject.root = mgroup
499 toolcontext.info("found project `{mproject}` at {dirpath}", 2)
500 else
501 mgroup = new MGroup(pn, parent.mproject, parent)
502 toolcontext.info("found sub group `{mgroup.full_name}` at {dirpath}", 2)
503 end
504 var readme = dirpath2.join_path("README.md")
505 if not readme.file_exists then readme = dirpath2.join_path("README")
506 if readme.file_exists then
507 var mdoc = new MDoc
508 var s = new IFStream.open(readme)
509 while not s.eof do
510 mdoc.content.add(s.read_line)
511 end
512 mgroup.mdoc = mdoc
513 mdoc.original_mentity = mgroup
514 end
515 mgroup.filepath = dirpath
516 mgroups[rdp] = mgroup
517 return mgroup
518 end
519
520 # Transform relative paths (starting with '../') into absolute paths
521 private fun module_absolute_path(path: String): String do
522 return getcwd.join_path(path).simplify_path
523 end
524
525 # Try to load a module AST using a path.
526 # Display an error if there is a problem (IO / lexer / parser) and return null
527 fun load_module_ast(filename: String): nullable AModule
528 do
529 if filename.file_extension != "nit" then
530 self.toolcontext.error(null, "Error: file {filename} is not a valid nit module.")
531 return null
532 end
533 if not filename.file_exists then
534 self.toolcontext.error(null, "Error: file {filename} not found.")
535 return null
536 end
537
538 self.toolcontext.info("load module {filename}", 2)
539
540 # Load the file
541 var file = new IFStream.open(filename)
542 var lexer = new Lexer(new SourceFile(filename, file))
543 var parser = new Parser(lexer)
544 var tree = parser.parse
545 file.close
546 var mod_name = filename.basename(".nit")
547
548 # Handle lexer and parser error
549 var nmodule = tree.n_base
550 if nmodule == null then
551 var neof = tree.n_eof
552 assert neof isa AError
553 error(neof, neof.message)
554 return null
555 end
556
557 return nmodule
558 end
559
560 # Try to load a module and its imported modules using a path.
561 # Display an error if there is a problem (IO / lexer / parser / importation) and return null
562 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
563 fun load_module(filename: String): nullable AModule
564 do
565 # Look for the module
566 var file = identify_file(filename)
567 if file == null then return null # forward error
568
569 # Already known and loaded? then return it
570 var mmodule = file.mmodule
571 if mmodule != null then
572 return mmodule2nmodule[mmodule]
573 end
574
575 # Load it manually
576 var nmodule = load_module_ast(file.filepath)
577 if nmodule == null then return null # forward error
578
579 # build the mmodule and load imported modules
580 mmodule = build_a_mmodule(file.mgroup, file.name, nmodule)
581
582 if mmodule == null then return null # forward error
583
584 # Update the file information
585 file.mmodule = mmodule
586
587 # Load imported module
588 build_module_importation(nmodule)
589
590 return nmodule
591 end
592
593 fun load_rt_module(parent: MModule, nmodule: AModule, mod_name: String): nullable AModule
594 do
595 # Create the module
596 var mmodule = new MModule(model, parent.mgroup, mod_name, nmodule.location)
597 nmodule.mmodule = mmodule
598 nmodules.add(nmodule)
599 self.mmodule2nmodule[mmodule] = nmodule
600
601 var imported_modules = new Array[MModule]
602
603 imported_modules.add(parent)
604 mmodule.set_visibility_for(parent, intrude_visibility)
605
606 mmodule.set_imported_mmodules(imported_modules)
607
608 return nmodule
609 end
610
611 # Visit the AST and create the `MModule` object
612 private fun build_a_mmodule(mgroup: nullable MGroup, mod_name: String, nmodule: AModule): nullable MModule
613 do
614 # Check the module name
615 var decl = nmodule.n_moduledecl
616 if decl == null then
617 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
618 else
619 var decl_name = decl.n_name.n_id.text
620 if decl_name != mod_name then
621 error(decl.n_name, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
622 end
623 end
624
625 # Create the module
626 var mmodule = new MModule(model, mgroup, mod_name, nmodule.location)
627 nmodule.mmodule = mmodule
628 nmodules.add(nmodule)
629 self.mmodule2nmodule[mmodule] = nmodule
630
631 if decl != null then
632 var ndoc = decl.n_doc
633 if ndoc != null then
634 var mdoc = ndoc.to_mdoc
635 mmodule.mdoc = mdoc
636 mdoc.original_mentity = mmodule
637 end
638 end
639
640 return mmodule
641 end
642
643 # Analysis the module importation and fill the module_importation_hierarchy
644 private fun build_module_importation(nmodule: AModule)
645 do
646 if nmodule.is_importation_done then return
647 nmodule.is_importation_done = true
648 var mmodule = nmodule.mmodule.as(not null)
649 var stdimport = true
650 var imported_modules = new Array[MModule]
651 for aimport in nmodule.n_imports do
652 stdimport = false
653 if not aimport isa AStdImport then
654 continue
655 end
656 var mgroup = mmodule.mgroup
657 if aimport.n_name.n_quad != null then mgroup = null # Start from top level
658 for grp in aimport.n_name.n_path do
659 var path = search_mmodule_by_name(grp, mgroup, grp.text)
660 if path == null then return # Skip error
661 mgroup = path.mgroup
662 end
663 var mod_name = aimport.n_name.n_id.text
664 var sup = self.get_mmodule_by_name(aimport.n_name, mgroup, mod_name)
665 if sup == null then continue # Skip error
666 aimport.mmodule = sup
667 imported_modules.add(sup)
668 var mvisibility = aimport.n_visibility.mvisibility
669 if mvisibility == protected_visibility then
670 error(aimport.n_visibility, "Error: only properties can be protected.")
671 return
672 end
673 if sup == mmodule then
674 error(aimport.n_name, "Error: Dependency loop in module {mmodule}.")
675 end
676 if sup.in_importation < mmodule then
677 error(aimport.n_name, "Error: Dependency loop between modules {mmodule} and {sup}.")
678 return
679 end
680 mmodule.set_visibility_for(sup, mvisibility)
681 end
682 if stdimport then
683 var mod_name = "standard"
684 var sup = self.get_mmodule_by_name(nmodule, null, mod_name)
685 if sup != null then # Skip error
686 imported_modules.add(sup)
687 mmodule.set_visibility_for(sup, public_visibility)
688 end
689 end
690 self.toolcontext.info("{mmodule} imports {imported_modules.join(", ")}", 3)
691 mmodule.set_imported_mmodules(imported_modules)
692
693 # TODO: Correctly check for useless importation
694 # It is even doable?
695 var directs = mmodule.in_importation.direct_greaters
696 for nim in nmodule.n_imports do
697 if not nim isa AStdImport then continue
698 var im = nim.mmodule
699 if im == null then continue
700 if directs.has(im) then continue
701 # This generates so much noise that it is simpler to just comment it
702 #warning(nim, "Warning: possible useless importation of {im}")
703 end
704 end
705
706 # All the loaded modules
707 var nmodules: Array[AModule] = new Array[AModule]
708
709 # Register the nmodule associated to each mmodule
710 # FIXME: why not refine the `MModule` class with a nullable attribute?
711 var mmodule2nmodule: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
712
713 # Helper function to display an error on a node.
714 # Alias for `self.toolcontext.error(n.hot_location, text)`
715 fun error(n: ANode, text: String)
716 do
717 self.toolcontext.error(n.hot_location, text)
718 end
719
720 # Helper function to display a warning on a node.
721 # Alias for: `self.toolcontext.warning(n.hot_location, text)`
722 fun warning(n: ANode, text: String)
723 do
724 self.toolcontext.warning(n.hot_location, text)
725 end
726
727 # Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
728 fun force_get_primitive_method(n: ANode, name: String, recv: MClass, mmodule: MModule): MMethod
729 do
730 var res = mmodule.try_get_primitive_method(name, recv)
731 if res == null then
732 self.toolcontext.fatal_error(n.hot_location, "Fatal Error: {recv} must have a property named {name}.")
733 abort
734 end
735 return res
736 end
737 end
738
739 # placeholder to a module file identified but not always loaded in a project
740 private class ModulePath
741 # The name of the module
742 # (it's the basename of the filepath)
743 var name: String
744
745 # The human path of the module
746 var filepath: String
747
748 # The group (and the project) of the possible module
749 var mgroup: MGroup
750
751 # The loaded module (if any)
752 var mmodule: nullable MModule = null
753
754 redef fun to_s do return filepath
755 end
756
757 redef class MGroup
758 # modules paths associated with the group
759 private var module_paths = new Array[ModulePath]
760 end
761
762 redef class AStdImport
763 # The imported module once determined
764 var mmodule: nullable MModule = null
765 end
766
767 redef class AModule
768 # The associated MModule once build by a `ModelBuilder`
769 var mmodule: nullable MModule
770 # Flag that indicate if the importation is already completed
771 var is_importation_done: Bool = false
772 end
773
774 redef class AVisibility
775 # The visibility level associated with the AST node class
776 fun mvisibility: MVisibility is abstract
777 end
778 redef class AIntrudeVisibility
779 redef fun mvisibility do return intrude_visibility
780 end
781 redef class APublicVisibility
782 redef fun mvisibility do return public_visibility
783 end
784 redef class AProtectedVisibility
785 redef fun mvisibility do return protected_visibility
786 end
787 redef class APrivateVisibility
788 redef fun mvisibility do return private_visibility
789 end
790
791 redef class ADoc
792 private var mdoc_cache: nullable MDoc
793 fun to_mdoc: MDoc
794 do
795 var res = mdoc_cache
796 if res != null then return res
797 res = new MDoc
798 for c in n_comment do
799 var text = c.text
800 if text.length < 2 then
801 res.content.add ""
802 continue
803 end
804 assert text.chars[0] == '#'
805 if text.chars[1] == ' ' then
806 text = text.substring_from(2) # eat starting `#` and space
807 else
808 text = text.substring_from(1) # eat atarting `#` only
809 end
810 if text.chars.last == '\n' then text = text.substring(0, text.length-1) # drop \n
811 res.content.add(text)
812 end
813 mdoc_cache = res
814 return res
815 end
816 end