def print_help puts <<% if stdlib %> #include <% end %> int main() { <%= ws %>unsigned char arr[] = <%= c_array %>; <%= ws %>printf("%s", arr); <%= ws %>return <%= stdlib ?"EXIT_SUCCESS":"0" %>; } CFILE # Arguments, which were partially passed via the command line flags = [:interpret_comments, :stdlib, :static, :analyzer] flags.delete :interpret_comments if ARGV.include? "-comments" flags.delete :stdlib if ARGV.include? "-stdlib" flags.delete :static if ARGV.include? "-static" flags.delete :analyzer if ARGV.include? "-analyzer" flags.push :tabs if ARGV.include? "+tabs" flags.push :cells if ARGV.include? "+cells" ws = flags.include?(:tabs) ? "\t" : " " puts "Whitespace: #{ws.inspect}" puts "Reading file..." filename = ARGV[-1] if filename == nil puts "No file name was given" print_help elsif ! File.readable?(filename) puts "Cannot read file #{filename}" exit! false end puts "Loading compiler..." class Compiler BF_CHARS = "<>+-,.[]".split "" IGNORE_CMT_CHARS = "\n\t\r".split "" C_FILE_BEGIN = <<% if @stdlib %> #include <% end %> int main() { <%= @whitespace %>unsigned char arr[<%= @num_cells %>]; <%= @whitespace %>for (<%= @stdlib ? "size_t" : "unsigned int" %> i = 0; i < <%= @num_cells %>; i++) <%= @whitespace %>{ <%= @whitespace * 2 %>arr[i] = 0; <%= @whitespace %>} <%= @whitespace %>unsigned char * ptr = arr; CFILE C_FILE_END = <return <%= @stdlib?"EXIT_SUCCESS":"0" %>; } CFILE INC_POINTER = <ptr++; CFILE DEC_POINTER = <ptr--; CFILE INC_VALUE = <(*ptr)++; CFILE DEC_VALUE = <(*ptr)--; CFILE INPUT_CHAR = <*ptr = getchar(); CFILE OUTPUT_CHAR = <putchar(*ptr); CFILE LOOP_BEGIN = <while(*ptr) <%= @whitespace * (spaces + 1) %>{ CFILE LOOP_END = <} CFILE COMMENT_BEGIN = </* CFILE COMMENT_BEGIN.chomp! COMMENT_END = <" code = ERB.new(INC_POINTER).result binding when "+" code = ERB.new(INC_VALUE).result binding when "-" code = ERB.new(DEC_VALUE).result binding when "[" code = ERB.new(LOOP_BEGIN).result binding nested = 1 when "]" code = ERB.new(LOOP_END).result binding nested = -1 when "," code = ERB.new(INPUT_CHAR).result binding when "." code = ERB.new(OUTPUT_CHAR).result binding end return [code, nested] end end puts "Loading analyzer..." class Analyzer BF_CHARS = "<>+-,.[]".split "" attr_reader :rounds, :max, :output def initialize code, process_output = false @bf = code @code_pointer = 0 @cells = Array.new @pointer = 0 @rounds = 0 @max = 1 @output = [] @process_output = process_output end def clear @code_pointer = 0 @cells = Array.new @pointer = 0 @rounds = 0 @max = 1 @output = [] end # The Analyzer works similar to a BF interpreter. # However, it does not accept any input. It determines the maximum number of # cells, the number of steps and the output. def analyze loops = [] while @code_pointer < @bf.length c = @bf[@code_pointer] print "\b#{c}" if @process_output && BF_CHARS.any? { |s| s == c } case c when "<" @pointer -= 1 when ">" @pointer += 1 when "+" @cells[@pointer] = @cells[@pointer].to_i + 1 when "-" @cells[@pointer] -= 1 if @cells[@pointer].to_i > 0 when "." @output << @cells[@pointer].to_i when "[" loops << @code_pointer @code_pointer = find_closing_bracket - 1 when "]" if @cells[@pointer].to_i == 0 loops.pop else @code_pointer = loops[-1] end end @code_pointer += 1 @rounds += 1 @max = @pointer if @pointer > max end @max += 1 end protected def find_closing_bracket counter = 0 search = @code_pointer + 1 while search < @bf.length case @bf[search] when "[" counter += 1 when "]" if counter > 0 counter -= 1 else break end end search += 1 end return search end end bf = File.read filename num_cells = 255 code = nil input_indepented_code = ! bf.include?(",") # If the user allows static compilation, the code does not request any input, # the use of the analyzer is allowed and no number of cells is specified, then... if input_indepented_code && flags.include?(:static) && flags.include?(:analyzer) && ! flags.include?(:cells) puts "Generating static program... (Use -static option to disable)" ana = Analyzer.new bf puts "Analying code... (Use -analyzer option to disable)" ana.analyze if ana.output.empty? puts "Program does not contain any output. The compilation is aborted." exit! false else c_array = "\{#{ana.output.join(", ")}, 0\}" stdlib = flags.include? :stdlib code = ERB.new(STATIC_PROG).result binding end else # If the code does not request any input, allows the use of the analyzer and # no number of cells is specified, then... if input_indepented_code && ! flags.include?(:cells) && flags.include?(:analyzer) ana = Analyzer.new bf puts "Analying code... (Use -analyzer option to disable)" ana.analyze num_cells = ana.max puts "Number of cells determined by the analyzer: #{ana.max}" puts "Number of steps to calculate the output: #{ana.rounds}" end if flags.include? :cells if ARGV[-2] num_cells = ARGV[-2].to_i else puts "The number of cells could not be read. The compilation is canceled." exit! false end end puts "Number of cells: #{num_cells}" puts "Compiling..." com = Compiler.new bf, num_cells, ws, flags.include?(:interpret_comments), flags.include?(:stdlib), true print "Compile " com.compile print "\n" code = com.ccode end puts "Compiled successfully!" puts "Writing file..." File.write "#{filename}.c", code puts "The full compilation process completed successfully."