Skip to content

Instantly share code, notes, and snippets.

@Sinthorion
Last active September 28, 2020 08:13
Show Gist options
  • Save Sinthorion/d7587a54680e7a6741043016982ce8c6 to your computer and use it in GitHub Desktop.
Save Sinthorion/d7587a54680e7a6741043016982ce8c6 to your computer and use it in GitHub Desktop.
Simple Ruby Brainfuck Runner
#!/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
#!/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