Merge: Intro and use `prompt`, an alternative to `readline`
[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 prompt
23
24 import nitc::interpreter
25 import nitc::frontend
26 import nitc::parser_util
27 intrude import nitc::scope
28
29 redef class ToolContext
30
31 # --no-prompt
32 var opt_no_prompt = new OptionBool("Disable writing a prompt.", "--no-prompt")
33
34 # --source-name
35 var opt_source_name = new OptionString("Set a name for the input source.", "--source-name")
36
37 redef init do
38 super
39 option_context.add_option(opt_no_prompt, opt_source_name)
40 end
41
42 # Parse a full module given as a string
43 #
44 # Return a AModule or a AError
45 fun p_module(string: String): ANode
46 do
47 var source_name = opt_source_name.value or else ""
48 string = "\n" * last_line + string
49 var source = new SourceFile.from_string(source_name, string)
50 var lexer = new Lexer(source)
51 var parser = new Parser(lexer)
52 var tree = parser.parse
53
54 var eof = tree.n_eof
55 if eof isa AError then
56 return eof
57 end
58 return tree.n_base.as(not null)
59 end
60
61 # The last line number read by `i_parse`
62 var last_line = 0
63
64 # Parse the input of the user as a module
65 fun i_parse(prompt: String): nullable ANode
66 do
67 var oldtext = ""
68
69 loop
70 var s
71 if opt_no_prompt.value then
72 s = stdin.read_line
73 if s == "" and stdin.eof then s = null
74 else
75 s = sys.prompt(prompt)
76 end
77 if s == null then return null
78 if s == "" then
79 if oldtext != "" then
80 oldtext += "\n"
81 else
82 last_line += 1
83 end
84 continue
85 end
86
87 if s.chars.first == ':' then
88 var res = new TString
89 res.text = s
90 return res
91 end
92
93 var text = oldtext + s + "\n"
94 oldtext = ""
95 var n = p_module(text)
96
97 if n isa AParserError and (n.token isa EOF or n.token isa TBadTString or n.token isa TBadExtern) then
98 # Unexpected end of file, thus continuing
99 if oldtext == "" then prompt = "." * prompt.length
100 oldtext = text
101 continue
102 end
103
104 last_line = n.location.file.line_starts.length - 1
105 prompt_add_history(text.chomp)
106 return n
107 end
108 end
109 end
110
111 redef class AMethPropdef
112 var injected_variables: nullable Map[Variable, Instance] = null is writable
113 var new_variables: nullable Array[Variable] = null
114
115 redef fun accept_scope_visitor(v)
116 do
117 var injected_variables = self.injected_variables
118 if injected_variables == null then
119 super
120 return
121 end
122
123 # Inject main variables in the initial scope
124 var scope = v.scopes.first
125 for variable in injected_variables.keys do
126 scope.variables[variable.name] = variable
127 end
128
129 super
130
131 # Gather new top-level variables as main variables
132 scope = v.scopes.first
133 var new_variables = new Array[Variable]
134 for variable in scope.variables.values do
135 if not injected_variables.has_key(variable) then
136 new_variables.add(variable)
137 end
138 end
139 self.new_variables = new_variables
140 end
141
142 redef fun call_commons(v, m, a, f)
143 do
144 var injected_variables = self.injected_variables
145 if injected_variables == null then return super
146
147 # Inject main variables in the frame
148 assert f isa InterpreterFrame
149 for variable, i in injected_variables do
150 f.map[variable] = i
151 end
152
153 var res = super
154
155 # Update the values of the variables
156 for variable in injected_variables.keys do
157 injected_variables[variable] = f.map[variable]
158 end
159 # Retrieve the values of the new main variables
160 for variable in new_variables.as(not null) do
161 injected_variables[variable] = f.map[variable]
162 end
163
164 return res
165 end
166 end
167
168 # Create a tool context to handle options and paths
169 var toolcontext = new ToolContext
170 toolcontext.option_context.options_before_rest = true
171 toolcontext.accept_no_arguments = true
172 toolcontext.keep_going = true
173 toolcontext.process_options(args)
174
175 # We need a model to collect stufs
176 var model = new Model
177 # An a model builder to parse files
178 var modelbuilder = new ModelBuilder(model, toolcontext)
179
180 var arguments = toolcontext.option_context.rest
181
182 # Our initial program is an empty module
183 var amodule = toolcontext.parse_module("")
184 var mmodule = modelbuilder.load_rt_module(null, amodule, "input-0")
185 modelbuilder.run_phases
186 if not toolcontext.check_errors then return
187 assert mmodule != null
188 var mmodules = [mmodule]
189 var mainmodule = toolcontext.make_main_module(mmodules)
190
191 # Start and run the interpreter on the empty module
192 var interpreter = new NaiveInterpreter(modelbuilder, mainmodule, arguments)
193 interpreter.start(mainmodule)
194
195 # Get the main object and the main method
196 var mainobj = interpreter.mainobj
197 assert mainobj != null
198 var sys_type = mainobj.mtype.as(MClassType)
199 var mainprop = mainmodule.try_get_primitive_method("main", sys_type.mclass)
200 assert mainprop != null
201
202 var main_variables = new Map[Variable, Instance]
203
204 var l = 0
205 loop
206 # Next piece of Nit code
207 var n = toolcontext.i_parse("-->")
208 if n == null then
209 break
210 end
211
212 # Special adhoc command
213 if n isa TString then
214 var s = n.text
215 if s == ":q" then
216 break
217 else
218 print "`:q` to quit"
219 end
220 continue
221 end
222
223 # An error
224 if n isa AError then
225 modelbuilder.error(n, n.message)
226 toolcontext.check_errors
227 continue
228 end
229
230 #n.dump_tree
231
232 # A syntactically module!
233 amodule = n.as(AModule)
234
235 # Try to load it as a submodule
236 l += 1
237 var newmodule = modelbuilder.load_rt_module(mainmodule, amodule, "input-{l}")
238 if newmodule == null then continue
239
240 var main_method = null
241 if amodule.n_classdefs.not_empty and amodule.n_classdefs.last isa AMainClassdef then
242 main_method = amodule.n_classdefs.last.n_propdefs.first
243 assert main_method isa AMethPropdef
244 main_method.injected_variables = main_variables
245 end
246
247 modelbuilder.run_phases
248 if not toolcontext.check_errors then
249 toolcontext.error_count = 0
250 continue
251 end
252 # Everything is fine, the module is the new main module!
253 mainmodule = newmodule
254 interpreter.mainmodule = mainmodule
255
256 # Run the main if the AST contains a main
257 if main_method != null then
258 do
259 interpreter.catch_count += 1
260 interpreter.send(mainprop, [mainobj])
261 catch
262 var e = interpreter.last_error
263 if e != null then
264 var en = e.node
265 if en != null then
266 print "{en.location}: Runtime error: {e.message}\n{en.location.colored_line("0;31")}"
267 else
268 print "Runtime error: {e.message}"
269 end
270 end
271 print interpreter.stack_trace
272 interpreter.frames.clear
273 end
274 end
275 end