src: adapt tools to use `ToolContext::nit_dir` instead of `NIT_DIR`
[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 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 dir is determined by the presence of a honomymous nit file
472 var pn = rdp.basename(".nit")
473 var mp = dirpath.join_path(pn + ".nit").simplify_path
474
475 if not mp.file_exists then return null
476
477 # check parent directory
478 var parentpath = dirpath.join_path("..").simplify_path
479 var parent = get_mgroup(parentpath)
480
481 var mgroup
482 if parent == null then
483 # no parent, thus new project
484 var mproject = new MProject(pn, model)
485 mgroup = new MGroup(pn, mproject, null) # same name for the root group
486 mproject.root = mgroup
487 toolcontext.info("found project `{mproject}` at {dirpath}", 2)
488 else
489 mgroup = new MGroup(pn, parent.mproject, parent)
490 toolcontext.info("found sub group `{mgroup.full_name}` at {dirpath}", 2)
491 end
492 mgroup.filepath = dirpath
493 mgroups[rdp] = mgroup
494 return mgroup
495 end
496
497 # Transform relative paths (starting with '../') into absolute paths
498 private fun module_absolute_path(path: String): String do
499 return getcwd.join_path(path).simplify_path
500 end
501
502 # Try to load a module AST using a path.
503 # Display an error if there is a problem (IO / lexer / parser) and return null
504 fun load_module_ast(filename: String): nullable AModule
505 do
506 if filename.file_extension != "nit" then
507 self.toolcontext.error(null, "Error: file {filename} is not a valid nit module.")
508 return null
509 end
510 if not filename.file_exists then
511 self.toolcontext.error(null, "Error: file {filename} not found.")
512 return null
513 end
514
515 self.toolcontext.info("load module {filename}", 2)
516
517 # Load the file
518 var file = new IFStream.open(filename)
519 var lexer = new Lexer(new SourceFile(filename, file))
520 var parser = new Parser(lexer)
521 var tree = parser.parse
522 file.close
523 var mod_name = filename.basename(".nit")
524
525 # Handle lexer and parser error
526 var nmodule = tree.n_base
527 if nmodule == null then
528 var neof = tree.n_eof
529 assert neof isa AError
530 error(neof, neof.message)
531 return null
532 end
533
534 return nmodule
535 end
536
537 # Try to load a module and its imported modules using a path.
538 # Display an error if there is a problem (IO / lexer / parser / importation) and return null
539 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
540 fun load_module(filename: String): nullable AModule
541 do
542 # Look for the module
543 var file = identify_file(filename)
544 if file == null then return null # forward error
545
546 # Already known and loaded? then return it
547 var mmodule = file.mmodule
548 if mmodule != null then
549 return mmodule2nmodule[mmodule]
550 end
551
552 # Load it manually
553 var nmodule = load_module_ast(file.filepath)
554 if nmodule == null then return null # forward error
555
556 # build the mmodule and load imported modules
557 mmodule = build_a_mmodule(file.mgroup, file.name, nmodule)
558
559 if mmodule == null then return null # forward error
560
561 # Update the file information
562 file.mmodule = mmodule
563
564 # Load imported module
565 build_module_importation(nmodule)
566
567 return nmodule
568 end
569
570 fun load_rt_module(parent: MModule, nmodule: AModule, mod_name: String): nullable AModule
571 do
572 # Create the module
573 var mmodule = new MModule(model, parent.mgroup, mod_name, nmodule.location)
574 nmodule.mmodule = mmodule
575 nmodules.add(nmodule)
576 self.mmodule2nmodule[mmodule] = nmodule
577
578 var imported_modules = new Array[MModule]
579
580 imported_modules.add(parent)
581 mmodule.set_visibility_for(parent, intrude_visibility)
582
583 mmodule.set_imported_mmodules(imported_modules)
584
585 return nmodule
586 end
587
588 # Visit the AST and create the `MModule` object
589 private fun build_a_mmodule(mgroup: nullable MGroup, mod_name: String, nmodule: AModule): nullable MModule
590 do
591 # Check the module name
592 var decl = nmodule.n_moduledecl
593 if decl == null then
594 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
595 else
596 var decl_name = decl.n_name.n_id.text
597 if decl_name != mod_name then
598 error(decl.n_name, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
599 end
600 end
601
602 # Create the module
603 var mmodule = new MModule(model, mgroup, mod_name, nmodule.location)
604 nmodule.mmodule = mmodule
605 nmodules.add(nmodule)
606 self.mmodule2nmodule[mmodule] = nmodule
607
608 if decl != null then
609 var ndoc = decl.n_doc
610 if ndoc != null then mmodule.mdoc = ndoc.to_mdoc
611 end
612
613 return mmodule
614 end
615
616 # Analysis the module importation and fill the module_importation_hierarchy
617 private fun build_module_importation(nmodule: AModule)
618 do
619 if nmodule.is_importation_done then return
620 nmodule.is_importation_done = true
621 var mmodule = nmodule.mmodule.as(not null)
622 var stdimport = true
623 var imported_modules = new Array[MModule]
624 for aimport in nmodule.n_imports do
625 stdimport = false
626 if not aimport isa AStdImport then
627 continue
628 end
629 var mgroup = mmodule.mgroup
630 if aimport.n_name.n_quad != null then mgroup = null # Start from top level
631 for grp in aimport.n_name.n_path do
632 var path = search_mmodule_by_name(grp, mgroup, grp.text)
633 if path == null then return # Skip error
634 mgroup = path.mgroup
635 end
636 var mod_name = aimport.n_name.n_id.text
637 var sup = self.get_mmodule_by_name(aimport.n_name, mgroup, mod_name)
638 if sup == null then continue # Skip error
639 aimport.mmodule = sup
640 imported_modules.add(sup)
641 var mvisibility = aimport.n_visibility.mvisibility
642 if mvisibility == protected_visibility then
643 error(aimport.n_visibility, "Error: only properties can be protected.")
644 return
645 end
646 if sup == mmodule then
647 error(aimport.n_name, "Error: Dependency loop in module {mmodule}.")
648 end
649 if sup.in_importation < mmodule then
650 error(aimport.n_name, "Error: Dependency loop between modules {mmodule} and {sup}.")
651 return
652 end
653 mmodule.set_visibility_for(sup, mvisibility)
654 end
655 if stdimport then
656 var mod_name = "standard"
657 var sup = self.get_mmodule_by_name(nmodule, null, mod_name)
658 if sup != null then # Skip error
659 imported_modules.add(sup)
660 mmodule.set_visibility_for(sup, public_visibility)
661 end
662 end
663 self.toolcontext.info("{mmodule} imports {imported_modules.join(", ")}", 3)
664 mmodule.set_imported_mmodules(imported_modules)
665 end
666
667 # All the loaded modules
668 var nmodules: Array[AModule] = new Array[AModule]
669
670 # Register the nmodule associated to each mmodule
671 # FIXME: why not refine the `MModule` class with a nullable attribute?
672 var mmodule2nmodule: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
673
674 # Helper function to display an error on a node.
675 # Alias for `self.toolcontext.error(n.hot_location, text)`
676 fun error(n: ANode, text: String)
677 do
678 self.toolcontext.error(n.hot_location, text)
679 end
680
681 # Helper function to display a warning on a node.
682 # Alias for: `self.toolcontext.warning(n.hot_location, text)`
683 fun warning(n: ANode, text: String)
684 do
685 self.toolcontext.warning(n.hot_location, text)
686 end
687
688 # Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
689 fun force_get_primitive_method(n: ANode, name: String, recv: MClass, mmodule: MModule): MMethod
690 do
691 var res = mmodule.try_get_primitive_method(name, recv)
692 if res == null then
693 self.toolcontext.fatal_error(n.hot_location, "Fatal Error: {recv} must have a property named {name}.")
694 abort
695 end
696 return res
697 end
698 end
699
700 # placeholder to a module file identified but not always loaded in a project
701 private class ModulePath
702 # The name of the module
703 # (it's the basename of the filepath)
704 var name: String
705
706 # The human path of the module
707 var filepath: String
708
709 # The group (and the project) of the possible module
710 var mgroup: MGroup
711
712 # The loaded module (if any)
713 var mmodule: nullable MModule = null
714
715 redef fun to_s do return filepath
716 end
717
718 redef class MGroup
719 # modules paths associated with the group
720 private var module_paths = new Array[ModulePath]
721 end
722
723 redef class AStdImport
724 # The imported module once determined
725 var mmodule: nullable MModule = null
726 end
727
728 redef class AModule
729 # The associated MModule once build by a `ModelBuilder`
730 var mmodule: nullable MModule
731 # Flag that indicate if the importation is already completed
732 var is_importation_done: Bool = false
733 end
734
735 redef class AVisibility
736 # The visibility level associated with the AST node class
737 fun mvisibility: MVisibility is abstract
738 end
739 redef class AIntrudeVisibility
740 redef fun mvisibility do return intrude_visibility
741 end
742 redef class APublicVisibility
743 redef fun mvisibility do return public_visibility
744 end
745 redef class AProtectedVisibility
746 redef fun mvisibility do return protected_visibility
747 end
748 redef class APrivateVisibility
749 redef fun mvisibility do return private_visibility
750 end
751
752 redef class ADoc
753 private var mdoc_cache: nullable MDoc
754 fun to_mdoc: MDoc
755 do
756 var res = mdoc_cache
757 if res != null then return res
758 res = new MDoc
759 for c in n_comment do
760 var text = c.text
761 if text.length < 2 then
762 res.content.add ""
763 continue
764 end
765 assert text.chars[0] == '#'
766 if text.chars[1] == ' ' then
767 text = text.substring_from(2) # eat starting `#` and space
768 else
769 text = text.substring_from(1) # eat atarting `#` only
770 end
771 if text.chars.last == '\n' then text = text.substring(0, text.length-1) # drop \n
772 res.content.add(text)
773 end
774 mdoc_cache = res
775 return res
776 end
777 end