Skip to content

Instantly share code, notes, and snippets.

@ohmree
Created November 1, 2019 23:29
Show Gist options
  • Save ohmree/2ee77e6a2133e14303ed68b950626cd0 to your computer and use it in GitHub Desktop.
Save ohmree/2ee77e6a2133e14303ed68b950626cd0 to your computer and use it in GitHub Desktop.
Problematic lines are 109-113
#!/usr/bin/env ruby
# frozen_string_literal: true
# A direction (y,x).
# The order follows the curses convention.
module Directions
RIGHT = { y: 0, x: 1 }.freeze
LEFT = { y: 0, x: -1 }.freeze
UP = { y: 1, x: 0 }.freeze
DOWN = { y: -1, x: 0 }.freeze
end
# These labels corrospond to method names on the `Interpreter` class
INSTRUCTIONS = {
' ' => :instruction_noop,
'0' => :instruction_0,
'1' => :instruction_1,
'2' => :instruction_2,
'3' => :instruction_3,
'4' => :instruction_4,
'5' => :instruction_5,
'6' => :instruction_6,
'7' => :instruction_7,
'8' => :instruction_8,
'9' => :instruction_9,
'"' => :instruction_start_string,
',' => :instruction_putc,
'>' => :instruction_right,
'<' => :instruction_left,
'v' => :instruction_down,
'^' => :instruction_up,
'@' => :instruction_quit,
'+' => :instruction_add,
'-' => :instruction_sub,
'*' => :instruction_mul,
'/' => :instruction_div
}.freeze
# The main befunge interpreter class.
class Interpreter
def initialize(code = nil)
# TODO: figure out a good starting value for the stack.
@stack = Array.new(500)
@direction = Directions::RIGHT
# If no code string given, initialize with an empty
# array of 25 lines and 80 columns.
@grid = if code
self.class.grid_from_string(code)
else
Array.new(25) { Array.new(80) { ' ' } }
end
@instruction_pointer = { y: 0, x: 0 }
# A shortcut for "the character at the instruction pointer".
@cursor = @grid[0][0]
end
# Move once in the direction `@direction`.
def move
# Wrap around if exceeding 25 y or 80 x.
# From my understanding this is the
# correct behavior in Befunge 93
# but not in Befunge 98.
y_result = @instruction_pointer[:y] + @direction[:y]
y_result = 0 if y_result >= 25
x_result = @instruction_pointer[:x] + @direction[:x]
x_result = 0 if x_result >= 80
@instruction_pointer = { y: y_result, x: x_result }
@cursor = @grid[y_result][x_result]
end
# The `s` parameter is the current instance.
# Might need to add more exit behavior later.
def quit
exit
end
def instruction_noop; end
# Here be some meta-fuckery.
# We define methods to push each number.
# Might be better to just handle numbers
# as an edge case in `step` but IDK.
'0'.upto('9') do |c|
define_method "instruction_#{c}".to_sym do
@stack.push(c.to_i)
end
end
def instruction_start_string
until @cursor == '"'
move
@stack.push(@cursor.ord)
end
end
def instruction_putc
putc @stack.pop
end
# Meta-fuckery pt. 2.
# These instructions change `@direction`.
Directions.constants.each do |name|
define_method "instruction_#{name.to_s.downcase}".to_sym do
@direction = Directions.const_get(name)
end
end
def instruction_quit
quit
end
def instruction_add
@stack.push(@stack.pop + @stack.pop)
end
def instruction_sub
a = @stack.pop
b = @stack.pop
@stack.push(b - a)
end
def instruction_mul
@stack.push(@stack.pop * @stack.pop)
end
def instruction_div
a = @stack.pop
b = @stack.pop
@stack.push(b / a)
end
# Step once through the program.
#
# If the thing at instruction pointer
# is a valid instruction, call it.
def step
instruction_name = INSTRUCTIONS.fetch(@cursor, :noop)
public_send(instruction_name, self)
move
rescue NoMethodError
abort <<~ERROR
Instructions table refers to instruction #{instruction_name},
but it doesn't exist in class #{self.class}.
Aborting
ERROR
end
def print
@grid.each do |ary|
puts ary.join
end
puts
puts "stack: #{@stack.join(' ')}"
end
attr_reader :direction, :grid, :stack, :cursor, :instruction_pointer
class << self
attr_reader :instructions
# Split a string of befunge code into a 2d array for `@grid`.
# TODO: pad with spaces so that lines = 25, cols = 80.
# (this is part of the befunge specification)
def grid_from_string(str)
str.split("\n").map(&:chars)
end
end
private
# We don't want the outside world
# to be able to modify the direction.
attr_writer :direction
end
code = <<~CODE.lstrip
> v
v ,,,,,"Hello"<
>48*, v
v,,,,,,"World!"<
>25*,@
CODE
# Example usage:
i = Interpreter.new(code)
i.print
i.step
i.print
# This is the error I'm getting when I try to run the program
$ ./befunge.rb
> v
v ,,,,,"Hello"<
>48*, v
v,,,,,,"World!"<
>25*,@
stack:
Traceback (most recent call last):
3: from ./befunge.rb:194:in `<main>'
2: from ./befunge.rb:144:in `step'
1: from ./befunge.rb:144:in `public_send'
./befunge.rb:109:in `block (2 levels) in <class:Interpreter>': wrong number of arguments (given 1, expected 0) (ArgumentError)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment