# 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.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