tools: cleanup after insertion of 'Location' class
[nit.git] / src / mmloader.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2006-2008 Floréal Morandat <morandat@lirmm.fr>
4 # Copyright 2008 Jean Privat <jean@pryen.org>
5 # Copyright 2009 Jean-Sebastien Gelinas <calestar@gmail.com>
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18
19 # This package is used to load a metamodel
20 package mmloader
21
22 import metamodel
23 import opts
24
25 class Location
26 readable var _file: String
27 readable var _line_start: Int
28 readable var _line_end: Int
29 readable var _column_start: Int
30 readable var _column_end: Int
31
32 redef meth to_s: String do
33 if line_start == line_end then
34 if column_start == column_end then
35 return "{file}:{line_start},{column_start}"
36 else
37 return "{file}:{line_start},{column_start}--{column_end}"
38 end
39 else
40 return "{file}:{line_start},{column_start}--{line_end}:{column_end}"
41 end
42 end
43 end
44
45 # Global context for tools
46 class ToolContext
47 special MMContext
48 # Number of errors
49 readable var _error_count: Int = 0
50
51 # Number of warnings
52 readable var _warning_count: Int = 0
53
54 # Display an error
55 fun error(s: String)
56 do
57 stderr.write("{s}\n")
58 _error_count = _error_count + 1
59 end
60
61 # Display a warning
62 fun warning(s: String)
63 do
64 if _opt_warn.value == 0 then return
65 stderr.write("{s}\n")
66 if _opt_warn.value == 1 then
67 _warning_count = _warning_count + 1
68 else
69 _error_count = _error_count + 1
70 end
71 end
72
73 # Display an info
74 meth info(s: String, level: Int)
75 do
76 if level <= verbose_level then
77 print "{s}"
78 end
79 end
80
81 # Paths where to locate modules files
82 readable var _paths: Array[String] = new Array[String]
83
84 # List of module loaders
85 var _loaders: Array[ModuleLoader] = new Array[ModuleLoader]
86
87 # Global OptionContext
88 readable var _option_context: OptionContext = new OptionContext
89
90 # Option --warn
91 readable var _opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
92
93 # Option --path
94 readable var _opt_path: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
95
96 # Option --lop
97 readable var _opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
98
99 # Option --only-metamodel
100 readable var _opt_only_metamodel: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
101
102 # Option --only-parse
103 readable var _opt_only_parse: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
104
105 # Option --help
106 readable var _opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
107
108 # Option --version
109 readable var _opt_version: OptionBool = new OptionBool("Show version and exit", "--version")
110
111 # Option --verbose
112 readable var _opt_verbose: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
113
114 # Verbose level
115 readable var _verbose_level: Int = 0
116
117 init
118 do
119 super
120 option_context.add_option(opt_warn, opt_path, opt_log, opt_only_parse, opt_only_metamodel, opt_help, opt_version, opt_verbose)
121 end
122
123 # Parse and process the options given on the command line
124 fun process_options
125 do
126 # init options
127 option_context.parse(args)
128
129 # Set verbose level
130 _verbose_level = opt_verbose.value
131
132 # Setup the paths value
133 paths.append(opt_path.value)
134
135 var path_env = once ("NIT_PATH".to_symbol).environ
136 if not path_env.is_empty then
137 paths.append(path_env.split_with(':'))
138 end
139
140 path_env = once ("NIT_DIR".to_symbol).environ
141 if not path_env.is_empty then
142 var libname = "{path_env}/lib"
143 if libname.file_exists then paths.add(libname)
144 end
145
146 var libname = "{sys.program_name.dirname}/../lib"
147 if libname.file_exists then paths.add(libname)
148 end
149
150 # Load and process a module in a directory (or a parent directory).
151 # If the module is already loaded, just return it without further processing.
152 # If no module is found, just return null without complaining.
153 private fun try_to_load(module_name: Symbol, dir: MMDirectory): nullable MMModule
154 do
155 # Look in the module directory
156 for m in dir.modules do
157 if m.name == module_name then return m
158 end
159
160 # print "try to load {module_name} in {dir.name} {_loaders.length}"
161
162 for l in _loaders do
163 var dir2 = l.try_to_load_dir(module_name, dir)
164 if dir2 != null then
165 var m = try_to_load(module_name, dir2)
166 if m != null then
167 dir2.owner = m
168 dir.add_module(m)
169 return m
170 end
171 end
172
173 if l.can_handle(module_name, dir) then
174 var full_name = dir.full_name_for(module_name)
175 if _processing_modules.has(full_name) then
176 # FIXME: Generate better error
177 error("Error: Dependency loop for module {full_name}")
178 exit(1)
179 abort
180 end
181 _processing_modules.add(full_name)
182 var m = l.load_and_process_module(self, module_name, dir)
183 _processing_modules.remove(full_name)
184 #if m != null then print "loaded {m.name} in {m.directory.name} -> {m.full_name} ({m.full_name.object_id.to_hex})"
185 dir.add_module(m)
186 return m
187 end
188 end
189 return null
190 end
191
192 # List of module currently processed.
193 # Used to prevent dependence loops.
194 var _processing_modules: HashSet[Symbol] = new HashSet[Symbol]
195
196 # Locate, load and analysis a module (and its supermodules) from its file name.
197 # If the module is already loaded, just return it without further processing.
198 # Beware, the files are automatically considered root of their directory.
199 fun get_module_from_filename(filename: String): MMModule
200 do
201 var path = filename.dirname
202 var module_name = filename.basename(".nit").to_symbol
203
204 var dir = directory_for(path)
205
206 if module_name.to_s == filename then
207 # It's just a modulename
208 # look for it in the path directory "."
209 var m = try_to_load(module_name, dir)
210 if m != null then return m
211
212 # Else look for it in the path
213 return get_module(module_name, null)
214 end
215
216 if not filename.file_exists then
217 error("Error: File {filename} not found.")
218 exit(1)
219 abort
220 end
221
222 # Try to load the module where mentionned
223 var m = try_to_load(module_name, dir)
224 if m != null then return m
225
226 error("Error: {filename} is not a NIT source module.")
227 exit(1)
228 abort
229 end
230
231 # Locate, load and analysis a module (and its supermodules).
232 # If the module is already loaded, just return it without further processing.
233 fun get_module(module_name: Symbol, from: nullable MMModule): MMModule
234 do
235 var m: MMModule
236 if from != null then
237 var dir: nullable MMDirectory = from.directory
238 while dir != null do
239 var m = try_to_load(module_name, dir)
240 if m != null then return m
241 dir = dir.parent
242 end
243 end
244
245 for p in paths do
246 var m = try_to_load(module_name, directory_for(p))
247 if m != null then return m
248 end
249 # FIXME: Generate better error
250 error("Error: No ressource found for module {module_name}.")
251 exit(1)
252 abort
253 end
254
255 # Return the module directory associated with a given path
256 private fun directory_for(path: String): MMDirectory
257 do
258 if _path_dirs.has_key(path) then return _path_dirs[path]
259 var dir = new MMDirectory(path.to_symbol, path, null)
260 _path_dirs[path] = dir
261 return dir
262 end
263
264 # Association bwtween plain path and module directories
265 var _path_dirs: Map[String, MMDirectory] = new HashMap[String, MMDirectory]
266
267 # Register a new module loader
268 fun register_loader(ml: ModuleLoader) do _loaders.add(ml)
269 end
270
271 # A load handler know how to load a specific module type
272 class ModuleLoader
273 # Type of module loaded by the loader
274 type MODULE: MMModule
275
276 # Extension that the loadhandler accepts
277 fun file_type: String is abstract
278
279 # Try to load a new module directory
280 fun try_to_load_dir(dirname: Symbol, parent_dir: MMDirectory): nullable MMDirectory
281 do
282 var fname = "{parent_dir.path}/{dirname}/"
283 if not fname.file_exists then return null
284
285 var dir = new MMDirectory(parent_dir.full_name_for(dirname), fname, parent_dir)
286 return dir
287 end
288
289 # Can the loadhandler load a given module?
290 # Return the file found
291 fun can_handle(module_name: Symbol, dir: MMDirectory): Bool
292 do
293 var fname = "{dir.path}/{module_name}.{file_type}"
294 if fname.file_exists then return true
295 return false
296 end
297
298 # Load the module and process it
299 # filename is the result of can_handle
300 fun load_and_process_module(context: ToolContext, module_name: Symbol, dir: MMDirectory): MODULE
301 do
302 var filename = "{dir.path}/{module_name}.{file_type}"
303 var m = load_module(context, module_name, dir, filename)
304 if not context.opt_only_parse.value then process_metamodel(context, m)
305 return m
306 end
307
308 # Load an parse the module
309 private fun load_module(context: ToolContext, module_name: Symbol, dir: MMDirectory, filename: String): MODULE
310 do
311 var file: IFStream
312 if filename == "-" then
313 file = stdin
314 else
315 file = new IFStream.open(filename.to_s)
316 end
317
318 if file.eof then
319 context.error("Error: Problem in opening file {filename}")
320 exit(1)
321 abort
322 end
323 var m = parse_file(context, file, filename, module_name, dir)
324 if file != stdin then file.close
325 return m
326 end
327
328 # Parse the file to load a module
329 protected fun parse_file(context: ToolContext, file: IFStream, filename: String, module_name: Symbol, dir: MMDirectory): MODULE is abstract
330
331 # Process a parsed module
332 protected fun process_metamodel(context: ToolContext, module: MODULE) is abstract
333 end
334
335 redef class MMModule
336 # Recurcivelty process an import modules
337 fun import_supers_modules(names: Collection[Symbol])
338 do
339 var c = context
340 assert c isa ToolContext
341 var supers = new Array[MMModule]
342 for n in names do
343 var m = c.get_module(n, self)
344 supers.add(m)
345 end
346 c.add_module(self,supers)
347 end
348 end