Mindbend is BF with extra steps.
What if BF had named registers? More operations? What if we fused that with assembly?
This is Mindbend, a silly little interpreter I made in an hour or two killing time on a Friday at school.
Basic rundown
There are 26 registers, a - z.
Each register is a 32-bit integer.
Commands
Keep in mind this notation:
- R is a register
- N is a number
- M is either
- C is a condition
-
*
represents an unfinished instruction
Here's the arithmetic:
-
!RM
moves M into R -
+RM
is R += M (addition) -
-RM
is R -= M (subtraction) -
*RM
is R *= M (multiplication) -
/RM
is R /= M (integer division) -
%RM
is R %= M (modulo)
Handling I/O:
-
.R
print out the value of R as a number -
;R
print out the value of R as a character (unicode codepoint) -
,R
accept one input character into R -
#R
read the input as a number, consuming the most amount of characters that creates a proper number -
_
take and discard an input character -
'M
seek to M in the input -
|R
store the length of the input in R
Conditions:
-
M=M
if M == M -
M!M
if M != M -
M>M
if M > M -
M<M
if M < M
Control structures:
-
iC[
...]
if C is true, then do what's inside, otherwise skip past]
-
wC[
...]
while C is true, do what's inside, otherwise skip past]
Examples
#a*a2.a
Read a number from input, double, and output
#a_#b+ab.a
Read two numbers delimited by a non-number character, add, and output
#bwa<b[.a+a1]
Read a number from the input and output every number from 0 to 1 less than it, e.g. if you pass in 10, the output will be 0123456789
An example with a breakdown
Here's the last example with a breakdown, using comments (:
):
#b : Take a number from the input
wa<b[ : While a < b
.a : Add a to output
+a1 : Add 1 to a
] : End of while block
module Mindbend
class Parser
getter registers : Hash(Char, Int32)
getter program : String = ""
getter position : Int32 = 0
getter input : String = ""
getter output : String = ""
getter input_position = 0
getter loop_pairs = [] of Tuple(Int32, Int32)
def reset
@program = ""
@position = 0
@input = ""
@input_position = 0
@output = ""
@loop_pairs = [] of Tuple(Int32, Int32)
('a'..'z').each { |char| @registers[char] = 0 }
end
def initialize()
@registers = {} of Char => Int32
end
def load_program(program)
reset
@program = program
end
def quick_run(program, input)
reset
@program = program
run(input)
@output
end
def run(input)
@input = input
while @position < @program.size
read_instruction
end
end
def read_char
char = @program[@position]?
@position += 1
char
end
def read_char!
read_char.not_nil!
end
def read_register
char = read_char!
raise Exception.new("#{char} is not a register") unless ('a'..'z').includes? char
char
end
def read_number
number_string = ""
loop do
char = read_char!
if number_string == "" && char == '-'
number_string += '-'
elsif ('0'..'9').includes? char
number_string += char
else
@position -= 1
break
end
end
raise Exception.new("Invalid number") if number_string == ""
number_string.to_i32
end
def read_number_from_input
number_string = ""
loop do
char = @input[@input_position]?
@input_position += 1
if number_string == "" && char == '-'
number_string += '-'
elsif char.nil?
break
elsif ('0'..'9').includes? char
number_string += char
else
@input_position -= 1
break
end
end
raise Exception.new("Invalid number from input") if number_string == ""
number_string.to_i32
end
def read_register_or_number
begin
return read_register
rescue
@position -= 1
return read_number
end
raise Exception.new("Invalid register/number")
end
def read_register_or_number_value : Int32
target = read_register_or_number
return target if target.is_a? Int32
return @registers[target] if target.is_a? Char
raise Exception.new("Invalid register/number")
end
def read_condition : Bool
a = read_register_or_number_value
operator = read_char!
raise Exception.new("Invalid conditional operator") unless ['=', '!', '>', '<'].includes? operator
b = read_register_or_number_value
case operator
when '='
return a == b
when '!'
return a != b
when '>'
return a > b
when '<'
return a < b
end
false
end
def read_open_block
raise Exception.new("Invalid block opening") unless read_char! == '['
end
def seek_close_block
level = 0
start = @position
end_position = -1
loop do
char = read_char
case char
when '['
level += 1
when ']'
if level > 0
level -= 1
else
end_position = @position
break
end
end
raise Exception.new("Block never closed") if @position > @program.size
end
@position = start
end_position
end
def read_instruction
case c = read_char
when '!'
# move instruction
register = read_register
from = read_register_or_number_value
@registers[register] = from
when '+'
# add instruction
register = read_register
operand = read_register_or_number_value
@registers[register] += operand
when '-'
# subtract instruction
register = read_register
operand = read_register_or_number_value
@registers[register] -= operand
when '*'
register = read_register
operand = read_register_or_number_value
@registers[register] *= operand
when '/'
register = read_register
operand = read_register_or_number_value
@registers[register] //= operand
when '%'
register = read_register
operand = read_register_or_number_value
@registers[register] = @registers[register] % operand
when ','
register = read_register
value = @input[@input_position]? || '0'
@registers[register] = value.to_i32? || value.ord
@input_position += 1
when '#'
register = read_register
value = read_number_from_input
@registers[register] = value
when '.'
register = read_register
@output += @registers[register].to_s
when ';'
register = read_register
@output += @registers[register].chr
when '\''
@input_position = read_register_or_number_value
when 'i'
condition = read_condition
read_open_block
unless condition
# skip to the next matching close block
@position = seek_close_block
end
when 'w'
start = @position - 1
condition = read_condition
read_open_block
end_pos = seek_close_block
if condition
loop_pairs << {end_pos, start}
else
@position = end_pos
end
when ']'
if lp = loop_pairs.find { |lp| lp[0] == @position }
@position = lp[1]
loop_pairs.delete lp
end
when '|'
@registers[read_register] = @input.size
when '_'
@input_position += 1
when ':'
until read_char == '\n'
end
when nil
# program is over
else
# ignore invalid instruction
end
end
end
end
# Spec example:
# TODO: replace with your own tests (TDD), these are just how-to examples.
parser = Mindbend::Parser.new
describe "Solution" do
it "succeeds on examples" do
parser.quick_run("#a*a2.a", "2").should eq "4"
parser.quick_run("#a_#b+ab.a", "1+2").should eq "3"
parser.quick_run("#bwa<b[.a+a1]", "10").should eq "0123456789"
end
end