summaryrefslogtreecommitdiff
path: root/src/flint.cr
blob: 02fdd855dbddb50b8c64c61fd2022d1ec1bd492f (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
# TODO: Write documentation for `Flint`

require "option_parser"

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

module Flint
  VERSION = "0.1.1"

  enum Language
    AmongUs
    Brainfuck
    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 flinti 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::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