model: add optional MDoc::original_mentity
[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
620 var mdoc = ndoc.to_mdoc
621 mmodule.mdoc = mdoc
622 mdoc.original_mentity = mmodule
623 end
624 end
625
626 return mmodule
627 end
628
629 # Analysis the module importation and fill the module_importation_hierarchy
630 private fun build_module_importation(nmodule: AModule)
631 do
632 if nmodule.is_importation_done then return
633 nmodule.is_importation_done = true
634 var mmodule = nmodule.mmodule.as(not null)
635 var stdimport = true
636 var imported_modules = new Array[MModule]
637 for aimport in nmodule.n_imports do
638 stdimport = false
639 if not aimport isa AStdImport then
640 continue
641 end
642 var mgroup = mmodule.mgroup
643 if aimport.n_name.n_quad != null then mgroup = null # Start from top level
644 for grp in aimport.n_name.n_path do
645 var path = search_mmodule_by_name(grp, mgroup, grp.text)
646 if path == null then return # Skip error
647 mgroup = path.mgroup
648 end
649 var mod_name = aimport.n_name.n_id.text
650 var sup = self.get_mmodule_by_name(aimport.n_name, mgroup, mod_name)
651 if sup == null then continue # Skip error
652 aimport.mmodule = sup
653 imported_modules.add(sup)
654 var mvisibility = aimport.n_visibility.mvisibility
655 if mvisibility == protected_visibility then
656 error(aimport.n_visibility, "Error: only properties can be protected.")
657 return
658 end
659 if sup == mmodule then
660 error(aimport.n_name, "Error: Dependency loop in module {mmodule}.")
661 end
662 if sup.in_importation < mmodule then
663 error(aimport.n_name, "Error: Dependency loop between modules {mmodule} and {sup}.")
664 return
665 end
666 mmodule.set_visibility_for(sup, mvisibility)
667 end
668 if stdimport then
669 var mod_name = "standard"
670 var sup = self.get_mmodule_by_name(nmodule, null, mod_name)
671 if sup != null then # Skip error
672 imported_modules.add(sup)
673 mmodule.set_visibility_for(sup, public_visibility)
674 end
675 end
676 self.toolcontext.info("{mmodule} imports {imported_modules.join(", ")}", 3)
677 mmodule.set_imported_mmodules(imported_modules)
678 end
679
680 # All the loaded modules
681 var nmodules: Array[AModule] = new Array[AModule]
682
683 # Register the nmodule associated to each mmodule
684 # FIXME: why not refine the `MModule` class with a nullable attribute?
685 var mmodule2nmodule: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
686
687 # Helper function to display an error on a node.
688 # Alias for `self.toolcontext.error(n.hot_location, text)`
689 fun error(n: ANode, text: String)
690 do
691 self.toolcontext.error(n.hot_location, text)
692 end
693
694 # Helper function to display a warning on a node.
695 # Alias for: `self.toolcontext.warning(n.hot_location, text)`
696 fun warning(n: ANode, text: String)
697 do
698 self.toolcontext.warning(n.hot_location, text)
699 end
700
701 # Force to get the primitive method named `name` on the type `recv` or do a fatal error on `n`
702 fun force_get_primitive_method(n: ANode, name: String, recv: MClass, mmodule: MModule): MMethod
703 do
704 var res = mmodule.try_get_primitive_method(name, recv)
705 if res == null then
706 self.toolcontext.fatal_error(n.hot_location, "Fatal Error: {recv} must have a property named {name}.")
707 abort
708 end
709 return res
710 end
711 end
712
713 # placeholder to a module file identified but not always loaded in a project
714 private class ModulePath
715 # The name of the module
716 # (it's the basename of the filepath)
717 var name: String
718
719 # The human path of the module
720 var filepath: String
721
722 # The group (and the project) of the possible module
723 var mgroup: MGroup
724
725 # The loaded module (if any)
726 var mmodule: nullable MModule = null
727
728 redef fun to_s do return filepath
729 end
730
731 redef class MGroup
732 # modules paths associated with the group
733 private var module_paths = new Array[ModulePath]
734 end
735
736 redef class AStdImport
737 # The imported module once determined
738 var mmodule: nullable MModule = null
739 end
740
741 redef class AModule
742 # The associated MModule once build by a `ModelBuilder`
743 var mmodule: nullable MModule
744 # Flag that indicate if the importation is already completed
745 var is_importation_done: Bool = false
746 end
747
748 redef class AVisibility
749 # The visibility level associated with the AST node class
750 fun mvisibility: MVisibility is abstract
751 end
752 redef class AIntrudeVisibility
753 redef fun mvisibility do return intrude_visibility
754 end
755 redef class APublicVisibility
756 redef fun mvisibility do return public_visibility
757 end
758 redef class AProtectedVisibility
759 redef fun mvisibility do return protected_visibility
760 end
761 redef class APrivateVisibility
762 redef fun mvisibility do return private_visibility
763 end
764
765 redef class ADoc
766 private var mdoc_cache: nullable MDoc
767 fun to_mdoc: MDoc
768 do
769 var res = mdoc_cache
770 if res != null then return res
771 res = new MDoc
772 for c in n_comment do
773 var text = c.text
774 if text.length < 2 then
775 res.content.add ""
776 continue
777 end
778 assert text.chars[0] == '#'
779 if text.chars[1] == ' ' then
780 text = text.substring_from(2) # eat starting `#` and space
781 else
782 text = text.substring_from(1) # eat atarting `#` only
783 end
784 if text.chars.last == '\n' then text = text.substring(0, text.length-1) # drop \n
785 res.content.add(text)
786 end
787 mdoc_cache = res
788 return res
789 end
790 end