Ad

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