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