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