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
|
# TODO: Write documentation for `Flint`
require "option_parser"
require "./util.cr"
require "./program.cr"
require "./brainfuck/*"
require "./thue/*"
module Flint
VERSION = "0.1.1"
enum Language
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 = case _args[0].downcase
when "brainfuck" then Language::Brainfuck
when "thue" then Language::Thue
else 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::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
|