nitin: keep the line number increasing between prompts
[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
26 redef class ToolContext
27
28 # --no-prompt
29 var opt_no_prompt = new OptionBool("Disable writing a prompt.", "--no-prompt")
30
31 # --source-name
32 var opt_source_name = new OptionString("Set a name for the input source.", "--source-name")
33
34 redef init do
35 super
36 option_context.add_option(opt_no_prompt, opt_source_name)
37 end
38
39 # Parse a full module given as a string
40 #
41 # Return a AModule or a AError
42 fun p_module(string: String): ANode
43 do
44 var source_name = opt_source_name.value or else ""
45 string = "\n" * last_line + string
46 var source = new SourceFile.from_string(source_name, string)
47 var lexer = new Lexer(source)
48 var parser = new Parser(lexer)
49 var tree = parser.parse
50
51 var eof = tree.n_eof
52 if eof isa AError then
53 return eof
54 end
55 return tree.n_base.as(not null)
56 end
57
58 # Read an user-line with a given `prompt`
59 #
60 # Return `null` if end of file
61 fun readline(prompt: String): nullable String do
62 printn prompt
63 var res = stdin.read_line
64 if res == "" and stdin.eof then return null
65 return res
66 end
67
68 # Add `text` in the history for `readline`.
69 #
70 # With the default implementation, the history is dropped
71 fun readline_add_history(text: String) do end
72
73 # The last line number read by `i_parse`
74 var last_line = 0
75
76 # Parse the input of the user as a module
77 fun i_parse(prompt: String): nullable ANode
78 do
79 var oldtext = ""
80
81 loop
82 var s
83 if opt_no_prompt.value then
84 s = stdin.read_line
85 if s == "" and stdin.eof then s = null
86 else
87 s = readline(prompt)
88 end
89 if s == null then return null
90 if s == "" then
91 if oldtext != "" then
92 oldtext += "\n"
93 else
94 last_line += 1
95 end
96 continue
97 end
98
99 if s.chars.first == ':' then
100 var res = new TString
101 res.text = s
102 return res
103 end
104
105 var text = oldtext + s + "\n"
106 oldtext = ""
107 var n = p_module(text)
108
109 if n isa AParserError and (n.token isa EOF) then
110 # Unexpected end of file, thus continuing
111 if oldtext == "" then prompt = "." * prompt.length
112 oldtext = text
113 continue
114 end
115
116 last_line = n.location.file.line_starts.length - 1
117 readline_add_history(text.chomp)
118 return n
119 end
120 end
121 end
122
123
124 # Create a tool context to handle options and paths
125 var toolcontext = new ToolContext
126 toolcontext.option_context.options_before_rest = true
127 toolcontext.accept_no_arguments = true
128 toolcontext.keep_going = true
129 toolcontext.process_options(args)
130
131 # We need a model to collect stufs
132 var model = new Model
133 # An a model builder to parse files
134 var modelbuilder = new ModelBuilder(model, toolcontext)
135
136 var arguments = toolcontext.option_context.rest
137
138 # Our initial program is an empty module
139 var amodule = toolcontext.parse_module("")
140 var mmodule = modelbuilder.load_rt_module(null, amodule, "input-0")
141 modelbuilder.run_phases
142 if not toolcontext.check_errors then return
143 assert mmodule != null
144 var mmodules = [mmodule]
145 var mainmodule = toolcontext.make_main_module(mmodules)
146
147 # Start and run the interpreter on the empty module
148 var interpreter = new NaiveInterpreter(modelbuilder, mainmodule, arguments)
149 interpreter.start(mainmodule)
150
151 # Get the main object and the main method
152 var mainobj = interpreter.mainobj
153 assert mainobj != null
154 var sys_type = mainobj.mtype.as(MClassType)
155 var mainprop = mainmodule.try_get_primitive_method("main", sys_type.mclass)
156 assert mainprop != null
157
158 var l = 0
159 loop
160 # Next piece of Nit code
161 var n = toolcontext.i_parse("-->")
162 if n == null then
163 break
164 end
165
166 # Special adhoc command
167 if n isa TString then
168 var s = n.text
169 if s == ":q" then
170 break
171 else
172 print "`:q` to quit"
173 end
174 continue
175 end
176
177 # An error
178 if n isa AError then
179 print "{n.location.colored_line("0;31")}: {n.message}"
180 continue
181 end
182
183 #n.dump_tree
184
185 # A syntactically module!
186 amodule = n.as(AModule)
187
188 # Try to load it as a submodule
189 l += 1
190 var newmodule = modelbuilder.load_rt_module(mainmodule, amodule, "input-{l}")
191 if newmodule == null then continue
192 modelbuilder.run_phases
193 if not toolcontext.check_errors then
194 toolcontext.error_count = 0
195 continue
196 end
197 # Everything is fine, the module is the new main module!
198 mainmodule = newmodule
199 interpreter.mainmodule = mainmodule
200
201 # Run the main if the AST contains a main
202 if amodule.n_classdefs.not_empty and amodule.n_classdefs.last isa AMainClassdef then
203 do
204 interpreter.catch_count += 1
205 interpreter.send(mainprop, [mainobj])
206 catch
207 var e = interpreter.last_error
208 if e != null then
209 var en = e.node
210 if en != null then
211 print "{en.location}: Runtime error: {e.message}\n{en.location.colored_line("0;31")}"
212 else
213 print "Runtime error: {e.message}"
214 end
215 end
216 print interpreter.stack_trace
217 interpreter.frames.clear
218 end
219 end
220 end