ni_nitdoc: added fast copy past utility to signatures.
[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 readable 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 readable var _opt_only_metamodel: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
42
43 # Option --only-parse
44 readable var _opt_only_parse: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
45
46 redef init
47 do
48 super
49 option_context.add_option(opt_path, opt_only_parse, opt_only_metamodel)
50 end
51
52 fun modelbuilder: ModelBuilder do return modelbuilder_real.as(not null)
53 private var modelbuilder_real: nullable ModelBuilder = null
54
55 end
56
57 # A model builder knows how to load nit source files and build the associated model
58 class ModelBuilder
59 # The model where new modules, classes and properties are added
60 var model: Model
61
62 # The toolcontext used to control the interaction with the user (getting options and displaying messages)
63 var toolcontext: ToolContext
64
65 # Run phases on all loaded modules
66 fun run_phases
67 do
68 var mmodules = model.mmodules.to_a
69 model.mmodule_importation_hierarchy.sort(mmodules)
70 var nmodules = new Array[AModule]
71 for mm in mmodules do
72 nmodules.add(mmodule2nmodule[mm])
73 end
74 toolcontext.run_phases(nmodules)
75
76 if toolcontext.opt_only_metamodel.value then
77 self.toolcontext.info("*** ONLY METAMODEL", 1)
78 exit(0)
79 end
80 end
81
82 # Instantiate a modelbuilder for a model and a toolcontext
83 # Important, the options of the toolcontext must be correctly set (parse_option already called)
84 init(model: Model, toolcontext: ToolContext)
85 do
86 self.model = model
87 self.toolcontext = toolcontext
88 assert toolcontext.modelbuilder_real == null
89 toolcontext.modelbuilder_real = self
90
91 # Setup the paths value
92 paths.append(toolcontext.opt_path.value)
93
94 var path_env = "NIT_PATH".environ
95 if not path_env.is_empty then
96 paths.append(path_env.split_with(':'))
97 end
98
99 path_env = "NIT_DIR".environ
100 if not path_env.is_empty then
101 var libname = "{path_env}/lib"
102 if libname.file_exists then paths.add(libname)
103 end
104
105 var libname = "{sys.program_name.dirname}/../lib"
106 if libname.file_exists then paths.add(libname.simplify_path)
107 end
108
109 # Load a bunch of modules.
110 # `modules' can contains filenames or module names.
111 # Imported modules are automatically loaded and modelized.
112 # The result is the corresponding model elements.
113 # Errors and warnings are printed with the toolcontext.
114 #
115 # Note: class and property model element are not analysed.
116 fun parse(modules: Sequence[String]): Array[MModule]
117 do
118 var time0 = get_time
119 # Parse and recursively load
120 self.toolcontext.info("*** PARSE ***", 1)
121 var mmodules = new Array[MModule]
122 for a in modules do
123 var nmodule = self.load_module(null, a)
124 if nmodule == null then continue # Skip error
125 mmodules.add(nmodule.mmodule.as(not null))
126 end
127 var time1 = get_time
128 self.toolcontext.info("*** END PARSE: {time1-time0} ***", 2)
129
130 self.toolcontext.check_errors
131
132 if toolcontext.opt_only_parse.value then
133 self.toolcontext.info("*** ONLY PARSE...", 1)
134 exit(0)
135 end
136
137 return mmodules
138 end
139
140 # Return a class named `name' visible by the module `mmodule'.
141 # Visibility in modules is correctly handled.
142 # If no such a class exists, then null is returned.
143 # If more than one class exists, then an error on `anode' is displayed and null is returned.
144 # FIXME: add a way to handle class name conflict
145 fun try_get_mclass_by_name(anode: ANode, mmodule: MModule, name: String): nullable MClass
146 do
147 var classes = model.get_mclasses_by_name(name)
148 if classes == null then
149 return null
150 end
151
152 var res: nullable MClass = null
153 for mclass in classes do
154 if not mmodule.in_importation <= mclass.intro_mmodule then continue
155 if not mmodule.is_visible(mclass.intro_mmodule, mclass.visibility) then continue
156 if res == null then
157 res = mclass
158 else
159 error(anode, "Ambigous class name '{name}'; conflict between {mclass.full_name} and {res.full_name}")
160 return null
161 end
162 end
163 return res
164 end
165
166 # Return a property named `name' on the type `mtype' visible in the module `mmodule'.
167 # Visibility in modules is correctly handled.
168 # Protected properties are returned (it is up to the caller to check and reject protected properties).
169 # If no such a property exists, then null is returned.
170 # If more than one property exists, then an error on `anode' is displayed and null is returned.
171 # FIXME: add a way to handle property name conflict
172 fun try_get_mproperty_by_name2(anode: ANode, mmodule: MModule, mtype: MType, name: String): nullable MProperty
173 do
174 var props = self.model.get_mproperties_by_name(name)
175 if props == null then
176 return null
177 end
178
179 var cache = self.try_get_mproperty_by_name2_cache[mmodule, mtype, name]
180 if cache != null then return cache
181
182 var res: nullable MProperty = null
183 var ress: nullable Array[MProperty] = null
184 for mprop in props do
185 if not mtype.has_mproperty(mmodule, mprop) then continue
186 if not mmodule.is_visible(mprop.intro_mclassdef.mmodule, mprop.visibility) then continue
187 if res == null then
188 res = mprop
189 else
190 var restype = res.intro_mclassdef.bound_mtype
191 var mproptype = mprop.intro_mclassdef.bound_mtype
192 if restype.is_subtype(mmodule, null, mproptype) then
193 # we keep res
194 else if mproptype.is_subtype(mmodule, null, restype) then
195 res = mprop
196 else
197 if ress == null then ress = new Array[MProperty]
198 ress.add(mprop)
199 end
200 end
201 end
202 if ress != null then
203 var restype = res.intro_mclassdef.bound_mtype
204 for mprop in ress do
205 var mproptype = mprop.intro_mclassdef.bound_mtype
206 if not restype.is_subtype(mmodule, null, mproptype) then
207 self.error(anode, "Ambigous property name '{name}' for {mtype}; conflict between {mprop.full_name} and {res.full_name}")
208 return null
209 end
210 end
211 end
212
213 self.try_get_mproperty_by_name2_cache[mmodule, mtype, name] = res
214 return res
215 end
216
217 private var try_get_mproperty_by_name2_cache: HashMap3[MModule, MType, String, nullable MProperty] = new HashMap3[MModule, MType, String, nullable MProperty]
218
219
220 # Alias for try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.mtype, name)
221 fun try_get_mproperty_by_name(anode: ANode, mclassdef: MClassDef, name: String): nullable MProperty
222 do
223 return try_get_mproperty_by_name2(anode, mclassdef.mmodule, mclassdef.bound_mtype, name)
224 end
225
226 # The list of directories to search for top level modules
227 # The list is initially set with :
228 # * the toolcontext --path option
229 # * the NIT_PATH environment variable
230 # * some heuristics including the NIT_DIR environment variable and the progname of the process
231 # Path can be added (or removed) by the client
232 var paths: Array[String] = new Array[String]
233
234 # Get a module by its short name; if required, the module is loaded, parsed and its hierarchies computed.
235 # If `mmodule' is set, then the module search starts from it up to the top level (see `paths');
236 # if `mmodule' is null then the module is searched in the top level only.
237 # If no module exists or there is a name conflict, then an error on `anode' is displayed and null is returned.
238 # FIXME: add a way to handle module name conflict
239 fun get_mmodule_by_name(anode: ANode, mmodule: nullable MModule, name: String): nullable MModule
240 do
241 var origmmodule = mmodule
242 var modules = model.get_mmodules_by_name(name)
243
244 var tries = new Array[String]
245
246 var lastmodule = mmodule
247 while mmodule != null do
248 var dirname = mmodule.location.file.filename.dirname
249
250 # Determine the owner
251 var owner: nullable MModule
252 if dirname.basename("") != mmodule.name then
253 owner = mmodule.direct_owner
254 else
255 owner = mmodule
256 end
257
258 # First, try the already known nested modules
259 if modules != null then
260 for candidate in modules do
261 if candidate.direct_owner == owner then
262 return candidate
263 end
264 end
265 end
266
267 # Second, try the directory to find a file
268 var try_file = dirname + "/" + name + ".nit"
269 tries.add try_file
270 if try_file.file_exists then
271 var res = self.load_module(owner, try_file.simplify_path)
272 if res == null then return null # Forward error
273 return res.mmodule.as(not null)
274 end
275
276 # Third, try if the requested module is itself an owner
277 try_file = dirname + "/" + name + "/" + name + ".nit"
278 if try_file.file_exists then
279 var res = self.load_module(owner, try_file.simplify_path)
280 if res == null then return null # Forward error
281 return res.mmodule.as(not null)
282 end
283
284 lastmodule = mmodule
285 mmodule = mmodule.direct_owner
286 end
287
288 if modules != null then
289 for candidate in modules do
290 if candidate.direct_owner == null then
291 return candidate
292 end
293 end
294 end
295
296 # Look at some known directories
297 var lookpaths = self.paths
298
299 # Look in the directory of the last module also (event if not in the path)
300 if lastmodule != null then
301 var dirname = lastmodule.location.file.filename.dirname
302 if dirname.basename("") == lastmodule.name then
303 dirname = dirname.dirname
304 end
305 if not lookpaths.has(dirname) then
306 lookpaths = lookpaths.to_a
307 lookpaths.add(dirname)
308 end
309 end
310
311 var candidate: nullable String = null
312 for dirname in lookpaths do
313 var try_file = (dirname + "/" + name + ".nit").simplify_path
314 tries.add try_file
315 if try_file.file_exists then
316 if candidate == null then
317 candidate = try_file
318 else if candidate != try_file then
319 error(anode, "Error: conflicting module file for {name}: {candidate} {try_file}")
320 end
321 end
322 try_file = (dirname + "/" + name + "/" + name + ".nit").simplify_path
323 if try_file.file_exists then
324 if candidate == null then
325 candidate = try_file
326 else if candidate != try_file then
327 error(anode, "Error: conflicting module file for {name}: {candidate} {try_file}")
328 end
329 end
330 end
331 if candidate == null then
332 if origmmodule != null then
333 error(anode, "Error: cannot find module {name} from {origmmodule}. tried {tries.join(", ")}")
334 else
335 error(anode, "Error: cannot find module {name}. tried {tries.join(", ")}")
336 end
337 return null
338 end
339 var res = self.load_module(mmodule, candidate)
340 if res == null then return null # Forward error
341 return res.mmodule.as(not null)
342 end
343
344 # Try to load a module using a path.
345 # Display an error if there is a problem (IO / lexer / parser) and return null
346 # Note: usually, you do not need this method, use `get_mmodule_by_name` instead.
347 fun load_module(owner: nullable MModule, filename: String): nullable AModule
348 do
349 if not filename.file_exists then
350 self.toolcontext.error(null, "Error: file {filename} not found.")
351 return null
352 end
353
354 var x = if owner != null then owner.to_s else "."
355 self.toolcontext.info("load module {filename} in {x}", 2)
356
357 # Load the file
358 var file = new IFStream.open(filename)
359 var lexer = new Lexer(new SourceFile(filename, file))
360 var parser = new Parser(lexer)
361 var tree = parser.parse
362 file.close
363
364 # Handle lexer and parser error
365 var nmodule = tree.n_base
366 if nmodule == null then
367 var neof = tree.n_eof
368 assert neof isa AError
369 error(neof, neof.message)
370 return null
371 end
372
373 # Check the module name
374 var mod_name = filename.basename(".nit")
375 var decl = nmodule.n_moduledecl
376 if decl == null then
377 #warning(nmodule, "Warning: Missing 'module' keyword") #FIXME: NOT YET FOR COMPATIBILITY
378 else
379 var decl_name = decl.n_name.n_id.text
380 if decl_name != mod_name then
381 error(decl.n_name, "Error: module name missmatch; declared {decl_name} file named {mod_name}")
382 end
383 end
384
385 # Create the module
386 var mmodule = new MModule(model, owner, mod_name, nmodule.location)
387 nmodule.mmodule = mmodule
388 nmodules.add(nmodule)
389 self.mmodule2nmodule[mmodule] = nmodule
390
391 build_module_importation(nmodule)
392
393 return nmodule
394 end
395
396 # Analysis the module importation and fill the module_importation_hierarchy
397 private fun build_module_importation(nmodule: AModule)
398 do
399 if nmodule.is_importation_done then return
400 nmodule.is_importation_done = true
401 var mmodule = nmodule.mmodule.as(not null)
402 var stdimport = true
403 var imported_modules = new Array[MModule]
404 for aimport in nmodule.n_imports do
405 stdimport = false
406 if not aimport isa AStdImport then
407 continue
408 end
409 var mod_name = aimport.n_name.n_id.text
410 var sup = self.get_mmodule_by_name(aimport.n_name, mmodule, mod_name)
411 if sup == null then continue # Skip error
412 imported_modules.add(sup)
413 var mvisibility = aimport.n_visibility.mvisibility
414 mmodule.set_visibility_for(sup, mvisibility)
415 end
416 if stdimport then
417 var mod_name = "standard"
418 var sup = self.get_mmodule_by_name(nmodule, null, mod_name)
419 if sup != null then # Skip error
420 imported_modules.add(sup)
421 mmodule.set_visibility_for(sup, public_visibility)
422 end
423 end
424 self.toolcontext.info("{mmodule} imports {imported_modules.join(", ")}", 3)
425 mmodule.set_imported_mmodules(imported_modules)
426 end
427
428 # All the loaded modules
429 var nmodules: Array[AModule] = new Array[AModule]
430
431 # Register the nmodule associated to each mmodule
432 # FIXME: why not refine the MModule class with a nullable attribute?
433 var mmodule2nmodule: HashMap[MModule, AModule] = new HashMap[MModule, AModule]
434
435 # Helper function to display an error on a node.
436 # Alias for `self.toolcontext.error(n.hot_location, text)'
437 fun error(n: ANode, text: String)
438 do
439 self.toolcontext.error(n.hot_location, text)
440 end
441
442 # Helper function to display a warning on a node.
443 # Alias for: `self.toolcontext.warning(n.hot_location, text)'
444 fun warning(n: ANode, text: String)
445 do
446 self.toolcontext.warning(n.hot_location, text)
447 end
448
449 # Force to get the primitive method named `name' on the type `recv' or do a fatal error on `n'
450 fun force_get_primitive_method(n: ANode, name: String, recv: MType, mmodule: MModule): MMethod
451 do
452 var res = mmodule.try_get_primitive_method(name, recv)
453 if res == null then
454 self.toolcontext.fatal_error(n.hot_location, "Fatal Error: {recv} must have a property named {name}.")
455 abort
456 end
457 return res
458 end
459 end
460
461 redef class AModule
462 # The associated MModule once build by a `ModelBuilder'
463 var mmodule: nullable MModule
464 # Flag that indicate if the importation is already completed
465 var is_importation_done: Bool = false
466 end
467
468 redef class AVisibility
469 # The visibility level associated with the AST node class
470 fun mvisibility: MVisibility is abstract
471 end
472 redef class AIntrudeVisibility
473 redef fun mvisibility do return intrude_visibility
474 end
475 redef class APublicVisibility
476 redef fun mvisibility do return public_visibility
477 end
478 redef class AProtectedVisibility
479 redef fun mvisibility do return protected_visibility
480 end
481 redef class APrivateVisibility
482 redef fun mvisibility do return private_visibility
483 end