Skip to content

Instantly share code, notes, and snippets.

@5thWall
Created July 21, 2015 01:32
Show Gist options
  • Save 5thWall/f6327b2a14d63d4f29a7 to your computer and use it in GitHub Desktop.
Save 5thWall/f6327b2a14d63d4f29a7 to your computer and use it in GitHub Desktop.
Implementation of a Befunge interpreter
require 'tape'
class BefungeMachine
attr_reader :tape, :stack, :output
def initialize(code, debug = false)
@tape = Tape.new(code)
@stack = []
@output = []
@debug = debug
end
def run
cmd = tape.cmd
loop do
if @debug
puts "Command: (#{tape.x}, #{tape.y}) '#{cmd}'"
end
return output.join('') if cmd == :EOP
send(cmd)
cmd = tape.next_cmd
end
end
private
def noop; end
[:up, :down, :left, :right].each do |dir|
define_method("move_#{dir}") do
tape.dir = dir
end
end
def move_random
tape.dir = Tape::DIRS.sample
end
(0..9).each do |n|
define_method("push_#{n}") do
stack.push(n)
end
end
def a_b
[stack.pop, stack.pop]
end
def addition
a, b = a_b
stack.push(a+b)
end
def subtraction
a, b = a_b
stack.push(b-a)
end
def multiplication
a, b = a_b
stack.push(a*b)
end
def division
a, b = a_b
stack.push(a.zero? ? 0 : b / a)
end
def modulo
a, b = a_b
stack.push(a.zero? ? 0 : b % a)
end
def l_not
stack.push(stack.pop.zero? ? 1 : 0)
end
def greater_than
a, b = a_b
stack.push(b > a ? 1 : 0)
end
def pop_lr
stack.pop.zero? ? move_right : move_left
end
def pop_ud
stack.pop.zero? ? move_down : move_up
end
def duplicate
top = stack.last
top.nil? ? stack.push(0) : stack.push(top)
end
def swap
a, b = a_b
stack << a << (b||0)
end
def discard
stack.pop
end
def pop_int
output << stack.pop.to_s
end
def pop_char
output << stack.pop.chr
end
def trampoline
tape.next_cmd
end
def string_mode
while((cmd = tape.next_raw) != '"')
stack.push(cmd.getbyte(0))
end
end
def put
x, y = a_b
tape.put(x, y, stack.pop.chr)
end
def get
x, y = a_b
stack.push(tape.get(x, y).getbyte(0))
end
end
module Parser
INSTRUCTIONS = {
'+' => :addition,
'-' => :subtraction,
'*' => :multiplication,
'/' => :division,
'%' => :modulo,
'!' => :l_not,
'`' => :greater_than,
'>' => :move_right,
'<' => :move_left,
'^' => :move_up,
'v' => :move_down,
'?' => :move_random,
'_' => :pop_lr,
'|' => :pop_ud,
'"' => :string_mode,
':' => :duplicate,
'\\' => :swap,
'$' => :discard,
'.' => :pop_int,
',' => :pop_char,
'#' => :trampoline,
'p' => :put,
'g' => :get,
'@' => :EOP,
' ' => :noop
}
(0..9).each do |n|
INSTRUCTIONS[n.to_s] = "push_#{n}".to_sym
end
INSTRUCTIONS.freeze
def self.parse(code)
code.split("\n").map(&:chars)
end
def self.parse_cmd(cmd, no_fail = false)
INSTRUCTIONS.fetch(cmd) do |_|
fail "Could not parse command #{cmd}" unless no_fail
end
end
end
require 'parser'
class Tape
DIRS = [:up, :down, :left, :right].freeze
attr_reader :tape, :ptr, :dir, :x, :y
def initialize(code)
@tape = Parser.parse(code)
@x, @y = 0, 0
@dir = :right
end
def cmd
Parser.parse_cmd(raw)
rescue => e
throw "Failure at #{x}, #{y}: #{e.message}"
end
def raw
tape[x][y]
end
def next_cmd
send(dir)
cmd
end
def next_raw
send(dir)
raw
end
def dir=(dir)
fail ArgumentError, "#{dir} is not a valid direction" unless DIRS.include?(dir)
@dir = dir
end
def put(x, y, val)
tape[x][y] = val
end
def get(x, y)
tape[x][y]
end
private
def max_h
@max_h ||= (tape.length - 1)
end
def max_w
tape[x].length - 1
end
def up
@x -= 1
@x = max_h if x < 0
@y = max_w if y > max_w
end
def down
@x = (x + 1) % (max_h+1)
@y = max_w if y > max_w
end
def right
@y = (y + 1) % (max_w+1)
end
def left
@y -= 1
@y = max_w if y < 0
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment