local M = {}
local ipairs, select, setmetatable, table_pack, table_unpack = ipairs, select, setmetatable, table.pack, table.unpack
function M.concat(...)
local res = {}
local inputs = { ... }
for _, tab in ipairs(inputs) do
for _, elem in ipairs(tab) do
table.insert(res, elem)
end
end
return res
end
function M.notail(...)
return ...
end
function M.table_keys(tbl)
local tbl_keys = {}
local tbl_keys_length = 0
local key = nil
while true do
key = next(tbl, key)
if key == nil then
break
end
tbl_keys_length = tbl_keys_length + 1
tbl_keys[tbl_keys_length] = key
end
return tbl_keys
end
function M.table_stable_pairs(tbl, comp)
local function table_stable_next(state, i)
if i <= state.tbl_keys_length then
local key = state.tbl_keys[i]
return i + 1, key, state.tbl[key]
end
end
local tbl_keys = M.table_keys(tbl)
table.sort(tbl_keys, comp)
local state = { tbl = tbl, tbl_keys = tbl_keys, tbl_keys_length = #tbl_keys }
return table_stable_next, state, 1
end
function M.tag(name, info, fn, ...)
return M.notail(fn(...))
end
function M.lock_table(t, userdata)
local mt = getmetatable(t)
if mt == nil then
mt = {}
mt.__newindex = function(n, k, v)
if mt.__lock then
error("LOCKED TABLE! " .. M.strip_ansi(mt.__locktrace or ""))
end
rawset(n, k, v)
end
setmetatable(t, mt)
end
mt = getmetatable(t)
if mt.__lock then
error("Tried to lock a table that was already locked! " .. M.strip_ansi(mt.__locktrace or ""))
end
mt.__lock = true
end
function M.unlock_table(t)
local mt = getmetatable(t)
if mt and mt.__lock then
mt.__lock = false
mt.__locktrace = nil
else
error("Tried to unlock a table that was already unlocked!")
end
end
function M.is_locked(t)
local mt = getmetatable(t)
return mt and mt.__lock
end
function M.check_locked(t, shadow)
local mt = getmetatable(t)
if mt and mt.__lock then
if shadow then
error(
"Trying to shadow an already shadowed object! This should never happen!"
.. M.strip_ansi(mt.__locktrace or "")
)
else
error("Trying to modify a shadowed object! This should never happen!" .. M.strip_ansi(mt.__locktrace or ""))
end
end
end
function M.getshadowdepth(t)
local mt = getmetatable(t)
if mt == nil or mt.__depth == nil then
return 0
end
return mt.__depth
end
function M.append(list, value)
list[#list + 1] = value
end
function M.pop(list)
M.check_locked(list)
local value = list[#list]
rawset(list, #list, nil)
local mt = getmetatable(list)
if mt and mt.__length then
mt.__length = mt.__length - 1
end
return value
end
local shadowarray_mt = {}
function M.shadowtable(obj, userdata)
M.check_locked(obj, true)
M.lock_table(obj, userdata)
local mt = {
__shadow = obj,
__depth = M.getshadowdepth(obj) + 1,
}
mt.__index = function(t, k)
return mt.__shadow[k]
end
mt.__newindex = function(t, k, v)
if mt.__lock then
error("LOCKED TABLE! " .. M.strip_ansi(mt.__locktrace or ""))
end
rawset(t, k, v)
end
mt.__pairs = function(tbl)
local keys = {}
local t = tbl
local k, _ = next(t, nil)
while k do
keys[k] = true
k, _ = next(t, k)
end
t = mt.__shadow
while t ~= nil do
local k, _ = next(t, nil)
while k do
keys[k] = true
k, _ = next(t, k)
end
t = getmetatable(t)
if t then
t = t.__shadow
end
end
local function iter(t, ind)
local k, v = next(keys, ind)
if v ~= nil then
return k, tbl[k]
end
return nil
end
return iter, tbl, nil
end
return M.notail(setmetatable({}, mt))
end
local function rawpairs(tbl)
local function iter(t, ind)
local k, v = next(t, ind)
if v ~= nil then
return k, v
end
return nil
end
return iter, tbl, nil
end
function M.shadowarray(obj, userdata)
M.check_locked(obj, true)
M.lock_table(obj, userdata)
local mt = {
__shadow = obj,
__length = #obj,
__depth = M.getshadowdepth(obj) + 1,
}
mt.__index = function(t, k)
if k > #t then
return nil
end
return mt.__shadow[k]
end
mt.__newindex = function(t, k, v)
if mt.__lock then
error("LOCKED TABLE! " .. M.strip_ansi(mt.__locktrace or ""))
end
if k == #t + 1 then
mt.__length = mt.__length + 1
end
rawset(t, k, v)
end
mt.__len = function(t)
return mt.__length
end
mt.__ipairs = function(tbl)
return function(t, i)
i = i + 1
local v = t[i]
if nil ~= v then
return i, v
end
end, tbl, 0
end
return M.notail(setmetatable({}, mt))
end
function M.commit(t)
local mt = getmetatable(t)
local original = mt.__shadow
local length = mt.__length
setmetatable(t, nil)
if original then
M.unlock_table(original)
end
for k, v in pairs(t) do
rawset(original, k, v)
end
if length then
for i = length + 1, #original do
rawset(original, i, nil)
end
local orig_mt = getmetatable(original)
if orig_mt and orig_mt.__length then
orig_mt.__length = length
end
end
M.invalidate(t)
return original
end
function M.revert(t)
local mt = getmetatable(t)
local original = mt.__shadow
setmetatable(t, nil)
if original then
M.unlock_table(original)
end
M.invalidate(t)
return original
end
function M.shallow_copy(src)
local t = {}
for k, v in pairs(src) do
t[k] = v
end
return t
end
function M.dumptable(t, spaces)
spaces = spaces or 0
local s = { tostring(t) .. ": " }
for k, v in pairs(t) do
s[#s + 1] = string.rep(" ", spaces) .. " " .. tostring(k) .. ": " .. tostring(v)
end
local mt = getmetatable(t)
if mt and mt.__shadow then
s[#s + 1] = string.rep(" ", spaces) .. " [shadows]: " .. tostring(M.dumptable(mt.__shadow, spaces + 2))
end
return M.notail(table.concat(s, "\n"))
end
function M.dumptree(t, spaces)
spaces = spaces or 0
local s = tostring(t) .. ": "
for k, v in pairs(t) do
s = s .. "\n" .. string.rep(" ", spaces) .. " " .. tostring(k) .. ": "
if type(v) == "table" then
s = s .. M.dumptable(v, spaces + 2)
else
s = s .. tostring(v)
end
end
local mt = getmetatable(t)
if mt and mt.__shadow then
s = s .. "\n" .. string.rep(" ", spaces) .. " [shadows]: " .. tostring(M.dumptable(mt.__shadow, spaces + 2))
end
return s
end
function M.rawdump(t, spaces)
local mt = getmetatable(t)
setmetatable(t, nil)
spaces = spaces or 0
local s = tostring(t) .. ": " .. tostring(mt)
for k, v in pairs(t) do
s = s .. "\n" .. string.rep(" ", spaces)
s = s .. " " .. tostring(k) .. ": " .. tostring(v)
end
setmetatable(t, mt)
return s
end
local invalidate_mt = {
__index = function()
error("INVALID TABLE")
end,
__newindex = function()
error("INVALID TABLE")
end,
__len = function()
error("INVALID TABLE")
end,
__ipairs = function()
error("INVALID TABLE")
end,
__pairs = function()
error("INVALID TABLE")
end,
__call = function()
error("INVALID TABLE")
end,
}
function M.invalidate(t)
setmetatable(t, invalidate_mt)
end
function M.is_invalid(t)
return getmetatable(t) == invalidate_mt
end
local memo_mt = { __mode = "k" }
local memo_end_tag = {}
local memo_nil_tag = {}
function M.memoize(fn, args_table)
local memotab = setmetatable({}, memo_mt)
if args_table then
local function wrapfn(args)
local thismemo = memotab
local n = args.n
if n == nil then
n = #args
end
for i = 1, n do
this_arg = args[i]
if this_arg == nil then
this_arg = memo_nil_tag
end
local nextmemo = thismemo[this_arg]
if not nextmemo then
nextmemo = setmetatable({}, memo_mt)
thismemo[this_arg] = nextmemo
end
thismemo = nextmemo
end
if not thismemo[memo_end_tag] then
thismemo[memo_end_tag] = table_pack(fn(args))
end
local values = thismemo[memo_end_tag]
return table_unpack(values, 1, values.n)
end
return wrapfn
end
local function wrapfn(...)
local thismemo = memotab
for i = 1, select("#", ...) do
local this_arg = select(i, ...)
if this_arg == nil then
this_arg = memo_nil_tag
end
local nextmemo = thismemo[this_arg]
if not nextmemo then
nextmemo = setmetatable({}, memo_mt)
thismemo[this_arg] = nextmemo
end
thismemo = nextmemo
end
if not thismemo[memo_end_tag] then
thismemo[memo_end_tag] = table_pack(fn(...))
end
local values = thismemo[memo_end_tag]
return table_unpack(values, 1, values.n)
end
return wrapfn
end
function M.split_commas(s)
local subs = {}
s = s .. ","
for sub in s:gmatch("(.-),") do
table.insert(subs, sub)
end
return subs
end
function M.strip_ansi(s)
return s:gsub("\x1b%[[^m]*m", "")
end
function M.here(offset)
if debug == nil then
return "<no debug info>"
end
local info = debug.getinfo((offset or 1) + 1, "Sl")
return info.source .. ":" .. info.currentline
end
function M.bound_here(offset)
return ""
end
function M.file_is_terminal(input_file)
return false
end
function M.outputGreen(s)
return "\27[32m" .. s .. "\27[0m"
end
function M.outputRed(s)
return "\27[31m" .. s .. "\27[0m"
end
function M.get_cursor_position(input_file, output_file)
if input_file == nil then
input_file = io.input()
end
if output_file == nil then
output_file = io.output()
end
output_file:write("\x1b[6n")
local terminal_data = input_file:read(1)
if terminal_data ~= "\x9b" then
terminal_data = terminal_data .. input_file:read(1)
assert(terminal_data == "\x1b[")
end
terminal_data = input_file:read("*n")
assert(terminal_data ~= nil)
local cursor_line = terminal_data
terminal_data = input_file:read(1)
assert(terminal_data == ";")
terminal_data = input_file:read("*n")
assert(terminal_data ~= nil)
local cursor_column = terminal_data
terminal_data = input_file:read(1)
assert(terminal_data == "R")
return cursor_line, cursor_column
end
function M.levenshtein(str1, str2)
local len1 = string.len(str1)
local len2 = string.len(str2)
local matrix = {}
local cost = 0
if len1 == 0 then
return len2
elseif len2 == 0 then
return len1
elseif str1 == str2 then
return 0
end
for i = 0, len1, 1 do
matrix[i] = {}
matrix[i][0] = i
end
for j = 0, len2, 1 do
matrix[0][j] = j
end
for i = 1, len1, 1 do
for j = 1, len2, 1 do
if str1:byte(i) == str2:byte(j) then
cost = 0
else
cost = 1
end
matrix[i][j] = math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost)
end
end
return matrix[len1][len2]
end
function M.insert_tree_node(obj, store, i, extractors, level)
if store == nil then
store = {}
end
M.check_locked(store)
local curlevel = M.getshadowdepth(store)
if i > #extractors then
if level > 0 then
for j = curlevel + 1, level do
store = M.shadowarray(store)
end
if M.getshadowdepth(store) ~= level then
error("Improper shadowing happened!")
end
end
M.append(store, obj)
return store
end
local kx = extractors[i]
local key = kx(obj)
if level > 0 then
for j = curlevel + 1, level do
store = M.shadowtable(store)
end
if M.getshadowdepth(store) ~= level then
error("Improper shadowing happened!")
end
end
store[key] = M.insert_tree_node(obj, store[key], i + 1, extractors, level)
return store
end
function M.commit_tree_node(node, depth)
if M.getshadowdepth(node) < depth then
return node
end
local mt = getmetatable(node)
local base = mt.__shadow
local isleaf = mt.__length ~= nil
setmetatable(node, nil)
M.unlock_table(base)
if base then
while M.getshadowdepth(base) < (depth - 1) do
if isleaf then
base = M.shadowarray(base)
else
base = M.shadowtable(base)
end
end
for k, v in rawpairs(node) do
if (not isleaf) or base[k] == nil then
rawset(base, k, M.commit_tree_node(v, depth))
end
end
M.invalidate(node)
return base
end
return node
end
function M.revert_tree_node(node, depth)
if M.getshadowdepth(node) < depth then
return node
end
local mt = getmetatable(node)
local base = mt.__shadow
setmetatable(node, nil)
if base then
for k, v in rawpairs(node) do
M.revert_tree_node(v, depth)
end
M.unlock_table(base)
M.invalidate(node)
local anykey, _ = next(base, nil)
while anykey == nil and getmetatable(base) and getmetatable(base).__shadow do
node = base
base = getmetatable(node).__shadow
M.unlock_table(base)
M.invalidate(node)
anykey, _ = next(base, nil)
end
return base
end
return node
end
local litprint_mt = {
__tostring = function(val)
return val.contents
end,
}
function M.litprint(s)
return M.notail(setmetatable({ contents = s }, litprint_mt))
end
function M.debug_break()
return require("lua-init").debug_break()
end
DEBUG_ID = 0
function M.debug_id()
DEBUG_ID = DEBUG_ID + 1
return DEBUG_ID
end
function M.custom_traceback(err, prefix, level)
if type(err) == "table" then
return err
end
if prefix == nil then
prefix = ""
end
local s =
{ type(err) == "string" and err or ("must pass string or table to error handler, found: " .. tostring(err)) }
if level == nil then
level = 0
end
local i = 3 + level
if debug then
local info = debug.getinfo(i, "Sfln")
while info ~= nil do
if info.func == M.tag then
local _, name = debug.getlocal(i, 1)
local _, tag = debug.getlocal(i, 2)
local _, fn = debug.getlocal(i, 3)
local ok, err = pcall(function()
s[#s + 1] = string.format("%s [%s:%d] (%s)", name, info.short_src, info.currentline, pdump(tag))
end)
if not ok then
s[#s + 1] =
string.format("TRACE FAIL: %s [%s:%d] (%s)", name, info.short_src, info.currentline, err)
end
else
local name = info.name or string.format("<%s:%d>", info.short_src, info.linedefined)
local args = {}
local j = 1
local arg, v = debug.getlocal(i, j)
while arg ~= nil do
table.insert(
args,
(type(v) == "table") and "<" .. arg .. ":table>" or string.sub(tostring(v), 1, 12)
)
j = j + 1
arg, v = debug.getlocal(i, j)
end
s[#s + 1] = string.format("%s [%s:%d]", name, info.short_src, info.currentline)
end
i = i + 1
info = debug.getinfo(i, "Sfln")
end
end
return M.notail(table.concat(s, "\n" .. prefix))
end
function M.stack_trace(err)
return M.notail(M.custom_traceback(err))
end
local internals_interface = require "internals-interface"
internals_interface.U = M
return M