195 lines
4.6 KiB
Lua
Raw Permalink Normal View History

2020-04-06 23:31:41 +02:00
------------
-- Constants
------------
local max_instructions = 10000 -- to avoid infinite loops
local arr_size = 30000
-- also numbers are 0-255
------------------
-- Execution logic
------------------
local possible_instructions = {">", "<", "+", "-", ".", ",", "[", "]"}
for num, instr in ipairs(possible_instructions) do
possible_instructions[instr] = num
end
-- Parses brainfuck code to single instructions
-- returns an array of instructions
local function parse_brainfuck(code)
local instructions = {}
local instr_index = 1
local jmps, jmps_count = {}, 0
for i = 1, #code do
local char = string.sub(code, i, i)
local instr_num = possible_instructions[char]
if instr_num then
instructions[instr_index] = char
if char == "[" then
-- a jump forward
jmps_count = jmps_count + 1
jmps[jmps_count] = instr_index
elseif char == "]" then
-- a jump backward
local forward_index
if jmps_count > 0 then
forward_index = jmps[jmps_count]
instructions[forward_index] = "[" .. instr_index
jmps[jmps_count] = nil
jmps_count = jmps_count - 1
else
forward_index = 1 -- no opening brace, jump back to start
end
instructions[instr_index] = "]" .. forward_index
end
instr_index = instr_index + 1
end
end
-- all other forward jumps jump to the end
for i = jmps_count, 1, -1 do
instructions[jmps[i]] = instr_index
end
return instructions
end
local function error_message(operation, pc, msg)
-- (pc is already incremented)
return string.format("Error: \"%s\" failed at %i: %s", operation, pc - 1, msg)
end
2020-04-06 23:56:26 +02:00
local function execute_brainfuck(code, input, print_numbers, read_numbers)
2020-04-06 23:31:41 +02:00
-- parse
local instructions = parse_brainfuck(code)
local prog_end = #instructions
-- init run-time stuff
local arr = {0}
local ptr = 1 -- pointer to arr (internal; external it begins with 0)
local pc = 1 -- program counter
local output = ""
local input_ptr = 1
local input_length = #input
-- execute
for executed_instructions = 0, max_instructions do
if pc > prog_end then
-- program ended
return true, "Success: " .. output
end
local instr = instructions[pc]
pc = pc + 1
if instr == ">" then
if ptr > arr_size then
return false, error_message(">", pc, "max is " .. arr_size)
end
ptr = ptr + 1
arr[ptr] = arr[ptr] or 0
elseif instr == "<" then
if ptr <= 1 then
return false, error_message("<", pc, "min is 0")
end
ptr = ptr - 1
elseif instr == "+" then
arr[ptr] = (arr[ptr] + 1) % 256
elseif instr == "-" then
arr[ptr] = (arr[ptr] + 255) % 256
elseif instr == "." then
2020-04-06 23:56:26 +02:00
if print_numbers then
output = output .. arr[ptr] .. " "
else
output = output .. string.char(arr[ptr])
end
2020-04-06 23:31:41 +02:00
elseif instr == "," then
--~ if input_ptr > input_length then
--~ return false, error_message(",", pc, "input is too short")
--~ end
local num
if input_ptr > input_length then
num = 0
else
2020-04-06 23:56:26 +02:00
if read_numbers then
local next_space = input:find(" ", input_ptr, true) or (#input + 1)
num = tonumber(input:sub(input_ptr, next_space - 1)) or 0
input_ptr = next_space
else
num = string.byte(input, input_ptr)
end
2020-04-06 23:31:41 +02:00
end
if not (num >= 0 and num < 256) then
return false, error_message(",", pc, "input is not ASCII at " .. input_ptr)
end
input_ptr = input_ptr + 1
arr[ptr] = num
else -- "[1234" or "]1234"
if instr:sub(1, 1) == "[" then
-- jump over if 0
if arr[ptr] == 0 then
pc = tonumber(instr:sub(2))
end
else -- "]"
-- jump back if not 0
if arr[ptr] ~= 0 then
pc = tonumber(instr:sub(2))
end
end
end
end
return false, string.format("Error: Timed out at &i (\"%s\").", pc, instructions[pc])
end
---------------------------
-- Chatcommand registration
---------------------------
minetest.register_chatcommand("brainfuck", {
2020-04-06 23:56:26 +02:00
params = "[input=\"<input>\" ][print_numbers ][read_numbers ]<brainfuck code>",
2020-04-06 23:31:41 +02:00
description = "Executes given brainfuck code",
privs = {},
func = function(name, param)
2020-04-06 23:56:26 +02:00
local input = ""
2020-04-06 23:31:41 +02:00
if param:sub(1, 7) == "input=\"" then
local in_end = param:find("\" ", 8, false)
if not in_end then
return false, "Error: Could not find enclosing \" for input."
end
input = param:sub(8, in_end - 1)
2020-04-06 23:56:26 +02:00
param = param:sub(in_end + 2)
2020-04-06 23:31:41 +02:00
end
2020-04-06 23:56:26 +02:00
local print_numbers = false
if param:sub(1, 14) == "print_numbers " then
print_numbers = true
param = param:sub(15)
end
local read_numbers = false
if param:sub(1, 13) == "read_numbers " then
read_numbers = true
param = param:sub(14)
end
return execute_brainfuck(param, input, print_numbers, read_numbers)
2020-04-06 23:31:41 +02:00
end,
})