~tim/scheme-vm

ref: c5110102ce2df2d78a0c87f293edf20f5a3984e7 scheme-vm/program.rb -rw-r--r-- 2.6 KiB
c5110102Tim Morgan Add more char functions 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
require_relative 'parser'
require_relative 'compiler'
require 'time'

class Program
  EXIT_CODE_VAR_UNDEFINED  = 1
  EXIT_CODE_STACK_TOO_DEEP = 2
  EXIT_CODE_SYNTAX_ERROR   = 3

  def initialize(code, filename: '(unknown)', args: [], stdout: $stdout)
    @filename = filename
    @args = args
    @stdout = stdout
    @code = code
    start_parse = Time.now
    @compiler = Compiler.new(code, filename: filename)
    @total_parse = Time.now - start_parse
  rescue Parser::ParseError => e
    print_syntax_error(e)
    @error_parsing = true
  end

  def run(code: nil, debug: 0)
    return EXIT_CODE_SYNTAX_ERROR if @error_parsing
    start_compile = Time.now
    @instr = @compiler.compile(code)
    @total_compile = Time.now - start_compile
    VM::PrettyPrinter.new(@instr, grouped: true, ip: true).print if debug >= 1
    vm.debug = debug
    start_execute = Time.now
    vm.execute(@instr)
    @total_execute = Time.now - start_execute
    print_timings if ENV['PRINT_TIMINGS']
    vm.return_value
  rescue VM::VariableUndefined => e
    print_variable_undefined_error(e)
    EXIT_CODE_VAR_UNDEFINED
  rescue VM::CallStackTooDeep => e
    print_call_stack_too_deep_error(e)
    EXIT_CODE_STACK_TOO_DEEP
  end

  def filename=(f)
    @filename = f
    @compiler.filename = f
  end

  def stdout=(io)
    @stdout = io
    vm.stdout = io
  end

  private

  def vm
    @vm ||= VM.new(stdout: @stdout, args: @args)
  end

  def print_variable_undefined_error(e)
    message = "Error: #{e.message}"
    message += error_details_to_s(e)
    @stdout.puts(message)
  end

  def error_details_to_s(e, code = nil)
    return '' unless e.filename && e.filename != ''
    return '' unless (code ||= @compiler.source[e.filename])
    lines_range = (e.line - 2)..(e.line - 1)
    code = code.split("\n")[lines_range].map { |l| "  #{l}" }.join("\n")
    line = "#{e.filename}##{e.line}"
    pointer = " #{' ' * e.column}^ #{e.message}"
    "\n\n#{line}\n\n#{code}\n#{pointer}"
  end

  def print_call_stack_too_deep_error(e)
    message = "Error: #{e.message}"
    @stdout.puts(message)
    e.call_stack.reverse.each do |frame|
      next unless (name = frame[:name])
      @stdout.puts "#{name.filename}##{name.line}"
      code = @compiler.source[name.filename].split("\n")[name.line - 1]
      @stdout.puts "  #{code}"
      @stdout.puts " #{' ' * name.column}^"
    end
  end

  def print_syntax_error(e)
    message = 'Syntax Error:' + error_details_to_s(e, @code)
    @stdout.puts(message)
  end

  def print_timings
    puts "parse:   #{@total_parse}"
    puts "compile: #{@total_compile}"
    puts "execute: #{@total_execute}"
  end
end