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