Last active
September 28, 2020 08:13
-
-
Save Sinthorion/d7587a54680e7a6741043016982ce8c6 to your computer and use it in GitHub Desktop.
Simple Ruby Brainfuck Runner
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'optparse' | |
options = Hash.new(false) | |
option_parser = OptionParser.new do |opts| | |
opts.banner = "Usage: bf.rb [options] <bf_file_or_string>" | |
opts.on '-t', '--[no-]timing', 'Track compilation and execution time' do |v| | |
options[:timing] = v | |
end | |
opts.on '-e', '--echo', 'Echo input file' do |v| | |
options[:echo] = v | |
end | |
opts.on '-c', '--compiled', 'Echo generated Ruby code' do |v| | |
options[:compiled] = v | |
end | |
opts.on '-o', '--optimize', 'Use experimental optimizations' do |v| | |
options[:optimize] = v | |
puts "Warning: The option --optimize is not yet supported\n" | |
end | |
opts.on '-d', '--debug', 'Enable debug mode' do |v| | |
options[:debug] = v | |
end | |
opts.on '-i', '--input=INPUT', 'Use given input string instead of STDIN' do |v| | |
options[:input] = v.each_byte | |
end | |
opts.on '--count', 'Count executed instructions' do |v| | |
options[:count] = v | |
end | |
if ARGV.empty? | |
abort(opts.help) | |
end | |
end.parse! | |
begin | |
ic = 0 if options[:count] | |
t0 = Time.now if options[:timing] | |
input = File.exists?(ARGV[0]) ? File.open(ARGV[0], 'r').read : ARGV[0] | |
puts input if options[:echo] | |
code = 'm=Hash.new(p=0);'+input.gsub( | |
/./, | |
'>' => 'p+=1;' + (options[:count] ? 'ic+=1;' : ''), | |
'<' => 'p-=1;' + (options[:count] ? 'ic+=1;' : ''), | |
'+' => 'm[p]+=1;' + (options[:count] ? 'ic+=1;' : ''), | |
'-' => 'm[p]-=1;' + (options[:count] ? 'ic+=1;' : ''), | |
'[' => '(' + (options[:count] ? 'ic+=1;' : ''), | |
']' => ')while((m[p]&=255)!=0);' + (options[:count] ? 'ic+=1;' : ''), | |
'.' => 'putc(m[p]&=255);' + (options[:count] ? 'ic+=1;' : ''), | |
',' => options[:input] ? 'begin m[p]=options[:input].next rescue m[p]=0 end;' : 'm[p]=STDIN.getbyte.ord if !STDIN.eof;' + (options[:count] ? 'ic+=1;' : ''), | |
'#' => options[:debug] ? 'print "m="+0.upto(m.keys.max).map{|i|m[i]%256}.to_s+"; p=#{p};\n";' : '', | |
) | |
puts code if options[:compiled] | |
t1 = Time.now if options[:timing] | |
print "\n" | |
print "output:\n" | |
eval code | |
print "\n" | |
t2 = Time.now if options[:timing] | |
if options[:timing] | |
puts "Time used:" | |
puts "Compilation: " + ((t1-t0)*1000).to_s + "ms" | |
puts "Execution: " + ((t2-t1)*1000).to_s + "ms" | |
end | |
puts "#{ic} instructions" if options[:count] | |
rescue SyntaxError => e | |
abort("Syntax Error - Are there unbalanced brackets?") | |
rescue Interrupt => e | |
abort("Interrupted") | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'optparse' | |
$options = Hash.new(false) | |
$options[:digitsize] = 16 | |
$options[:cellsize] = 2**8 | |
$options[:wrap] = true | |
option_parser = OptionParser.new do |opts| | |
opts.banner = "Usage: bf.rb [$options] <bf_string_or_file>" | |
opts.on '-d', '--debug', 'Enable debug mode; prints memory dump on "#" instruction' do |v| | |
$options[:debug] = v | |
end | |
opts.on '-i', '--input=INPUT', '[DEBUG] Use given input string instead of STDIN' do |v| | |
abort("Input must be a string") unless v.is_a? String | |
$options[:input] = v.each_byte | |
end | |
opts.on '-t', '--[no-]timing', 'Measure compilation and execution time' do |v| | |
$options[:timing] = v | |
end | |
opts.on '--echo', 'Echo input file' do |v| | |
$options[:echo] = v | |
end | |
opts.on '--compiled', 'Echo generated Ruby code' do |v| | |
$options[:compiled] = v | |
end | |
#opts.on '-o', '--optimize', 'Use experimental optimizations' do |v| | |
# $options[:optimize] = v | |
# puts "Warning: The option --optimize is not yet supported\n" | |
#end | |
opts.on '--count', 'Count executed instructions' do |v| | |
$options[:count] = v | |
end | |
opts.on '--cellsize=SIZE', 'Cell size in bits; default 8' do |v| | |
abort("Cellsize must be integer, found '#{v}'") unless v.is_a? Integer | |
$options[:cellsize] = 2**(v.to_i) | |
end | |
opts.on '--[no-]wrap', 'Behavior on cell overflow: wrap or error. Default true (=wrap)' do |v| | |
$options[:wrap] = v | |
end | |
opts.on '--bfc', 'Use BFC (Layer 1) features' do |v| | |
$options[:bfc] = v | |
end | |
opts.on '--bfc-number-format=FORMAT', 'Number format for BFC quantifiers. Allowed values are d|h|o|b, default h (hex)' do |v| | |
$options[:digitsize] = case v | |
when 'h' then 16 | |
when 'd' then 10 | |
when 'o' then 8 | |
when 'b' then 2 | |
else abort("Invalid number format. Allowed values are d|h|o|b") | |
end | |
end | |
if ARGV.empty? | |
abort(opts.help) | |
end | |
end.parse! | |
begin | |
$ic = 0 if $options[:count] | |
t0 = Time.now if $options[:timing] | |
brainfuck = File.exists?(ARGV[0]) && File.file?(ARGV[0]) ? File.open(ARGV[0], 'r').read: ARGV[0] | |
puts brainfuck if $options[:echo] | |
$m = Hash.new($p=0) | |
$q = 0 | |
def q() | |
return $q==0 ? 1 : $q | |
end | |
def quantify(x) | |
return if !$options[:bfc] | |
$q = $q * $options[:digitsize] + x if x < $options[:digitsize] | |
end | |
def bound(x) | |
abort("Cell size overflow") if !$options[:wrap] && x > $options[:cellsize] | |
x % $options[:cellsize] | |
end | |
def incr(x=1) | |
$m[$p] = bound($m[$p] + x) | |
end | |
def decr(x=1) | |
incr(-x) | |
end | |
def left(x=1) | |
$p -= x | |
raise "Negative pointer index" if $p < 0 | |
end | |
def right(x=1) | |
$p += x | |
end | |
def read | |
if $options[:input].nil? | |
$m[$p] = STDIN.getbyte.ord || alert("strange input") | |
else | |
begin | |
$m[$p] = $options[:input].next | |
rescue | |
$m[$p] = 0 | |
end | |
end | |
end | |
def printc(x=1) | |
raise Exception.new(x) if x.class != Fixnum | |
x.times { putc($m[$p]) } | |
end | |
def debug() | |
puts "m=" + 0.upto($m.keys.max).map{|i| $m[i]}.to_s + "; p=#{$p};\n" if $options[:debug] | |
end | |
def inst(x) | |
$ic += x if $options[:count] | |
$q = 0 | |
end | |
def zero() | |
$m[$p] = 0 if $options[:bfc] | |
end | |
code = brainfuck.gsub(/./i, | |
'+' => 'incr(q);inst(q);', | |
'-' => 'decr(q);inst(q);', | |
'<' => 'left(q);inst(q);', | |
'>' => 'right(q);inst(q);', | |
'[' => '($q=0;', | |
']' => ')while(($m[$p])!=0);inst(q);', | |
'.' => 'printc(q);inst(q);', | |
',' => 'read;inst(q);', | |
'#' => 'debug;', | |
'_' => 'zero;', | |
'0' => 'quantify(0);', | |
'1' => 'quantify(1);', | |
'2' => 'quantify(2);', | |
'3' => 'quantify(3);', | |
'4' => 'quantify(4);', | |
'5' => 'quantify(5);', | |
'6' => 'quantify(6);', | |
'7' => 'quantify(7);', | |
'8' => 'quantify(8);', | |
'9' => 'quantify(9);', | |
'A' => 'quantify(10);', | |
'B' => 'quantify(11);', | |
'C' => 'quantify(12);', | |
'D' => 'quantify(13);', | |
'E' => 'quantify(14);', | |
'F' => 'quantify(15);' | |
) | |
puts code if $options[:compiled] | |
t1 = Time.now if $options[:timing] | |
print "\n" | |
print "output:\n" | |
eval code | |
print "\n" | |
t2 = Time.now if $options[:timing] | |
if $options[:timing] | |
puts "Time used:" | |
puts "Compilation: " + ((t1-t0)*1000).to_s + "ms" | |
puts "Execution: " + ((t2-t1)*1000).to_s + "ms" | |
end | |
puts "#{$ic} instructions" if $options[:count] | |
rescue SyntaxError => e | |
abort("Syntax Error - Are there unbalanced brackets?") | |
rescue Interrupt => e | |
abort("Interrupted") | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment