~tim/scheme-vm

scheme-vm/program.rb -rw-r--r-- 1.9 KiB View raw
                                                                                
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
require_relative 'parser'
require_relative 'compiler'
require 'time'

class Program
  EXIT_CODE_SYNTAX_ERROR   = 1
  EXIT_CODE_VAR_UNDEFINED  = 2
  EXIT_CODE_STACK_TOO_DEEP = 3
  EXIT_CODE_FATAL_ERROR    = 4

  def initialize(code, filename: '(unknown)', args: [], stdout: $stdout)
    @filename = filename
    @args = args
    @stdout = stdout
    @source = {}
    @code = code
    @debug = 0
  end

  attr_accessor :debug

  def run
    @ast = parse(@code)
    @compiler = Compiler.new(@ast, filename: @filename, program: self)
    @instr = @compiler.compile
    VM::PrettyPrinter.new(@instr, grouped: true, ip: true).print if @debug >= 1
    vm.debug = @debug
    vm.execute(@instr)
    vm.return_value
  rescue Parser::ParseError => e
    print_syntax_error(e, @code)
    EXIT_CODE_SYNTAX_ERROR
  rescue VM::VariableUndefined => e
    print_general_error(e)
    EXIT_CODE_VAR_UNDEFINED
  rescue VM::CallStackTooDeep => e
    print_fatal_error(e)
    EXIT_CODE_STACK_TOO_DEEP
  rescue VM::FatalError => e
    print_fatal_error(e)
    EXIT_CODE_FATAL_ERROR
  end

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

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

  def parse(code, filename: @filename)
    @source[filename] = code
    Parser.new(code, filename: filename).parse
  end

  private

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

  def print_general_error(e)
    code = @source[e.filename]
    VM::SourceCodeErrorPrinter.new(
      title: "Error: #{e.message}",
      code: code,
      error: e
    ).print(@stdout)
  end

  def print_syntax_error(e, code)
    VM::SourceCodeErrorPrinter.new(
      title: "Syntax Error:",
      code: code,
      error: e
    ).print(@stdout)
  end

  def print_fatal_error(e)
    VM::CallStackPrinter.new(
      title: "Error: #{e.message}",
      call_stack: e.call_stack,
      source: @source,
      message: e.message
    ).print(@stdout)
  end
end