update NOTICE and LICENSE
[nit.git] / src / mmloader.nit
index 2660eca..9d28ff2 100644 (file)
@@ -2,6 +2,7 @@
 #
 # Copyright 2006-2008 Floréal Morandat <morandat@lirmm.fr>
 # Copyright 2008 Jean Privat <jean@pryen.org>
+# Copyright 2009 Jean-Sebastien Gelinas <calestar@gmail.com>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This package is used to load a metamodel 
+# This module is used to load a metamodel
 package mmloader
 
 import metamodel
 import opts
+import location
+
+class Message
+       super Comparable
+       redef type OTHER: Message
+
+       readable var _location: nullable Location
+       readable var _text: String
+
+       redef fun <(other: OTHER): Bool do
+               if location == null then return true
+               if other.location == null then return false
+
+               return location.as(not null) < other.location.as(not null)
+       end
+
+       redef fun to_s: String
+       do
+               var l = location
+               if l == null then
+                       return text
+               else
+                       return "{l}: {text}"
+               end
+       end
+
+       fun to_color_string: String
+       do
+               var esc = 27.ascii
+               var red = "{esc}[0;31m"
+               var bred = "{esc}[1;31m"
+               var green = "{esc}[0;32m"
+               var yellow = "{esc}[0;33m"
+               var def = "{esc}[0m"
+
+               var l = location
+               if l == null then
+                       return text
+               else if l.file == null then
+                       return "{yellow}{l}{def}: {text}"
+               else
+                       var i = location.line_start
+                       var line_start = l.file.line_starts[i-1]
+                       var line_end = line_start
+                       var string = l.file.string
+                       while line_end+1 < string.length and string[line_end+1] != '\n' and string[line_end+1] != '\r' do
+                               line_end += 1
+                       end
+                       var lstart = string.substring(line_start, location.column_start - 1)
+                       var cend
+                       if i != location.line_end then
+                               cend = line_end - line_start + 1
+                       else
+                               cend = location.column_end
+                       end
+                       var lmid = string.substring(line_start + location.column_start - 1, cend - location.column_start + 1)
+                       var lend = string.substring(line_start + cend, line_end - line_start - cend + 1)
+                       var indent = new Buffer
+                       for j in [line_start..line_start+location.column_start-1[ do
+                               if string[j] == '\t' then
+                                       indent.add '\t'
+                               else
+                                       indent.add ' '
+                               end
+                       end
+                       return "{yellow}{l}{def}: {text}\n\t{lstart}{bred}{lmid}{def}{lend}\n\t{indent}^"
+               end
+       end
+end
 
 # Global context for tools
 class ToolContext
-special MMContext
+       super MMContext
        # Number of errors
-       readable attr _error_count: Int = 0 
+       readable var _error_count: Int = 0
 
        # Number of warnings
-       readable attr _warning_count: Int = 0
+       readable var _warning_count: Int = 0
+
+       # Directory where to generate log files
+       readable var _log_directory: String = "logs"
+
+       # Messages
+       var _messages: Array[Message] = new Array[Message]
+       var _message_sorter: ComparableSorter[Message] = new ComparableSorter[Message]
+
+       fun check_errors
+       do
+               if _messages.length > 0 then
+                       _message_sorter.sort(_messages)
+
+                       for m in _messages do
+                               if opt_no_color.value then
+                                       stderr.write("{m}\n")
+                               else
+                                       stderr.write("{m.to_color_string}\n")
+                               end
+                       end
+
+                       _messages.clear
+               end
+
+               if error_count > 0 then exit(1)
+       end
 
        # Display an error
-       meth error(s: String)
+       fun error(l: nullable Location, s: String)
        do
-               stderr.write("{s}\n")
+               _messages.add(new Message(l,s))
                _error_count = _error_count + 1
+               if opt_stop_on_first_error.value then check_errors
+       end
+
+       # Add an error, show errors and quit
+       fun fatal_error(l: nullable Location, s: String)
+       do
+               error(l,s)
+               check_errors
        end
 
        # Display a warning
-       meth warning(s: String)
+       fun warning(l: nullable Location, s: String)
        do
                if _opt_warn.value == 0 then return
-               stderr.write("{s}\n")
+               _messages.add(new Message(l,s))
                if _opt_warn.value == 1 then
                        _warning_count = _warning_count + 1
                else
                        _error_count = _error_count + 1
                end
+               if opt_stop_on_first_error.value then check_errors
+       end
+
+       # Display an info
+       fun info(s: String, level: Int)
+       do
+               if level <= verbose_level then
+                       print "{s}"
+               end
        end
 
        # Paths where to locate modules files
-       readable attr _paths: Array[String]
+       readable var _paths: Array[String] = new Array[String]
 
        # List of module loaders
-       attr _loaders: Array[ModuleLoader] = new Array[ModuleLoader]
+       var _loaders: Array[ModuleLoader] = new Array[ModuleLoader]
        
        # Global OptionContext
-       readable attr _option_context: OptionContext = new OptionContext
+       readable var _option_context: OptionContext = new OptionContext
 
        # Option --warn
-       readable attr _opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
+       readable var _opt_warn: OptionCount = new OptionCount("Show warnings", "-W", "--warn")
 
        # Option --path
-       readable attr _opt_path: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
+       readable var _opt_path: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
+
+       # Option --log
+       readable var _opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
+
+       # Option --log-dir
+       readable var _opt_log_dir: OptionString = new OptionString("Directory where to generate log files", "--log-dir")
 
-       # Option --lop
-       readable attr _opt_log: OptionBool = new OptionBool("Generate various log files", "--log")
-       
        # Option --only-metamodel
-       readable attr _opt_only_metamodel: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
+       readable var _opt_only_metamodel: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
 
        # Option --only-parse
-       readable attr _opt_only_parse: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
+       readable var _opt_only_parse: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
 
        # Option --help
-       readable attr _opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
+       readable var _opt_help: OptionBool = new OptionBool("Show Help (This screen)", "-h", "-?", "--help")
+
+       # Option --version
+       readable var _opt_version: OptionBool = new OptionBool("Show version and exit", "--version")
+
+       # Option --verbose
+       readable var _opt_verbose: OptionCount = new OptionCount("Verbose", "-v", "--verbose")
+
+       # Option --stop-on-first-error
+       readable var _opt_stop_on_first_error: OptionBool = new OptionBool("Stop on first error", "--stop-on-first-error")
+
+       # Option --no-color
+       readable var _opt_no_color: OptionBool = new OptionBool("Do not use color to display errors and warnings", "--no-color")
+
+       # Verbose level
+       readable var _verbose_level: Int = 0
 
        init
        do
                super
-               option_context.add_option(opt_warn, opt_path, opt_log, opt_only_parse, opt_only_metamodel, opt_help)
+               option_context.add_option(opt_warn, 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)
        end
 
        # Parse and process the options given on the command line
-       meth process_options
+       fun process_options
        do
                # init options
                option_context.parse(args)
 
+               # Set verbose level
+               _verbose_level = opt_verbose.value
+
                # Setup the paths value
-               _paths = new Array[String]
                paths.append(opt_path.value)
 
                var path_env = once ("NIT_PATH".to_symbol).environ
@@ -105,12 +238,18 @@ special MMContext
 
                var libname = "{sys.program_name.dirname}/../lib"
                if libname.file_exists then paths.add(libname)
+
+               if opt_log_dir.value != null then _log_directory = opt_log_dir.value.as(not null)
+               if _opt_log.value then
+                       # Make sure the output directory exists
+                       log_directory.mkdir
+               end
        end
 
        # Load and process a module in a directory (or a parent directory).
        # If the module is already loaded, just return it without further processing.
        # If no module is found, just return null without complaining.
-       private meth try_to_load(module_name: Symbol, dir: MMDirectory): MMModule
+       private fun try_to_load(module_name: Symbol, dir: MMDirectory): nullable MMModule
        do
                # Look in the module directory
                for m in dir.modules do
@@ -134,9 +273,7 @@ special MMContext
                                var full_name = dir.full_name_for(module_name)
                                if _processing_modules.has(full_name) then
                                        # FIXME: Generate better error
-                                       error("Error: Dependency loop for module {full_name}")
-                                       exit(1)
-                                       abort
+                                       fatal_error(null, "Error: Dependency loop for module {full_name}")
                                end
                                _processing_modules.add(full_name)
                                var m = l.load_and_process_module(self, module_name, dir)
@@ -151,12 +288,12 @@ special MMContext
 
        # List of module currently processed.
        # Used to prevent dependence loops.
-       attr _processing_modules: HashSet[Symbol] = new HashSet[Symbol]
+       var _processing_modules: HashSet[Symbol] = new HashSet[Symbol]
 
        # Locate, load and analysis a module (and its supermodules) from its file name.
        # If the module is already loaded, just return it without further processing.
        # Beware, the files are automatically considered root of their directory.
-       meth get_module_from_filename(filename: String): MMModule
+       fun get_module_from_filename(filename: String): MMModule
        do
                var path = filename.dirname
                var module_name = filename.basename(".nit").to_symbol
@@ -174,8 +311,7 @@ special MMContext
                end
 
                if not filename.file_exists then
-                       error("Error: File {filename} not found.")
-                       exit(1)
+                       fatal_error(null, "Error: File {filename} not found.")
                        abort
                end
 
@@ -183,18 +319,16 @@ special MMContext
                var m = try_to_load(module_name, dir)
                if m != null then return m
 
-               error("Error: {filename} is not a NIT source module.")
-               exit(1)
+               fatal_error(null, "Error: {filename} is not a NIT source module.")
                abort
        end
 
        # Locate, load and analysis a module (and its supermodules).
        # If the module is already loaded, just return it without further processing.
-       meth get_module(module_name: Symbol, from: MMModule): MMModule
+       fun get_module(module_name: Symbol, from: nullable MMModule): MMModule
        do
-               var m: MMModule
                if from != null then
-                       var dir = from.directory
+                       var dir: nullable MMDirectory = from.directory
                        while dir != null do
                                var m = try_to_load(module_name, dir)
                                if m != null then return m
@@ -207,13 +341,12 @@ special MMContext
                        if m != null then return m
                end
                # FIXME: Generate better error
-               error("Error: No ressource found for module {module_name}.")
-               exit(1)
+               fatal_error(null, "Error: No ressource found for module {module_name}.")
                abort
        end
 
        # Return the module directory associated with a given path
-       private meth directory_for(path: String): MMDirectory
+       private fun directory_for(path: String): MMDirectory
        do
                if _path_dirs.has_key(path) then return _path_dirs[path]
                var dir = new MMDirectory(path.to_symbol, path, null) 
@@ -222,10 +355,10 @@ special MMContext
        end
 
        # Association bwtween plain path and module directories
-       attr _path_dirs: Map[String, MMDirectory] = new HashMap[String, MMDirectory]
+       var _path_dirs: Map[String, MMDirectory] = new HashMap[String, MMDirectory]
 
        # Register a new module loader
-       meth register_loader(ml: ModuleLoader) do _loaders.add(ml)
+       fun register_loader(ml: ModuleLoader) do _loaders.add(ml)
 end
 
 # A load handler know how to load a specific module type
@@ -234,10 +367,10 @@ class ModuleLoader
        type MODULE: MMModule
 
        # Extension that the loadhandler accepts
-       meth file_type: String is abstract
+       fun file_type: String is abstract
 
        # Try to load a new module directory
-       meth try_to_load_dir(dirname: Symbol, parent_dir: MMDirectory): MMDirectory
+       fun try_to_load_dir(dirname: Symbol, parent_dir: MMDirectory): nullable MMDirectory
        do
                var fname = "{parent_dir.path}/{dirname}/"
                if not fname.file_exists then return null
@@ -248,7 +381,7 @@ class ModuleLoader
 
        # Can the loadhandler load a given module?
        # Return the file found
-       meth can_handle(module_name: Symbol, dir: MMDirectory): Bool
+       fun can_handle(module_name: Symbol, dir: MMDirectory): Bool
        do
                var fname = "{dir.path}/{module_name}.{file_type}"
                if fname.file_exists then return true
@@ -257,7 +390,7 @@ class ModuleLoader
 
        # Load the module and process it
        # filename is the result of can_handle
-       meth load_and_process_module(context: ToolContext, module_name: Symbol, dir: MMDirectory): MODULE
+       fun load_and_process_module(context: ToolContext, module_name: Symbol, dir: MMDirectory): MODULE
        do
                var filename = "{dir.path}/{module_name}.{file_type}"
                var m = load_module(context, module_name, dir, filename)
@@ -266,7 +399,7 @@ class ModuleLoader
        end
 
        # Load an parse the module
-       private meth load_module(context: ToolContext, module_name: Symbol, dir: MMDirectory, filename: String): MODULE
+       private fun load_module(context: ToolContext, module_name: Symbol, dir: MMDirectory, filename: String): MODULE
        do
                var file: IFStream
                if filename == "-" then
@@ -276,9 +409,7 @@ class ModuleLoader
                end
 
                if file.eof then
-                       context.error("Error: Problem in opening file {filename}")
-                       exit(1)
-                       abort
+                       context.fatal_error(null, "Error: Problem in opening file {filename}")
                end
                var m = parse_file(context, file, filename, module_name, dir)
                if file != stdin then file.close
@@ -286,23 +417,8 @@ class ModuleLoader
        end
 
        # Parse the file to load a module
-       protected meth parse_file(context: ToolContext, file: IFStream, filename: String, module_name: Symbol, dir: MMDirectory): MODULE is abstract
+       protected fun parse_file(context: ToolContext, file: IFStream, filename: String, module_name: Symbol, dir: MMDirectory): MODULE is abstract
 
        # Process a parsed module
-       protected meth process_metamodel(context: ToolContext, module: MODULE) is abstract
-end
-
-redef class MMModule
-       # Recurcivelty process an import modules
-       meth import_supers_modules(names: Collection[Symbol])
-       do
-               var c = context
-               assert c isa ToolContext
-               var supers = new Array[MMModule]
-               for n in names do
-                       var m = c.get_module(n, self)
-                       supers.add(m)
-               end
-               c.add_module(self,supers)
-       end
+       protected fun process_metamodel(context: ToolContext, mod: MODULE) is abstract
 end