1 # This file is part of NIT ( http://www.nitlanguage.org ).
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>
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
11 # http://www.apache.org/licenses/LICENSE-2.0
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.
19 # This module is used to load a metamodel
28 redef type OTHER: Message
30 readable var _location
: nullable Location
31 readable var _text
: String
33 redef fun <(other
: OTHER): Bool do
34 if location
== null then return true
35 if other
.location
== null then return false
37 return location
.as(not null) < other
.location
.as(not null)
40 redef fun to_s
: String
50 fun to_color_string
: String
53 var red
= "{esc}[0;31m"
54 var bred
= "{esc}[1;31m"
55 var green
= "{esc}[0;32m"
56 var yellow
= "{esc}[0;33m"
62 else if l
.file
== null then
63 return "{yellow}{l}{def}: {text}"
65 var i
= location
.line_start
66 var line_start
= l
.file
.line_starts
[i-1
]
67 var line_end
= line_start
68 var string
= l
.file
.string
69 while line_end
+1 < string
.length
and string
[line_end
+1] != '\n' and string
[line_end
+1] != '\r' do
72 var lstart
= string
.substring
(line_start
, location
.column_start
- 1)
74 if i
!= location
.line_end
then
75 cend
= line_end
- line_start
+ 1
77 cend
= location
.column_end
81 if line_start
+ cend
<= string
.length
then
82 lmid
= string
.substring
(line_start
+ location
.column_start
- 1, cend
- location
.column_start
+ 1)
83 lend
= string
.substring
(line_start
+ cend
, line_end
- line_start
- cend
+ 1)
88 var indent
= new Buffer
89 for j
in [line_start
..line_start
+location
.column_start-1
[ do
90 if string
[j
] == '\t' then
96 return "{yellow}{l}{def}: {text}\n\t{lstart}{bred}{lmid}{def}{lend}\n\t{indent}^"
101 # Global context for tools
105 readable var _error_count
: Int = 0
108 readable var _warning_count
: Int = 0
110 # Directory where to generate log files
111 readable var _log_directory
: String = "logs"
114 var _messages
: Array[Message] = new Array[Message]
115 var _message_sorter
: ComparableSorter[Message] = new ComparableSorter[Message]
119 if _messages
.length
> 0 then
120 _message_sorter
.sort
(_messages
)
122 for m
in _messages
do
123 if opt_no_color
.value
then
124 stderr
.write
("{m}\n")
126 stderr
.write
("{m.to_color_string}\n")
133 if error_count
> 0 then exit
(1)
137 fun error
(l
: nullable Location, s
: String)
139 _messages
.add
(new Message(l
,s
))
140 _error_count
= _error_count
+ 1
141 if opt_stop_on_first_error
.value
then check_errors
144 # Add an error, show errors and quit
145 fun fatal_error
(l
: nullable Location, s
: String)
152 fun warning
(l
: nullable Location, s
: String)
154 if _opt_warn
.value
== 0 then return
155 _messages
.add
(new Message(l
,s
))
156 _warning_count
= _warning_count
+ 1
157 if opt_stop_on_first_error
.value
then check_errors
161 fun info
(s
: String, level
: Int)
163 if level
<= verbose_level
then
168 # Paths where to locate modules files
169 readable var _paths
: Array[String] = new Array[String]
171 # List of module loaders
172 var _loaders
: Array[ModuleLoader] = new Array[ModuleLoader]
174 # Global OptionContext
175 readable var _option_context
: OptionContext = new OptionContext
178 readable var _opt_warn
: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
181 readable var _opt_quiet
: OptionBool = new OptionBool("Do not show warnings", "-q", "--quiet")
184 readable var _opt_path
: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
187 readable var _opt_log
: OptionBool = new OptionBool("Generate various log files", "--log")
190 readable var _opt_log_dir
: OptionString = new OptionString("Directory where to generate log files", "--log-dir")
192 # Option --only-metamodel
193 readable var _opt_only_metamodel
: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
195 # Option --only-parse
196 readable var _opt_only_parse
: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
199 readable var _opt_help
: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
202 readable var _opt_version
: OptionBool = new OptionBool("Show version and exit", "--version")
205 readable var _opt_verbose
: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
207 # Option --stop-on-first-error
208 readable var _opt_stop_on_first_error
: OptionBool = new OptionBool("Stop on first error", "--stop-on-first-error")
211 readable var _opt_no_color
: OptionBool = new OptionBool("Do not use color to display errors and warnings", "--no-color")
214 readable var _verbose_level
: Int = 0
219 option_context
.add_option
(opt_warn
, opt_quiet
, opt_stop_on_first_error
, opt_no_color
, opt_path
, opt_log
, opt_log_dir
, opt_only_parse
, opt_only_metamodel
, opt_help
, opt_version
, opt_verbose
)
222 # Parse and process the options given on the command line
225 self.opt_warn
.value
= 1
228 option_context
.parse
(args
)
231 _verbose_level
= opt_verbose
.value
233 if self.opt_quiet
.value
then self.opt_warn
.value
= 0
235 # Setup the paths value
236 paths
.append
(opt_path
.value
)
238 var path_env
= once
("NIT_PATH".to_symbol
).environ
239 if not path_env
.is_empty
then
240 paths
.append
(path_env
.split_with
(':'))
243 path_env
= once
("NIT_DIR".to_symbol
).environ
244 if not path_env
.is_empty
then
245 var libname
= "{path_env}/lib"
246 if libname
.file_exists
then paths
.add
(libname
)
249 var libname
= "{sys.program_name.dirname}/../lib"
250 if libname
.file_exists
then paths
.add
(libname
.simplify_path
)
252 if opt_log_dir
.value
!= null then _log_directory
= opt_log_dir
.value
.as(not null)
253 if _opt_log
.value
then
254 # Make sure the output directory exists
259 # Load and process a module in a directory (or a parent directory).
260 # If the module is already loaded, just return it without further processing.
261 # If no module is found, just return null without complaining.
262 private fun try_to_load
(module_name
: Symbol, dir
: MMDirectory): nullable MMModule
264 # Look in the module directory
265 for m
in dir
.modules
.values
do
266 if m
.name
== module_name
then return m
269 # print "try to load {module_name} in {dir.name} {_loaders.length}"
272 var dir2
= l
.try_to_load_dir
(module_name
, dir
)
274 var m
= try_to_load
(module_name
, dir2
)
282 if l
.can_handle
(module_name
, dir
) then
283 var full_name
= dir
.full_name_for
(module_name
)
284 if _processing_modules
.has
(full_name
) then
285 # FIXME: Generate better error
286 fatal_error
(null, "Error: Dependency loop for module {full_name}")
288 _processing_modules
.add
(full_name
)
289 var m
= l
.load_and_process_module
(self, module_name
, dir
)
290 _processing_modules
.remove
(full_name
)
291 #if m != null then print "loaded {m.name} in {m.directory.name} -> {m.full_name} ({m.full_name.object_id.to_hex})"
299 # List of module currently processed.
300 # Used to prevent dependence loops.
301 var _processing_modules
: HashSet[Symbol] = new HashSet[Symbol]
303 # Locate, load and analysis a module (and its supermodules) from its file name.
304 # If the module is already loaded, just return it without further processing.
305 # Beware, the files are automatically considered root of their directory.
306 fun get_module_from_filename
(filename
: String): MMModule
308 var path
= filename
.dirname
309 var module_name
= filename
.basename
(".nit").to_symbol
311 var dir
= directory_for
(path
)
313 if module_name
.to_s
== filename
then
314 # It's just a modulename
315 # look for it in the path directory "."
316 var m
= try_to_load
(module_name
, dir
)
317 if m
!= null then return m
319 # Else look for it in the path
320 return get_module
(module_name
, null)
323 if not filename
.file_exists
then
324 fatal_error
(null, "Error: File {filename} not found.")
328 # Try to load the module where mentionned
329 var m
= try_to_load
(module_name
, dir
)
330 if m
!= null then return m
332 fatal_error
(null, "Error: {filename} is not a NIT source module.")
336 # Locate, load and analysis a module (and its supermodules).
337 # If the module is already loaded, just return it without further processing.
338 fun get_module
(module_name
: Symbol, from
: nullable MMModule): MMModule
341 var dir
: nullable MMDirectory = from
.directory
343 var m
= try_to_load
(module_name
, dir
)
344 if m
!= null then return m
350 var m
= try_to_load
(module_name
, directory_for
(p
))
351 if m
!= null then return m
353 # FIXME: Generate better error
354 fatal_error
(null, "Error: No ressource found for module {module_name}.")
358 # Return the module directory associated with a given path
359 private fun directory_for
(path
: String): MMDirectory
361 if _path_dirs
.has_key
(path
) then return _path_dirs
[path
]
362 var dir
= new MMDirectory(path
.to_symbol
, path
, null)
363 _path_dirs
[path
] = dir
367 # Association bwtween plain path and module directories
368 var _path_dirs
: Map[String, MMDirectory] = new HashMap[String, MMDirectory]
370 # Register a new module loader
371 fun register_loader
(ml
: ModuleLoader) do _loaders
.add
(ml
)
374 # A load handler know how to load a specific module type
376 # Type of module loaded by the loader
377 type MODULE: MMModule
379 # Extension that the loadhandler accepts
380 fun file_type
: String is abstract
382 # Try to load a new module directory
383 fun try_to_load_dir
(dirname
: Symbol, parent_dir
: MMDirectory): nullable MMDirectory
385 var fname
= "{parent_dir.path}/{dirname}"
386 if not fname
.file_exists
then return null
388 var dir
= new MMDirectory(parent_dir
.full_name_for
(dirname
), fname
, parent_dir
)
392 # Can the loadhandler load a given module?
393 # Return the file found
394 fun can_handle
(module_name
: Symbol, dir
: MMDirectory): Bool
396 var fname
= "{dir.path}/{module_name}.{file_type}"
397 if fname
.file_exists
then return true
401 # Load the module and process it
402 # filename is the result of can_handle
403 fun load_and_process_module
(context
: ToolContext, module_name
: Symbol, dir
: MMDirectory): MODULE
405 var filename
= "{dir.path}/{module_name}.{file_type}".simplify_path
406 var m
= load_module
(context
, module_name
, dir
, filename
)
407 if not context
.opt_only_parse
.value
then process_metamodel
(context
, m
)
411 # Load an parse the module
412 private fun load_module
(context
: ToolContext, module_name
: Symbol, dir
: MMDirectory, filename
: String): MODULE
415 if filename
== "-" then
418 file
= new IFStream.open
(filename
.to_s
)
422 context
.fatal_error
(null, "Error: Problem in opening file {filename}")
424 var m
= parse_file
(context
, file
, filename
, module_name
, dir
)
425 if file
!= stdin
then file
.close
429 # Parse the file to load a module
430 protected fun parse_file
(context
: ToolContext, file
: IFStream, filename
: String, module_name
: Symbol, dir
: MMDirectory): MODULE is abstract
432 # Process a parsed module
433 protected fun process_metamodel
(context
: ToolContext, mod
: MODULE) is abstract