1 # coding: utf-8
2 # frozen_string_literal: true
3
4 require 'pp'
5
6
7
8 module Umu
9
10 module Commander
11
12 module_function
13
14 begin
15 require 'readline'
16
17 def input(prompt)
18 Readline.readline(prompt, true)
19 end
20
21 rescue ::LoadError
22 def input(prompt)
23 STDERR.print prompt; STDERR.flush
24
25 STDIN.gets
26 end
27 end
28
29
30 STDIN_FILE_NAME = '<stdin>'
31
32 PARSER = CS::Parser.new
33
34
35 def main(args)
36 ASSERT.kind_of args, ::Array
37
38 exit_code = begin
39 setup_env, file_names = Commander.setup args
40
41 if setup_env.pref.interactive_mode?
42 env = unless file_names.empty?
43 Commander.process_files file_names, setup_env
44 else
45 setup_env
46 end
47
48 Commander.interact env, setup_env
49 else
50 if file_names.empty?
51 raise X::CommandError.new('No input files')
52 end
53
54 Commander.process_files file_names, setup_env
55 end
56
57 0
58 rescue X::Abstraction::RuntimeError => e
59 e.print_backtrace
60 STDERR.puts
61 STDERR.puts e.to_s
62
63 1
64 rescue X::Abstraction::Expected => e
65 STDERR.puts
66 STDERR.puts e.to_s
67
68 1
69 rescue ::SystemCallError, ::IOError, ::SystemStackError => e
70 STDERR.puts
71 STDERR.printf "[%s] %s\n", e.class.to_s, e.to_s
72
73 1
74 rescue ::Interrupt
75 1
76 end
77
78 ASSERT.kind_of exit_code, ::Integer
79 end
80
81
82 def setup(args)
83 pref, file_names = Commander.parse_option(
84 args, E::INITIAL_PREFERENCE
85 )
86
87 env = if pref.no_prelude?
88 E.setup pref
89 else
90 Commander.process_source(
91 Prelude::SOURCE_TEXT,
92 Prelude::FILE_NAME,
93 E.setup(E::INITIAL_PREFERENCE),
94 Prelude::START_LINE_NUM
95 ).update_preference pref
96 end
97
98 [env, file_names]
99 end
100
101
102 def interact(init_env, setup_env)
103 ASSERT.kind_of init_env, E::Entry
104 ASSERT.kind_of setup_env, E::Entry
105
106 init_tokens = []
107 init_lexer = LL.make_initial_lexer STDIN_FILE_NAME, 0
108 final_env = loop.inject(
109 [init_tokens, init_lexer, init_env]
110 ) { |(tokens, lexer, env ), _|
111 ASSERT.kind_of tokens, ::Array
112 ASSERT.kind_of lexer, LL::Abstract
113 ASSERT.kind_of env, E::Entry
114
115 line_num = lexer.loc.line_num
116
117 prompt = format("umu:%d%s ",
118 line_num + 1,
119
120 if lexer.in_comment?
121 '#'
122 elsif lexer.in_braket?
123 '*'
124 else
125 '>'
126 end
127 )
128
129 next_tokens, next_lexer, next_env = begin
130 opt_line = Commander.input prompt
131 break env if opt_line.nil?
132 line = opt_line
133
134 if lexer.between_braket? && /^:/ =~ line
135 [
136 [],
137 lexer.next_line_num,
138 Subcommand.execute(line, line_num, env, setup_env)
139 ]
140 else
141 Commander.process_line(
142 line + "\n",
143 tokens,
144 lexer,
145 env.update_line(STDIN_FILE_NAME, line_num, line)
146 )
147 end
148 rescue X::Abstraction::RuntimeError => e
149 e.print_backtrace
150 STDERR.puts
151 STDERR.puts e.to_s
152
153 [[], lexer.recover, env]
154 rescue X::Abstraction::Expected => e
155 STDERR.puts
156 STDERR.puts e.to_s
157
158 [[], lexer.recover, env]
159 rescue ::SystemCallError, ::IOError, ::SystemStackError => e
160 STDERR.puts
161 STDERR.printf "[%s] %s\n", e.class.to_s, e.to_s
162
163 [[], lexer.recover, env]
164 rescue ::Interrupt
165 STDERR.puts
166 STDERR.puts '^C'
167
168 [[], lexer.recover, env]
169 end
170
171 [next_tokens, next_lexer, next_env]
172 }
173
174 ASSERT.kind_of final_env, E::Entry
175 end
176
177
178 def process_line(line, tokens, init_lexer, env)
179 ASSERT.kind_of line, ::String
180 ASSERT.kind_of tokens, ::Array
181 ASSERT.kind_of init_lexer, LL::Abstract
182 ASSERT.kind_of env, E::Entry
183
184 pref = env.pref
185 file_name = STDIN_FILE_NAME
186 line_num = init_lexer.loc.line_num
187
188 if pref.any_trace?
189 STDERR.printf "________ Source: '%s' ________\n", file_name
190 STDERR.printf "%04d: %s", line_num + 1, line
191 end
192
193 if pref.lex_trace_mode?
194 STDERR.puts
195 STDERR.printf "________ Lexer Trace: '%s' ________", file_name
196 STDERR.printf "\nINIT-LEXTER: %s\n", init_lexer.to_s
197 end
198
199 scanner = ::StringScanner.new line
200 next_tokens, next_lexer = LL.lex(
201 tokens, init_lexer, scanner, pref
202 ) do |event, matched, output_tokens, lexer, before_line_num|
203
204 if pref.lex_trace_mode?
205 STDERR.printf("\nPATTERN: %s \"%s\"\n",
206 event.to_s,
207 Escape.unescape(matched)
208 )
209 unless output_tokens.empty?
210 STDERR.printf(" TOKENS: %s -- %s\n",
211 output_tokens.map(&:to_s).join(' '),
212 output_tokens[0].loc.to_s
213 )
214 end
215 STDERR.printf " NEXT-LEXER: %s\n", lexer.to_s
216 end
217 end
218
219 if next_lexer.between_braket?
220 if pref.dump_mode?
221 STDERR.puts
222 STDERR.printf("________ Tokens: '%s' ________\n",
223 file_name
224 )
225 STDERR.printf("%04d: %s\n",
226 line_num + 1,
227 next_tokens.map(&:to_s).join(' ')
228 )
229 end
230
231 next_env = Commander.process_tokens(next_tokens, env) do
232 |sym, value|
233
234 next if /^%/ =~ sym.to_s
235
236 case value
237 when VC::Fun
238 STDERR.printf "fun %s = ", sym.to_s
239 when VC::Struct::Entry
240 STDERR.printf "structure %s = ", sym.to_s
241 else
242 STDERR.printf("val %s : %s = ",
243 sym.to_s,
244 value.type_sym.to_s
245 )
246 end
247 STDERR.flush
248 PP.pp value, STDERR
249 STDOUT.flush
250 end
251
252 [[], next_lexer, next_env]
253 else
254 [next_tokens, next_lexer, env]
255 end
256 end
257
258
259 def process_files(file_names, init_env)
260 ASSERT.kind_of file_names, ::Array
261 ASSERT.kind_of init_env, E::Entry
262
263 final_env = file_names.inject(init_env) { |env, file_name|
264 ASSERT.kind_of env, E::Entry
265 ASSERT.kind_of file_name, ::String
266
267 source = ::File.open(file_name) { |io| io.read }
268
269 Commander.process_source source, file_name, env
270 }
271
272 ASSERT.kind_of final_env, E::Entry
273 end
274
275
276 def process_source(source, file_name, env, init_line_num = 0)
277 ASSERT.kind_of source, ::String
278 ASSERT.kind_of file_name, ::String
279 ASSERT.kind_of env, E::Entry
280 ASSERT.kind_of init_line_num, ::Integer
281
282 pref = env.pref
283
284 if pref.dump_mode?
285 STDERR.puts
286 STDERR.printf "________ Source: '%s' ________\n", file_name
287 source.each_line.with_index do |line, index|
288 STDERR.printf "%04d: %s", index + 1, line
289 end
290 end
291
292 if pref.dump_mode?
293 STDERR.puts
294 STDERR.printf "________ Tokens: '%s' ________", file_name
295 end
296
297 init_tokens = []
298 init_lexer = LL.make_initial_lexer file_name, init_line_num
299 scanner = ::StringScanner.new source
300 tokens, _lexer = LL.lex(
301 init_tokens, init_lexer, scanner, pref
302 ) do |_event, _matched, output_tokens, _lexer, before_line_num|
303
304 if pref.dump_mode?
305 output_tokens.each do |token|
306 tk_line_num = token.loc.line_num
307
308 if tk_line_num != before_line_num
309 STDERR.printf "\n%04d: ", tk_line_num + 1
310 end
311
312 unless token && token.separator?
313 STDERR.printf "%s ", token.to_s
314 STDERR.flush
315 end
316 end
317 end
318 end
319
320 if pref.dump_mode?
321 STDERR.puts
322 end
323
324 Commander.process_tokens(
325 tokens,
326 env.update_source(file_name, source, init_line_num)
327 )
328 end
329
330
331 def process_tokens(tokens, init_env)
332 ASSERT.kind_of tokens, ::Array
333 ASSERT.kind_of init_env, E::Entry
334
335 csyn_stmts = PARSER.parse tokens
336
337 final_env = csyn_stmts.inject(init_env) { |env, csyn_stmt|
338 ASSERT.kind_of env, E::Entry
339 ASSERT.kind_of csyn_stmt, CS::Abstract
340
341 result = execute(csyn_stmt, env)
342 ASSERT.kind_of result, ASR::Abstract
343
344 case result
345 when ASR::Value
346 value = result.value
347
348 if block_given?
349 yield :it, value
350 end
351
352 next_env = env.va_extend_value :it, value
353 when ASR::Environment
354 next_env = result.env
355
356 if block_given?
357 next_env.va_get_bindings_difference_with(env).each do
358 |sym, value|
359
360 yield sym, value
361 end
362 end
363
364 next_env
365 else
366 ASSERT.abort result.inspect
367 end
368 }
369
370 ASSERT.kind_of final_env, E::Entry
371 end
372
373
374 def execute(csyn, env)
375 ASSERT.kind_of env, E::Entry
376 ASSERT.kind_of csyn, CS::Abstract
377
378 print_trace_of_con_syntax csyn, env.pref
379
380 asyn = csyn.desugar env
381
382 print_trace_of_abs_syntax asyn, env.pref
383
384 result = asyn.evaluate env
385
386 if env.pref.any_trace?
387 STDERR.puts
388 end
389
390 ASSERT.kind_of result, ASR::Abstract
391 end
392
393
394 def print_trace_of_con_syntax(csyn, pref)
395 ASSERT.kind_of csyn, CS::Abstract
396
397 if pref.dump_mode?
398 STDERR.puts
399 STDERR.printf(
400 "________ Concrete Syntax: %s ________\n",
401 csyn.loc.to_s
402 )
403 PP.pp csyn, STDERR
404 end
405
406 if pref.trace_mode?
407 STDERR.puts
408 STDERR.puts "________ Desugar Trace ________"
409 end
410 end
411
412
413 def print_trace_of_abs_syntax(asyn, pref)
414 ASSERT.kind_of asyn, AS::Abstract
415
416 if pref.dump_mode?
417 STDERR.puts
418 STDERR.printf(
419 "________ Abstract Syntax: %s ________\n",
420 asyn.loc.to_s
421 )
422 PP.pp asyn, STDERR
423 end
424
425 if pref.trace_mode?
426 STDERR.puts
427 STDERR.puts "________ Evaluator Trace ________"
428 end
429 end
430
431 end # Umu::Commander
432
433 end # Umu