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