Merge: nitc: check pkg-config packages availability later
[nit.git] / contrib / nitin / nitin.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
18
19 # The Nit interactive interpreter
20 module nitin
21
22 import nitc::interpreter
23 import nitc::frontend
24 import nitc::parser_util
25 intrude import nitc::scope
26
27 redef class ToolContext
28
29 # --no-prompt
30 var opt_no_prompt = new OptionBool("Disable writing a prompt.", "--no-prompt")
31
32 # --source-name
33 var opt_source_name = new OptionString("Set a name for the input source.", "--source-name")
34
35 redef init do
36 super
37 option_context.add_option(opt_no_prompt, opt_source_name)
38 end
39
40 # Parse a full module given as a string
41 #
42 # Return a AModule or a AError
43 fun p_module(string: String): ANode
44 do
45 var source_name = opt_source_name.value or else ""
46 string = "\n" * last_line + string
47 var source = new SourceFile.from_string(source_name, string)
48 var lexer = new Lexer(source)
49 var parser = new Parser(lexer)
50 var tree = parser.parse
51
52 var eof = tree.n_eof
53 if eof isa AError then
54 return eof
55 end
56 return tree.n_base.as(not null)
57 end
58
59 # Read an user-line with a given `prompt`
60 #
61 # Return `null` if end of file
62 fun readline(prompt: String): nullable String do
63 printn prompt
64 var res = stdin.read_line
65 if res == "" and stdin.eof then return null
66 return res
67 end
68
69 # Add `text` in the history for `readline`.
70 #
71 # With the default implementation, the history is dropped
72 fun readline_add_history(text: String) do end
73
74 # The last line number read by `i_parse`
75 var last_line = 0
76
77 # Parse the input of the user as a module
78 fun i_parse(prompt: String): nullable ANode
79 do
80 var oldtext = ""
81
82 loop
83 var s
84 if opt_no_prompt.value then
85 s = stdin.read_line
86 if s == "" and stdin.eof then s = null
87 else
88 s = readline(prompt)
89 end
90 if s == null then return null
91 if s == "" then
92 if oldtext != "" then
93 oldtext += "\n"
94 else
95 last_line += 1
96 end
97 continue
98 end
99
100 if s.chars.first == ':' then
101 var res = new TString
102 res.text = s
103 return res
104 end
105
106 var text = oldtext + s + "\n"
107 oldtext = ""
108 var n = p_module(text)
109
110 if n isa AParserError and (n.token isa EOF or n.token isa TBadTString or n.token isa TBadExtern) then
111 # Unexpected end of file, thus continuing
112 if oldtext == "" then prompt = "." * prompt.length
113 oldtext = text
114 continue
115 end
116
117 last_line = n.location.file.line_starts.length - 1
118 readline_add_history(text.chomp)
119 return n
120 end
121 end
122 end
123
124 redef class AMethPropdef
125 var injected_variables: nullable Map[Variable, Instance] = null is writable
126 var new_variables: nullable Array[Variable] = null
127
128 redef fun accept_scope_visitor(v)
129 do
130 var injected_variables = self.injected_variables
131 if injected_variables == null then
132 super
133 return
134 end
135
136 # Inject main variables in the initial scope
137 var scope = v.scopes.first
138 for variable in injected_variables.keys do
139 scope.variables[variable.name] = variable
140 end
141
142 super
143
144 # Gather new top-level variables as main variables
145 scope = v.scopes.first
146 var new_variables = new Array[Variable]
147 for variable in scope.variables.values do
148 if not injected_variables.has_key(variable) then
149 new_variables.add(variable)
150 end
151 end
152 self.new_variables = new_variables
153 end
154
155 redef fun call_commons(v, m, a, f)
156 do
157 var injected_variables = self.injected_variables
158 if injected_variables == null then return super
159
160 # Inject main variables in the frame
161 assert f isa InterpreterFrame
162 for variable, i in injected_variables do
163 f.map[variable] = i
164 end
165
166 var res = super
167
168 # Update the values of the variables
169 for variable in injected_variables.keys do
170 injected_variables[variable] = f.map[variable]
171 end
172 # Retrieve the values of the new main variables
173 for variable in new_variables.as(not null) do
174 injected_variables[variable] = f.map[variable]
175 end
176
177 return res
178 end
179 end
180
181 # Create a tool context to handle options and paths
182 var toolcontext = new ToolContext
183 toolcontext.option_context.options_before_rest = true
184 toolcontext.accept_no_arguments = true
185 toolcontext.keep_going = true
186 toolcontext.process_options(args)
187
188 # We need a model to collect stufs
189 var model = new Model
190 # An a model builder to parse files
191 var modelbuilder = new ModelBuilder(model, toolcontext)
192
193 var arguments = toolcontext.option_context.rest
194
195 # Our initial program is an empty module
196 var amodule = toolcontext.parse_module("")
197 var mmodule = modelbuilder.load_rt_module(null, amodule, "input-0")
198 modelbuilder.run_phases
199 if not toolcontext.check_errors then return
200 assert mmodule != null
201 var mmodules = [mmodule]
202 var mainmodule = toolcontext.make_main_module(mmodules)
203
204 # Start and run the interpreter on the empty module
205 var interpreter = new NaiveInterpreter(modelbuilder, mainmodule, arguments)
206 interpreter.start(mainmodule)
207
208 # Get the main object and the main method
209 var mainobj = interpreter.mainobj
210 assert mainobj != null
211 var sys_type = mainobj.mtype.as(MClassType)
212 var mainprop = mainmodule.try_get_primitive_method("main", sys_type.mclass)
213 assert mainprop != null
214
215 var main_variables = new Map[Variable, Instance]
216
217 var l = 0
218 loop
219 # Next piece of Nit code
220 var n = toolcontext.i_parse("-->")
221 if n == null then
222 break
223 end
224
225 # Special adhoc command
226 if n isa TString then
227 var s = n.text
228 if s == ":q" then
229 break
230 else
231 print "`:q` to quit"
232 end
233 continue
234 end
235
236 # An error
237 if n isa AError then
238 modelbuilder.error(n, n.message)
239 toolcontext.check_errors
240 continue
241 end
242
243 #n.dump_tree
244
245 # A syntactically module!
246 amodule = n.as(AModule)
247
248 # Try to load it as a submodule
249 l += 1
250 var newmodule = modelbuilder.load_rt_module(mainmodule, amodule, "input-{l}")
251 if newmodule == null then continue
252
253 var main_method = null
254 if amodule.n_classdefs.not_empty and amodule.n_classdefs.last isa AMainClassdef then
255 main_method = amodule.n_classdefs.last.n_propdefs.first
256 assert main_method isa AMethPropdef
257 main_method.injected_variables = main_variables
258 end
259
260 modelbuilder.run_phases
261 if not toolcontext.check_errors then
262 toolcontext.error_count = 0
263 continue
264 end
265 # Everything is fine, the module is the new main module!
266 mainmodule = newmodule
267 interpreter.mainmodule = mainmodule
268
269 # Run the main if the AST contains a main
270 if main_method != null then
271 do
272 interpreter.catch_count += 1
273 interpreter.send(mainprop, [mainobj])
274 catch
275 var e = interpreter.last_error
276 if e != null then
277 var en = e.node
278 if en != null then
279 print "{en.location}: Runtime error: {e.message}\n{en.location.colored_line("0;31")}"
280 else
281 print "Runtime error: {e.message}"
282 end
283 end
284 print interpreter.stack_trace
285 interpreter.frames.clear
286 end
287 end
288 end