1229 lines
33 KiB
Lua
1229 lines
33 KiB
Lua
require('base')
|
|
|
|
-- Bullshit
|
|
sigs = {
|
|
virtual_signal = {},
|
|
item = {},
|
|
fluid = {},
|
|
}
|
|
|
|
for sig_name, sig_arr in pairs(sigs) do
|
|
for name, prototype in pairs(prototypes[sig_name]) do
|
|
if not prototype.parameter then
|
|
table.insert(sig_arr, name)
|
|
end
|
|
end
|
|
end
|
|
-- Bullshit
|
|
|
|
local F_TRUE = -1
|
|
local F_FALSE = 0
|
|
|
|
local F_IMMED = 0x80
|
|
local F_HIDDEN = 0x20
|
|
local F_LENMASK = 0x1F
|
|
|
|
local cw_index = 0
|
|
local codewords = {}
|
|
local codeword_names = {}
|
|
local labels = {}
|
|
local init_context = {
|
|
memspace = {},
|
|
xp = 0,
|
|
sp = 0x1000,
|
|
rp = 0x1100,
|
|
last_xt = 0,
|
|
keybuf = {},
|
|
outbuf = {},
|
|
yield = false,
|
|
waiting_key = false,
|
|
sleep_ticks = 0,
|
|
bs = '',
|
|
valid = false,
|
|
}
|
|
|
|
function copy_context(ctx)
|
|
local new_ctx = {
|
|
xp = ctx.xp,
|
|
sp = ctx.sp,
|
|
rp = ctx.rp,
|
|
last_xt = ctx.last_xt,
|
|
yield = ctx.yield,
|
|
waiting_key = ctx.waiting_key,
|
|
sleep_ticks = ctx.sleep_ticks,
|
|
bs = ctx.bs,
|
|
keybuf = {},
|
|
outbuf = {},
|
|
memspace = {},
|
|
}
|
|
for i, v in pairs(ctx.keybuf) do new_ctx.keybuf[i] = v end
|
|
for i, v in pairs(ctx.outbuf) do new_ctx.outbuf[i] = v end
|
|
for i, v in pairs(ctx.memspace) do new_ctx.memspace[i] = v end
|
|
|
|
return new_ctx
|
|
end
|
|
|
|
dbg_print = function(...)
|
|
--print(...)
|
|
-- local str = ''
|
|
-- for i, v in pairs({...}) do
|
|
-- str = str..tostring(v)..' '
|
|
-- end
|
|
-- log(str)
|
|
-- game.print(str)
|
|
end
|
|
|
|
dbg_interp = dbg_print
|
|
|
|
local function output_string(context, str)
|
|
for c in str:gmatch('.') do
|
|
table.insert(context.outbuf, c)
|
|
end
|
|
end
|
|
|
|
local function read(context, addr)
|
|
local res = context.memspace[addr]
|
|
if res == nil then
|
|
error('Read from invalid address')
|
|
end
|
|
return res
|
|
end
|
|
|
|
local function write(context, addr, data)
|
|
context.memspace[addr] = data
|
|
end
|
|
|
|
local function push_rsp(context, data)
|
|
dbg_print('push rsp ', data)
|
|
context.rp = context.rp - 1
|
|
write(context, context.rp, data)
|
|
end
|
|
|
|
local function pop_rsp(context)
|
|
local data = read(context, context.rp)
|
|
context.rp = context.rp + 1
|
|
dbg_print('pop rsp ', data)
|
|
return data
|
|
end
|
|
|
|
local function push(context, data)
|
|
dbg_print('push ', data)
|
|
context.sp = context.sp - 1
|
|
write(context, context.sp, data)
|
|
end
|
|
|
|
local function pop(context)
|
|
local data = read(context, context.sp)
|
|
if data == nil then
|
|
error('Pop from outside stack')
|
|
end
|
|
context.sp = context.sp + 1
|
|
dbg_print('pop ', data)
|
|
return data
|
|
end
|
|
|
|
local function peek(context, off)
|
|
return read(context, context.sp + off)
|
|
end
|
|
|
|
local function do_codeword(context, xt)
|
|
-- Save xt so we can resume from a yield
|
|
context.last_xt = xt
|
|
local codeword = read(context, xt)
|
|
dbg_print('XT', xt, labels[xt])
|
|
dbg_print('CW', codeword, codeword_names[codeword])
|
|
|
|
-- Reinit codewords when loading a save
|
|
if #codewords == 0 then
|
|
forth_initialize(context.entity)
|
|
end
|
|
|
|
codewords[codeword](context)
|
|
end
|
|
|
|
local function forth_next(context)
|
|
local xt = nil
|
|
if context.yield then
|
|
xt = context.last_xt
|
|
context.yield = false
|
|
else
|
|
local xp = context.xp
|
|
xt = read(context, xp)
|
|
dbg_print('XP', xp)
|
|
context.xp = context.xp + 1
|
|
end
|
|
|
|
do_codeword(context, xt)
|
|
end
|
|
|
|
function forth_tick(context)
|
|
if context.sleep_ticks > 0 then
|
|
context.sleep_ticks = context.sleep_ticks - 1
|
|
return true
|
|
elseif context.yield and context.waiting_key and #context.keybuf == 0 then
|
|
return true
|
|
else
|
|
forth_next(context)
|
|
return false
|
|
end
|
|
end
|
|
|
|
function feed_key(context, key)
|
|
table.insert(context.keybuf, key)
|
|
end
|
|
|
|
function feed_file(context, file)
|
|
while true do
|
|
local c = file:read(1)
|
|
if c == nil then break end
|
|
feed_key(context, string.byte(c))
|
|
end
|
|
end
|
|
|
|
function forth_initialize(entity)
|
|
-- init_context.valid = false
|
|
if init_context.valid then
|
|
local new_ctx = copy_context(init_context)
|
|
new_ctx.entity = entity
|
|
return new_ctx
|
|
end
|
|
|
|
local link = 0
|
|
local init_here = 0x10
|
|
|
|
local function init_comma(data)
|
|
write(init_context, init_here, data)
|
|
init_here = init_here + 1
|
|
end
|
|
|
|
local function init_comma_string(str)
|
|
for i=1, #str do
|
|
init_comma(string.byte(str, i, i))
|
|
end
|
|
end
|
|
|
|
local function defcode(name, flags, callback)
|
|
-- log(tostring(init_here)..' '..name)
|
|
init_comma(link)
|
|
link = init_here - 1
|
|
local lenflag = flags + #name
|
|
init_comma(lenflag)
|
|
init_comma_string(name)
|
|
local xt = init_here
|
|
|
|
codewords[cw_index] = callback
|
|
codeword_names[cw_index] = name
|
|
init_comma(cw_index) -- codeword
|
|
init_comma(0) -- doesword
|
|
cw_index = cw_index + 1
|
|
|
|
labels[xt] = name
|
|
|
|
return xt
|
|
end
|
|
|
|
local DOCOL = defcode('DOCOL', 0, function(context)
|
|
-- XP point to the XT to return to, push that
|
|
push_rsp(context, context.xp)
|
|
|
|
-- Skip past the codeword and doesword
|
|
context.xp = context.last_xt + 2
|
|
-- XP now points to first XT of the called word
|
|
end)
|
|
local __DOCOL = read(init_context, DOCOL)
|
|
|
|
local DOCREATE = defcode('DOCREATE', 0, function(context)
|
|
-- Push address of data field (after codeword and doesword)
|
|
push(context, context.last_xt + 2)
|
|
-- log('docreate '..tonumber(context.last_xt + 2)..' = '..read(context, context.last_xt + 2))
|
|
end)
|
|
local __DOCREATE = read(init_context, DOCREATE)
|
|
|
|
local DODOES = defcode('DODOES', 0, function(context)
|
|
-- XP point to the XT to return to, push that
|
|
push_rsp(context, context.xp)
|
|
|
|
-- Push address of data field (after codeword and doesword)
|
|
push(context, context.last_xt + 2)
|
|
-- Skip past the codeword and read doesword
|
|
context.xp = read(context, context.last_xt + 1)
|
|
-- XP now points to first XT of the DOES> section
|
|
end)
|
|
local __DODOES = read(init_context, DODOES)
|
|
|
|
local function defword(name, flags, xts)
|
|
-- log(tostring(init_here)..' '..name)
|
|
init_comma(link)
|
|
link = init_here - 1
|
|
local lenflag = flags + #name
|
|
init_comma(lenflag)
|
|
init_comma_string(name)
|
|
local xt = init_here
|
|
|
|
init_comma(__DOCOL) -- codeword
|
|
init_comma(0) -- doesword
|
|
for i=1, #xts do
|
|
init_comma(xts[i])
|
|
end
|
|
|
|
labels[xt] = name
|
|
|
|
return xt
|
|
end
|
|
|
|
local function defvar(name, flags, value)
|
|
-- log(tostring(init_here)..' '..name)
|
|
init_comma(link)
|
|
link = init_here - 1
|
|
local lenflag = flags + #name
|
|
init_comma(lenflag)
|
|
init_comma_string(name)
|
|
local xt = init_here
|
|
|
|
init_comma(__DOCREATE) -- codeword
|
|
init_comma(0) -- doesword
|
|
init_comma(value) -- value on param list
|
|
|
|
labels[xt] = name
|
|
|
|
return xt
|
|
end
|
|
|
|
local function readvar(context, xt)
|
|
-- Skip over codeword, variable is stored after it
|
|
-- log('readvar @ '..tonumber(xt + 2)..': '..read(context, xt + 2))
|
|
return read(context, xt + 2)
|
|
end
|
|
|
|
local function writevar(context, xt, data)
|
|
-- Skip over codeword, variable is stored after it
|
|
-- log('writevar @ '..tonumber(xt + 2)..': '..data)
|
|
return write(context, xt + 2, data)
|
|
end
|
|
|
|
local function defconst(name, flags, value)
|
|
local entry = defcode(name, flags, function(context)
|
|
push(context, value)
|
|
end)
|
|
|
|
return entry
|
|
end
|
|
|
|
local DROP = defcode('DROP', 0, function(context)
|
|
pop(context)
|
|
end)
|
|
|
|
local SWAP = defcode('SWAP', 0, function(context)
|
|
local a = pop(context)
|
|
local b = pop(context)
|
|
push(context, a)
|
|
push(context, b)
|
|
-- print(b, a, '->', a, b)
|
|
end)
|
|
|
|
local DUP = defcode('DUP', 0, function(context)
|
|
local a = peek(context, 0)
|
|
push(context, a)
|
|
end)
|
|
|
|
local OVER = defcode('OVER', 0, function(context)
|
|
local a = peek(context, 1)
|
|
push(context, a)
|
|
end)
|
|
|
|
local ROT = defcode('ROT', 0, function(context)
|
|
local c = pop(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, b)
|
|
push(context, c)
|
|
push(context, a)
|
|
end)
|
|
|
|
local NROT = defcode('-ROT', 0, function(context)
|
|
local c = pop(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, c)
|
|
push(context, a)
|
|
push(context, b)
|
|
end)
|
|
|
|
local TWODROP = defcode('2DROP', 0, function(context)
|
|
pop(context)
|
|
pop(context)
|
|
end)
|
|
|
|
local TWODUP = defcode('2DUP', 0, function(context)
|
|
push(context, peek(context, 1))
|
|
push(context, peek(context, 1))
|
|
end)
|
|
|
|
local TWOSWAP = defcode('2SWAP', 0, function(context)
|
|
local a1 = pop(context)
|
|
local a2 = pop(context)
|
|
local b1 = pop(context)
|
|
local b2 = pop(context)
|
|
push(context, a1)
|
|
push(context, a2)
|
|
push(context, b1)
|
|
push(context, b2)
|
|
end)
|
|
|
|
local QDUP = defcode('?DUP', 0, function(context)
|
|
local a = peek(context, 0)
|
|
if a ~= 0 then
|
|
push(context, a)
|
|
end
|
|
end)
|
|
|
|
local INCR = defcode('1+', 0, function(context)
|
|
local a = pop(context)
|
|
push(context, a + 1)
|
|
end)
|
|
|
|
local DECR = defcode('1-', 0, function(context)
|
|
local a = pop(context)
|
|
push(context, a - 1)
|
|
end)
|
|
|
|
local CELLPLUS = defcode('CELL+', 0, function(context)
|
|
local a = pop(context)
|
|
push(context, a + 1)
|
|
end)
|
|
|
|
local CELLMINUS = defcode('CELL-', 0, function(context)
|
|
local a = pop(context)
|
|
push(context, a - 1)
|
|
end)
|
|
|
|
local ADD = defcode('+', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, a + b)
|
|
end)
|
|
|
|
local SUB = defcode('-', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
-- print(a, '-', b, '=', a-b)
|
|
push(context, a - b)
|
|
end)
|
|
|
|
local MUL = defcode('*', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, a * b)
|
|
end)
|
|
|
|
local UMDIVMOD = defcode('UM/MOD', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
-- We do some cope
|
|
a = bit32.band(a, 0x7FFFFFFF)
|
|
b = bit32.band(b, 0x7FFFFFFF)
|
|
push(context, a % b)
|
|
push(context, math.floor(a / b))
|
|
end)
|
|
|
|
local DIVMOD = defcode('/MOD', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, a % b)
|
|
push(context, math.floor(a / b))
|
|
end)
|
|
|
|
local EQU = defcode('=', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
if a == b then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local NEQU = defcode('<>', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
if a ~= b then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local LT = defcode('<', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
if a < b then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local GT = defcode('>', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
if a > b then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local LE = defcode('<=', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
if a <= b then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local GE = defcode('>=', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
if a >= b then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local ZEQU = defcode('0=', 0, function(context)
|
|
local a = pop(context)
|
|
if a == 0 then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local ZNEQU = defcode('0<>', 0, function(context)
|
|
local a = pop(context)
|
|
if a ~= 0 then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local ZLT = defcode('0<', 0, function(context)
|
|
local a = pop(context)
|
|
if a < 0 then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local ZGT = defcode('0>', 0, function(context)
|
|
local a = pop(context)
|
|
if a > 0 then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local ZLE = defcode('0<=', 0, function(context)
|
|
local a = pop(context)
|
|
if a <= 0 then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local ZGE = defcode('0>=', 0, function(context)
|
|
local a = pop(context)
|
|
if a >= 0 then
|
|
push(context, F_TRUE)
|
|
else
|
|
push(context, F_FALSE)
|
|
end
|
|
end)
|
|
|
|
local AND = defcode('AND', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, bit32.band(a, b))
|
|
end)
|
|
|
|
local OR = defcode('OR', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, bit32.bor(a, b))
|
|
end)
|
|
|
|
local XOR = defcode('XOR', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, bit32.bxor(a, b))
|
|
end)
|
|
|
|
local LSL = defcode('LSL', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, bit32.lshift(a, b))
|
|
end)
|
|
|
|
local LSR = defcode('LSR', 0, function(context)
|
|
local b = pop(context)
|
|
local a = pop(context)
|
|
push(context, bit32.rshift(a, b))
|
|
end)
|
|
|
|
local INVERT = defcode('INVERT', 0, function(context)
|
|
local a = pop(context)
|
|
push(context, bit32.bnot(a))
|
|
end)
|
|
|
|
local NEGATE = defcode('NEGATE', 0, function(context)
|
|
local a = pop(context)
|
|
push(context, -a)
|
|
end)
|
|
|
|
local EXIT = defcode('EXIT', 0, function(context)
|
|
context.xp = pop_rsp(context)
|
|
end)
|
|
|
|
local LIT = defcode('LIT', 0, function(context)
|
|
local a = read(context, context.xp)
|
|
context.xp = context.xp + 1
|
|
push(context, a)
|
|
end)
|
|
|
|
local STORE = defcode('!', 0, function(context)
|
|
local addr = pop(context)
|
|
local data = pop(context)
|
|
write(context, addr, data)
|
|
-- print(data, addr, '!', read(context, addr))
|
|
end)
|
|
|
|
local FETCH = defcode('@', 0, function(context)
|
|
local addr = pop(context)
|
|
-- print(addr, '@', read(context, addr))
|
|
push(context, read(context, addr))
|
|
end)
|
|
|
|
local ADDSTORE = defcode('+!', 0, function(context)
|
|
local addr = pop(context)
|
|
local amnt = pop(context)
|
|
write(context, addr, read(context, addr) + amnt)
|
|
end)
|
|
|
|
local SUBSTORE = defcode('-!', 0, function(context)
|
|
local addr = pop(context)
|
|
local amnt = pop(context)
|
|
write(context, addr, read(context, addr) - amnt)
|
|
end)
|
|
|
|
local CMOVE = defcode('CMOVE', 0, function(context)
|
|
local len = pop(context)
|
|
local dst = pop(context)
|
|
local src = pop(context)
|
|
|
|
for i=1,len do
|
|
write(context, dst + i - 1, read(context, src + i - 1))
|
|
end
|
|
end)
|
|
|
|
local STATE = defvar('STATE', 0, 0)
|
|
local HERE = defvar('HERE', 0, 0)
|
|
local LATEST = defvar('LATEST', 0, 0)
|
|
local S0 = defvar('S0', 0, init_context.sp)
|
|
local BASE = defvar('BASE', 0, 10)
|
|
|
|
local R0 = defconst('R0', 0, init_context.rp)
|
|
local __F_FALSE = defconst('FALSE', 0, F_FALSE)
|
|
local __F_TRUE = defconst('TRUE', 0, F_TRUE)
|
|
local __F_IMMED = defconst('F_IMMED', 0, F_IMMED)
|
|
local __F_HIDDEN = defconst('F_HIDDEN', 0, F_HIDDEN)
|
|
local __F_LENMASK = defconst('F_LENMASK', 0, F_LENMASK)
|
|
|
|
local TOR = defcode('>R', 0, function(context)
|
|
local a = pop(context)
|
|
push_rsp(context, a)
|
|
end)
|
|
|
|
local FROMR = defcode('R>', 0, function(context)
|
|
local a = pop_rsp(context)
|
|
push(context, a)
|
|
end)
|
|
|
|
local RFETCH = defcode('R@', 0, function(context)
|
|
local data = read(context, context.rp)
|
|
push(context, data)
|
|
end)
|
|
|
|
local RSTORE = defcode('R!', 0, function(context)
|
|
local data = pop(context)
|
|
write(context, context.rp, data)
|
|
end)
|
|
|
|
local RSPFETCH = defcode('RSP@', 0, function(context)
|
|
push(context, context.rp)
|
|
end)
|
|
|
|
local RSPSTORE = defcode('RSP!', 0, function(context)
|
|
context.rp = pop(context)
|
|
end)
|
|
|
|
local RDROP = defcode('RDROP', 0, function(context)
|
|
pop_rsp(context)
|
|
end)
|
|
|
|
local DSPFETCH = defcode('DSP@', 0, function(context)
|
|
push(context, context.sp)
|
|
end)
|
|
|
|
local DSPSTORE = defcode('DSP!', 0, function(context)
|
|
context.sp = pop(context)
|
|
end)
|
|
|
|
local function _key(context)
|
|
if #context.keybuf == 0 then
|
|
context.waiting_key = true
|
|
context.yield = true
|
|
return nil
|
|
end
|
|
context.waiting_key = false
|
|
return table.remove(context.keybuf, 1)
|
|
end
|
|
|
|
local KEY = defcode('KEY', 0, function(context)
|
|
local key = _key(context)
|
|
if key ~= nil then
|
|
context.bs = context.bs..string.char(key)
|
|
if key == 10 then
|
|
dbg_print('enter is here '..context.bs)
|
|
context.bs = ''
|
|
end
|
|
push(context, key)
|
|
end
|
|
end)
|
|
|
|
local EMIT = defcode('EMIT', 0, function(context)
|
|
table.insert(context.outbuf, string.char(pop(context)))
|
|
end)
|
|
|
|
local BRANCH = defcode('BRANCH', 0, function(context)
|
|
context.xp = context.xp + read(context, context.xp)
|
|
end)
|
|
|
|
local ZBRANCH = defcode('0BRANCH', 0, function(context)
|
|
local data = pop(context)
|
|
if data == 0 then
|
|
context.xp = context.xp + read(context, context.xp)
|
|
else
|
|
context.xp = context.xp + 1
|
|
end
|
|
end)
|
|
|
|
local function _comma(context, data)
|
|
local here = readvar(context, HERE)
|
|
write(context, here, data)
|
|
here = here + 1
|
|
writevar(context, HERE, here)
|
|
end
|
|
|
|
local COMMA = defcode(',', 0, function(context)
|
|
local data = pop(context)
|
|
_comma(context, data)
|
|
end)
|
|
|
|
local WORD = defword('WORD', 0, { -- ( delim -- paddr)
|
|
HERE, FETCH, TOR, -- ( delim ) ( r: start )
|
|
LIT, 0, COMMA, -- Store temp length
|
|
TOR, -- ( ) ( r: start delim )
|
|
-- Skip leading delimiters
|
|
KEY, -- ( key ) ( r: start delim )
|
|
DUP, RFETCH, EQU, -- ( key delim ) ( r: start delim )
|
|
ZBRANCH, 4, -- ( key ) ( r: start delim )
|
|
DROP, -- ( ) ( r: start delim )
|
|
BRANCH, -8, -- Branch till no more delim's
|
|
-- Check for /n or delim
|
|
DUP, RFETCH, EQU, -- ( key not_delim ) Check for delim
|
|
OVER, LIT, 10, EQU, -- ( key not_delim not_nl ) Check for \n
|
|
OR, ZBRANCH, 15, -- ( key ) ( r: start delim ) If delim or space then
|
|
FROMR, TWODROP, -- ( ) ( r: start )
|
|
RFETCH, -- ( start ) ( r: start )
|
|
HERE, FETCH, FROMR, SUB, DECR, -- ( start len ) ( r: )
|
|
OVER, STORE, -- ( start ) ( r: ) - Store length
|
|
DUP, HERE, STORE, -- Restore HERE
|
|
EXIT,
|
|
-- Store key and grab the next one
|
|
COMMA, -- Store key ( ) ( r: start delim )
|
|
KEY, -- ( key ) ( r: start delim)
|
|
BRANCH, -27, -- Branch till invalid key (space, \n, etc)
|
|
})
|
|
|
|
local function _number(context, accum, addr, len)
|
|
local str = ''
|
|
for i=1, len do
|
|
str = str..string.char(read(context, addr + i - 1))
|
|
end
|
|
|
|
local start = 1
|
|
if string.sub(str, 1, 1) == '-' then start = 2 end
|
|
|
|
local result = accum
|
|
local last = 0
|
|
for i=start, len do
|
|
local tmp = tonumber(string.sub(str, 1, i), readvar(context, BASE))
|
|
if tmp == nil then break end
|
|
result = tmp
|
|
last = i
|
|
end
|
|
|
|
return result, addr + last, len - last
|
|
end
|
|
|
|
local NUMBER = defcode('>NUMBER', 0, function(context)
|
|
local len = pop(context)
|
|
local addr = pop(context)
|
|
local accum = pop(context)
|
|
local num, rem_addr, remaining = _number(context, accum, addr, len)
|
|
push(context, num)
|
|
push(context, rem_addr)
|
|
push(context, remaining)
|
|
end)
|
|
|
|
local function _find(context, paddr)
|
|
local len = read(context, paddr)
|
|
local addr = paddr + 1
|
|
local current = readvar(context, LATEST)
|
|
|
|
while true do
|
|
if current == 0 then
|
|
return 0 end
|
|
|
|
local cur_len = bit32.band(read(context, current + 1), bit32.bor(F_HIDDEN, F_LENMASK))
|
|
-- game.print('current='..tostring(current)..' cur_len='..tostring(cur_len)..' len='..tostring(len))
|
|
local a = ''
|
|
local b = ''
|
|
if cur_len == len then
|
|
local match = true
|
|
for i=1, len do
|
|
-- +2 to skip link/flags, -1 because lua | addr is just text so only -1 because lua
|
|
a = a..string.char(read(context, current + i + 1))
|
|
b = b..string.char(read(context, addr + i - 1))
|
|
if read(context, current + i + 1) ~= read(context, addr + i - 1) then
|
|
match = false
|
|
break
|
|
end
|
|
end
|
|
if match then
|
|
return current
|
|
end
|
|
end
|
|
current = read(context, current)
|
|
end
|
|
end
|
|
|
|
local FIND = defcode('FIND', 0, function(context)
|
|
local paddr = pop(context)
|
|
push(context, _find(context, paddr))
|
|
end)
|
|
|
|
local function _tcfa(context, addr)
|
|
local len = bit32.band(read(context, addr+1), F_LENMASK)
|
|
return addr + len + 2
|
|
end
|
|
|
|
-- ( entry -- cfa)
|
|
local TCFA = defcode('>CFA', 0, function(context)
|
|
local addr = pop(context)
|
|
push(context, _tcfa(context, addr))
|
|
end)
|
|
|
|
-- ( entry -- does)
|
|
local TDOES = defcode('>DOES', 0, function(context)
|
|
local addr = pop(context)
|
|
push(context, _tcfa(context, addr) + 1)
|
|
end)
|
|
|
|
-- ( entry -- dfa)
|
|
local TDFA = defcode('>DFA', 0, function(context)
|
|
local addr = pop(context)
|
|
push(context, _tcfa(context, addr) + 2)
|
|
end)
|
|
|
|
-- ( xt -- dfa)
|
|
local TBODY = defcode('>BODY', 0, function(context)
|
|
local xt = pop(context)
|
|
push(context, xt + 2)
|
|
end)
|
|
|
|
local ALLOT = defword('ALLOT', 0, { -- ( n -- )
|
|
HERE, ADDSTORE, -- ( )
|
|
EXIT
|
|
})
|
|
|
|
-- Our cell size is 1 so do nothing /shrug
|
|
local CELLS = defword('CELLS', 0, {
|
|
EXIT
|
|
})
|
|
|
|
local COUNT = defword('COUNT', 0, {
|
|
DUP, FETCH, SWAP, INCR, SWAP,
|
|
EXIT
|
|
})
|
|
|
|
local BL = defword('BL', 0, {
|
|
LIT, string.byte(' '),
|
|
EXIT
|
|
})
|
|
|
|
local LBRAC = defcode('[', F_IMMED, function(context)
|
|
-- print('SET STATE EXEC')
|
|
writevar(context, STATE, 0)
|
|
-- print('STATE ', readvar(context, STATE))
|
|
end)
|
|
|
|
local RBRAC = defcode(']', 0, function(context)
|
|
-- print('SET STATE COMP')
|
|
writevar(context, STATE, 1)
|
|
-- print('STATE ', readvar(context, STATE))
|
|
end)
|
|
|
|
local IMMEDIATE = defcode('IMMEDIATE', F_IMMED, function(context)
|
|
local latest = readvar(context, LATEST)
|
|
|
|
local flags = read(context, latest + 1)
|
|
flags = bit32.bxor(flags, F_IMMED)
|
|
write(context, latest + 1, flags)
|
|
end)
|
|
|
|
local HIDDEN = defcode('HIDDEN', 0, function(context)
|
|
local entry = pop(context)
|
|
|
|
local flags = read(context, entry + 1)
|
|
flags = bit32.bxor(flags, F_HIDDEN)
|
|
write(context, entry + 1, flags)
|
|
end)
|
|
|
|
local HEADERCOMMA = defword('HEADER,', 0, {
|
|
HERE, FETCH, -- ( HERE ) ( c: )
|
|
LATEST, FETCH, COMMA, -- ( HERE ) ( c: LATEST )
|
|
LATEST, STORE, -- ( ) ( c: LATEST )
|
|
BL, WORD, COUNT, INCR, ALLOT, DROP, -- ( ) ( c: LATEST WORD ) Technically should align here
|
|
EXIT
|
|
})
|
|
|
|
local CREATE = defword('CREATE', 0, {
|
|
HEADERCOMMA,
|
|
LIT, __DOCREATE, COMMA,
|
|
LIT, 0, COMMA,
|
|
EXIT
|
|
})
|
|
|
|
|
|
local DOES = defword('(DOES>)', 0, {
|
|
FROMR, -- ( RP ) Steal the return address (points to target code)
|
|
LATEST, FETCH, TCFA, -- ( RP CFA ) Jump to CFA of LATEST word
|
|
DUP, LIT, __DODOES, SWAP, STORE, -- ( RP CFA ) Swap docreate for dodoes
|
|
INCR, STORE, -- ( ) Store RP in doesword
|
|
EXIT,
|
|
})
|
|
|
|
local COLON = defword(':', 0, {
|
|
HEADERCOMMA,
|
|
LIT, __DOCOL, COMMA,
|
|
LIT, 0, COMMA,
|
|
LATEST, FETCH, HIDDEN,
|
|
RBRAC,
|
|
EXIT
|
|
})
|
|
|
|
local SEMICOLON = defword(';', F_IMMED, {
|
|
LIT, EXIT, COMMA,
|
|
LATEST, FETCH, HIDDEN,
|
|
LBRAC,
|
|
EXIT,
|
|
})
|
|
|
|
-- Needed to bootstrap LITERAL/['] dependency loop
|
|
local COMPTICK = defcode('COMP\'', 0, function(context)
|
|
local xt = read(context, context.xp)
|
|
context.xp = context.xp + 1
|
|
push(context, xt)
|
|
end)
|
|
|
|
local LITSTRING = defcode('LITSTRING', 0, function(context)
|
|
local len = read(context, context.xp)
|
|
context.xp = context.xp + 1
|
|
|
|
local addr = context.xp
|
|
|
|
context.xp = context.xp + len
|
|
|
|
push(context, addr)
|
|
push(context, len)
|
|
end)
|
|
|
|
local TELL = defcode('TELL', 0, function(context)
|
|
local len = pop(context)
|
|
local addr = pop(context)
|
|
|
|
-- local str = ''
|
|
for i=1, len do
|
|
-- str = str..string.char(read(context, addr + i - 1))
|
|
table.insert(context.outbuf, string.char(read(context, addr + i - 1)))
|
|
end
|
|
|
|
-- game.print(str)
|
|
end)
|
|
|
|
local INTERPRET = defcode('INTERPRET', 0, function(context)
|
|
local paddr = pop(context)
|
|
|
|
local is_lit = false
|
|
local num = nil
|
|
|
|
local entry = _find(context, paddr)
|
|
|
|
local xt = nil
|
|
if entry == 0 then
|
|
dbg_print('Not found')
|
|
-- Literal?
|
|
is_lit = true
|
|
local remaining = nil
|
|
local len = read(context, paddr)
|
|
local addr = paddr + 1
|
|
num, _, remaining = _number(context, 0, addr, len)
|
|
if remaining ~= 0 then
|
|
-- Not a number
|
|
context.keybuf = {}
|
|
dbg_interp('ERR!')
|
|
output_string(context, 'err!\n')
|
|
return
|
|
else
|
|
dbg_interp('literal')
|
|
xt = LIT
|
|
end
|
|
else
|
|
dbg_print('entry=', entry)
|
|
xt = _tcfa(context, entry)
|
|
dbg_print('xt=', xt)
|
|
local flags = read(context, entry + 1)
|
|
if bit32.band(flags, F_IMMED) > 0 then
|
|
-- Execute XT
|
|
dbg_interp('IMMEDIATE EXEC')
|
|
local codeword = read(context, xt)
|
|
dbg_print('XT', xt, labels[xt])
|
|
dbg_print('CW', codeword, codeword_names[codeword])
|
|
do_codeword(context, xt)
|
|
return
|
|
end
|
|
end
|
|
-- Word or literal
|
|
-- print('STATE ', readvar(context, STATE))
|
|
if readvar(context, STATE) == 0 then
|
|
-- Executing
|
|
if is_lit then
|
|
dbg_interp('push '..tostring(num))
|
|
push(context, num)
|
|
else
|
|
-- Execute XT
|
|
dbg_interp('INTERPRET EXEC')
|
|
local codeword = read(context, xt)
|
|
dbg_interp('XT '..tostring(xt)..' '..tostring(labels[xt]))
|
|
dbg_interp('CW '..tostring(codeword)..' '..tostring(codeword_names[codeword]))
|
|
do_codeword(context, xt)
|
|
end
|
|
else
|
|
-- Compiling
|
|
dbg_interp('comma '..tostring(xt))
|
|
_comma(context, xt)
|
|
if is_lit then
|
|
dbg_interp('comma '..tostring(num))
|
|
_comma(context, num)
|
|
end
|
|
end
|
|
end)
|
|
|
|
local OK = defcode('OK', 0, function(context)
|
|
-- game.print('ok')
|
|
output_string(context, 'ok\n')
|
|
end)
|
|
|
|
local QUIT = defword('QUIT', 0, {
|
|
R0, RSPSTORE,
|
|
BL, WORD,
|
|
DUP,FETCH,ZGT,ZBRANCH,4,
|
|
INTERPRET,
|
|
BRANCH,2,
|
|
DROP,
|
|
OK,
|
|
BRANCH,-13,
|
|
EXIT,
|
|
})
|
|
|
|
local EXECUTE = defcode('EXECUTE', 0, function(context)
|
|
local xt = pop(context)
|
|
|
|
local cw = read(context, xt)
|
|
dbg_print('EXECUTE EXEC')
|
|
do_codeword(context, xt)
|
|
end)
|
|
|
|
local ITEMS = defcode('ITEMS', 0, function(context)
|
|
for id, name in pairs(sigs.item) do
|
|
output_string(context, id..' '..name..'\n')
|
|
end
|
|
end)
|
|
|
|
local FLUIDS = defcode('FLUIDS', 0, function(context)
|
|
for id, name in pairs(sigs.fluid) do
|
|
output_string(context, id..' '..name..'\n')
|
|
end
|
|
end)
|
|
|
|
local SIGNALS = defcode('SIGNALS', 0, function(context)
|
|
for id, name in pairs(sigs.virtual_signal) do
|
|
output_string(context, id..' '..name..'\n')
|
|
end
|
|
end)
|
|
|
|
local NSIGS = defcode('#SLOTS', 0, function(context)
|
|
local control = context.entity.get_or_create_control_behavior()
|
|
local section = control.get_section(1)
|
|
push(context, section.filters_count)
|
|
end)
|
|
|
|
local LIST_SIGS = defcode('LIST-SLOTS', 0, function(context)
|
|
local control = context.entity.get_or_create_control_behavior()
|
|
local section = control.get_section(1)
|
|
local filters = section.filters
|
|
end)
|
|
|
|
local function id_for_signal(type, name)
|
|
-- Factorio moment
|
|
if type == 'virtual' then type = 'virtual_signal' end
|
|
for i, sname in pairs(sigs[type]) do
|
|
if sname == name then return i end
|
|
end
|
|
return -1
|
|
end
|
|
|
|
local SLOTFETCH = defcode('SLOT@', 0, function(context)
|
|
local control = context.entity.get_or_create_control_behavior()
|
|
local section = control.get_section(1)
|
|
local filters = section.filters
|
|
|
|
local index = pop(context)
|
|
|
|
if index > section.filters_count then
|
|
error('Invalid slot index')
|
|
end
|
|
|
|
local filter = filters[index]
|
|
push(context, id_for_signal(filter.value.type, filter.value.name))
|
|
push(context, filter.min)
|
|
end)
|
|
|
|
function set_slot(context, type)
|
|
local control = context.entity.get_or_create_control_behavior()
|
|
control.enabled = true
|
|
local section = control.get_section(1)
|
|
section.active = true
|
|
local filters = section.filters
|
|
|
|
local value = pop(context)
|
|
local id = pop(context)
|
|
local index = pop(context)
|
|
|
|
local sig_list = sigs[type]
|
|
if id > #sig_list then
|
|
error('Invalid signal ID')
|
|
end
|
|
local name = sig_list[id]
|
|
|
|
-- More factorio cope
|
|
if type == 'virtual_signal' then type = 'virtual' end
|
|
local new_filter = { min = value, ['value'] = { comparator = '=', name = name, quality = 'normal', type = type } }
|
|
filters[index] = new_filter
|
|
section.filters = filters
|
|
end
|
|
|
|
local ITEMSTORE = defcode('ITEM!', 0, function(context)
|
|
set_slot(context, 'item')
|
|
end)
|
|
|
|
local FLUIDSTORE = defcode('FLUID!', 0, function(context)
|
|
set_slot(context, 'fluid')
|
|
end)
|
|
|
|
local SIGNALSTORE = defcode('SIGNAL!', 0, function(context)
|
|
set_slot(context, 'virtual_signal')
|
|
end)
|
|
|
|
local SLEEP = defcode('SLEEP', 0, function(context)
|
|
local ms = pop(context)
|
|
context.sleep_ticks = math.floor(ms * 60.0 / 1000.0)
|
|
end)
|
|
|
|
local DICTEND = defword('DICTEND', 0, {QUIT})
|
|
|
|
write(init_context, 0, QUIT)
|
|
|
|
init_context.xp = 0
|
|
writevar(init_context, HERE, init_here)
|
|
writevar(init_context, LATEST, link)
|
|
|
|
-- Bake the base forth code
|
|
for c in base_fth:gmatch('.') do
|
|
table.insert(init_context.keybuf, string.byte(c))
|
|
end
|
|
table.insert(init_context.keybuf, 10)
|
|
|
|
while true do
|
|
local waiting = forth_tick(init_context)
|
|
if waiting then break end
|
|
end
|
|
|
|
-- log('End bake')
|
|
-- for i, v in pairs(init_context.memspace) do
|
|
-- log(tostring(i)..' '..tostring(v))
|
|
-- end
|
|
|
|
init_context.outbuf = {}
|
|
|
|
init_context.valid = true
|
|
|
|
local new_ctx = copy_context(init_context)
|
|
new_ctx.entity = entity
|
|
return new_ctx
|
|
end
|
|
|
|
function pprint(thing)
|
|
for i, v in pairs(thing) do
|
|
print(i, v)
|
|
end
|
|
end
|
|
|
|
function pc(ctx)
|
|
print(ctx.xp, labels[read(ctx, ctx.xp)])
|
|
end
|
|
|
|
-- local f = io.open('base.fth', 'r')
|
|
-- if f ~= nil then
|
|
-- feed_file(ctx, f)
|
|
-- f:close()
|
|
-- end
|
|
|
|
function e()
|
|
while true do
|
|
local waiting = forth_tick(ctx)
|
|
if waiting then
|
|
local key = string.byte(io.read(1))
|
|
dbg_print('feed ', key, string.char(key))
|
|
feed_key(ctx, key)
|
|
end
|
|
end
|
|
end
|