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