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
|