ni_nitdoc: added fast copy past utility to signatures.
[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 module is used to load a metamodel
20 package mmloader
21
22 import metamodel
23 import opts
24 import toolcontext
25
26 redef class ToolContext
27 super MMContext
28
29 # Paths where to locate modules files
30 readable var _paths: Array[String] = new Array[String]
31
32 # List of module loaders
33 var _loaders: Array[ModuleLoader] = new Array[ModuleLoader]
34
35 # Option --path
36 readable var _opt_path: OptionArray = new OptionArray("Set include path for loaders (may be used more than once)", "-I", "--path")
37
38 # Option --only-metamodel
39 readable var _opt_only_metamodel: OptionBool = new OptionBool("Stop after meta-model processing", "--only-metamodel")
40
41 # Option --only-parse
42 readable var _opt_only_parse: OptionBool = new OptionBool("Only proceed to parse step of loaders", "--only-parse")
43
44 redef init
45 do
46 super
47 option_context.add_option(opt_path, opt_only_parse, opt_only_metamodel)
48 end
49
50 # Parse and process the options given on the command line
51 redef fun process_options
52 do
53 super
54
55 # Setup the paths value
56 paths.append(opt_path.value)
57
58 var path_env = "NIT_PATH".environ
59 if not path_env.is_empty then
60 paths.append(path_env.split_with(':'))
61 end
62
63 path_env = "NIT_DIR".environ
64 if not path_env.is_empty then
65 var libname = "{path_env}/lib"
66 if libname.file_exists then paths.add(libname)
67 end
68
69 var libname = "{sys.program_name.dirname}/../lib"
70 if libname.file_exists then paths.add(libname.simplify_path)
71 end
72
73 # Load and process a module in a directory (or a parent directory).
74 # If the module is already loaded, just return it without further processing.
75 # If no module is found, just return null without complaining.
76 private fun try_to_load(module_name: Symbol, dir: MMDirectory): nullable MMModule
77 do
78 # Look in the module directory
79 for m in dir.modules.values do
80 if m.name == module_name then return m
81 end
82
83 # print "try to load {module_name} in {dir.name} {_loaders.length}"
84
85 for l in _loaders do
86 var dir2 = l.try_to_load_dir(module_name, dir)
87 if dir2 != null then
88 var m = try_to_load(module_name, dir2)
89 if m != null then
90 dir2.owner = m
91 dir.add_module(m)
92 return m
93 end
94 end
95
96 if l.can_handle(module_name, dir) then
97 var full_name = dir.full_name_for(module_name)
98 if _processing_modules.has(full_name) then
99 # FIXME: Generate better error
100 fatal_error(null, "Error: Dependency loop for module {full_name}")
101 end
102 _processing_modules.add(full_name)
103 var m = l.load_and_process_module(self, module_name, dir)
104 _processing_modules.remove(full_name)
105 #if m != null then print "loaded {m.name} in {m.directory.name} -> {m.full_name} ({m.full_name.object_id.to_hex})"
106 dir.add_module(m)
107 return m
108 end
109 end
110 return null
111 end
112
113 # List of module currently processed.
114 # Used to prevent dependence loops.
115 var _processing_modules: HashSet[Symbol] = new HashSet[Symbol]
116
117 # Locate, load and analysis a module (and its supermodules) from its file name.
118 # If the module is already loaded, just return it without further processing.
119 # Beware, the files are automatically considered root of their directory.
120 fun get_module_from_filename(filename: String): MMModule
121 do
122 var path = filename.dirname
123 var module_name = filename.basename(".nit").to_symbol
124
125 var dir = directory_for(path)
126
127 if module_name.to_s == filename then
128 # It's just a modulename
129 # look for it in the path directory "."
130 var m = try_to_load(module_name, dir)
131 if m != null then return m
132
133 # Else look for it in the path
134 return get_module(module_name, null)
135 end
136
137 if not filename.file_exists then
138 fatal_error(null, "Error: File {filename} not found.")
139 abort
140 end
141
142 # Try to load the module where mentionned
143 var m = try_to_load(module_name, dir)
144 if m != null then return m
145
146 fatal_error(null, "Error: {filename} is not a NIT source module.")
147 abort
148 end
149
150 # Locate, load and analysis a module (and its supermodules).
151 # If the module is already loaded, just return it without further processing.
152 fun get_module(module_name: Symbol, from: nullable MMModule): MMModule
153 do
154 if from != null then
155 var dir: nullable MMDirectory = from.directory
156 while dir != null do
157 var m = try_to_load(module_name, dir)
158 if m != null then return m
159 dir = dir.parent
160 end
161 end
162
163 for p in paths do
164 var m = try_to_load(module_name, directory_for(p))
165 if m != null then return m
166 end
167 # FIXME: Generate better error
168 fatal_error(null, "Error: No ressource found for module {module_name}.")
169 abort
170 end
171
172 # Return the module directory associated with a given path
173 private fun directory_for(path: String): MMDirectory
174 do
175 if _path_dirs.has_key(path) then return _path_dirs[path]
176 var dir = new MMDirectory(path.to_symbol, path, null)
177 _path_dirs[path] = dir
178 return dir
179 end
180
181 # Association bwtween plain path and module directories
182 var _path_dirs: Map[String, MMDirectory] = new HashMap[String, MMDirectory]
183
184 # Register a new module loader
185 fun register_loader(ml: ModuleLoader) do _loaders.add(ml)
186 end
187
188 # A load handler know how to load a specific module type
189 interface ModuleLoader
190 # Type of module loaded by the loader
191 type MODULE: MMModule
192
193 # Extension that the loadhandler accepts
194 fun file_type: String is abstract
195
196 # Try to load a new module directory
197 fun try_to_load_dir(dirname: Symbol, parent_dir: MMDirectory): nullable MMDirectory
198 do
199 var fname = "{parent_dir.path}/{dirname}"
200 if not fname.file_exists then return null
201
202 var dir = new MMDirectory(parent_dir.full_name_for(dirname), fname, parent_dir)
203 return dir
204 end
205
206 # Can the loadhandler load a given module?
207 # Return the file found
208 fun can_handle(module_name: Symbol, dir: MMDirectory): Bool
209 do
210 var fname = "{dir.path}/{module_name}.{file_type}"
211 if fname.file_exists then return true
212 return false
213 end
214
215 # Load the module and process it
216 # filename is the result of can_handle
217 fun load_and_process_module(context: ToolContext, module_name: Symbol, dir: MMDirectory): MODULE
218 do
219 var filename = "{dir.path}/{module_name}.{file_type}".simplify_path
220 var m = load_module(context, module_name, dir, filename)
221 if not context.opt_only_parse.value then process_metamodel(context, m)
222 return m
223 end
224
225 # Load an parse the module
226 private fun load_module(context: ToolContext, module_name: Symbol, dir: MMDirectory, filename: String): MODULE
227 do
228 var file: IFStream
229 if filename == "-" then
230 file = stdin
231 else
232 file = new IFStream.open(filename.to_s)
233 end
234
235 if file.eof then
236 context.fatal_error(null, "Error: Problem in opening file {filename}")
237 end
238 var m = parse_file(context, file, filename, module_name, dir)
239 if file != stdin then file.close
240 return m
241 end
242
243 # Parse the file to load a module
244 protected fun parse_file(context: ToolContext, file: IFStream, filename: String, module_name: Symbol, dir: MMDirectory): MODULE is abstract
245
246 # Process a parsed module
247 protected fun process_metamodel(context: ToolContext, mod: MODULE) is abstract
248 end