summaryrefslogtreecommitdiff
path: root/src/flint.cr
blob: 4fe0ef1f4dd27789a4333df3975cbb553b1c9b69 (plain)
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# TODO: Write documentation for `Flint`

require "option_parser"

require "./util.cr"
require "./program.cr"
require "./among_us/*"
require "./brainfuck/*"
require "./false/program.cr"
require "./thue/*"

module Flint
  VERSION = "0.1.1"

  enum Language
    AmongUs
    Brainfuck
    False
    Thue
  end

  enum ExecutionMode
    Interpret
    Compile
  end

  private def self.crash(message : String)
    puts message
    exit(1)
  end

  private def self.crash(message : String, p : OptionParser)
    puts message
    puts p
    exit(1)
  end

  private def self.crash(message : String, ex_message : String?)
    puts message
    puts ex_message unless ex_message.nil?
    exit(1)
  end

  def self.main
    execution_mode = ExecutionMode::Interpret
    language = nil
    source_file = nil
    read_stdin = false
    language_options = Hash(Symbol, String).new

    parser = OptionParser.new do |parser|
      parser.banner = "Basic usage: flint [OPTIONS] [LANGUAGE] [FILE]"

      parser.on("-h", "--help", "show this help and exit") do
        puts parser
        exit(0)
      end
      parser.on("-i", "--interpet", "interpet the provided source code, implied by default, overridden by '-c' or '--compile'") { nil }
      parser.on("-c", "--compile", "produce a binary or different source code file instead of interpeting the code, overrides an explicit '-i' or '--intepret' flag") do
        execution_mode = ExecutionMode::Compile
      end
      parser.on("--stdin", "read source code from STDIN instead of a file") do
        read_stdin = true
      end
      parser.on("-m CELLS", "--memory-size=CELLS", "specify the number of memory cells available, defaults vary depending on language") do |_cells|
        language_options[:memory_size] = _cells
      end
      parser.on("--supported-languages", "print a list of languages supported by flint and exit") do
        puts "Languages supported by flint (not guaranteed to be fully implemented):"
        Language.names.each { |name| puts name }
        exit(0)
      end

      parser.unknown_args do |_args|
        crash("ERROR: No language chosen", parser) if _args.size == 0
        crash("ERROR: No source file chosen", parser) if _args.size == 1 && !read_stdin
        source_file = _args[1] unless read_stdin
        language = Language.parse?(_args[0])
        if language.nil?
          crash("ERROR: Unknown language specified: '#{_args[0]}'\nUser the '--supported-languages' flag to see whatlanguages are valid.", parser)
        end
      end
    end

    parser.parse

    # at this point, source_file and language are provably not nil
    # the not_nil! method has to be used because the type-checker cannot deduce this
    begin
      source_io = read_stdin ? STDIN : File.new(source_file.not_nil!)
    rescue File::NotFoundError
      crash("ERROR: Could not load source file '#{source_file}'")
    end

    program = case language.not_nil!
      in Language::AmongUs
        AmongUs::Program.new(source_io)
      in Language::False
        False::Program.new(source_io)
      in Language::Brainfuck
        begin
          m_str = language_options[:memory_size]?
          m = m_str.try { |_m| _m.to_i}
        rescue ArgumentError
          crash("ERROR: Failed to parse the tape size value of '#{m_str}' as an integer", parser)
        end
        Brainfuck::Program.new(source_io, m)
      in Language::Thue
        Thue::Program.new(source_io)
      end

    begin
      case execution_mode
      when ExecutionMode::Interpret
        program.interpret
      when ExecutionMode::Compile
        program.compile
      end
    rescue ex : Util::ParserError
      crash("Caught ParserError", ex.message)
    rescue ex : Util::InterpreterError
      crash("Error encountered while interpreting:", ex.message)
    rescue ex : Util::CompilerError
      crash("Failed to compile program:", ex.message)
    end
  end
end

Flint.main