Compare commits
2 Commits
9c6ede00df
...
2efe383cbf
Author | SHA1 | Date | |
---|---|---|---|
2efe383cbf | |||
596fc9e9ce |
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
!/addon/
|
||||||
|
!/build/
|
||||||
|
!/core/
|
||||||
|
!/mod/
|
||||||
|
!/.gitignore
|
||||||
|
!/Makefile
|
13
Makefile
Normal file
13
Makefile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.PHONY: clean install-mods install
|
||||||
|
|
||||||
|
install: clean install-mods
|
||||||
|
luajit build/init.lua --linux
|
||||||
|
|
||||||
|
install-mods: clean
|
||||||
|
rm -rf ~/.local/lib/.tal_mod
|
||||||
|
mkdir -p ~/.local/lib/.tal_mod
|
||||||
|
cp -r mod/* ~/.local/lib/.tal_mod/
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf ~/.local/bin/tal
|
||||||
|
rm -rf ~/.local/lib/.tal_mod
|
14
addon/TAL/config.json
Normal file
14
addon/TAL/config.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/LuaLS/LLS-Addons/main/schemas/addon_config.schema.json",
|
||||||
|
"words": ["array%s*%{"],
|
||||||
|
"settings": {
|
||||||
|
"Lua.runtime.version" : "LuaJIT",
|
||||||
|
"Lua.diagnostics.globals" : [
|
||||||
|
"global1",
|
||||||
|
"global2"
|
||||||
|
],
|
||||||
|
"Lua.runtime.special" : {
|
||||||
|
"import" : "require"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
addon/TAL/library/array.lua
Normal file
123
addon/TAL/library/array.lua
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
--- @alias array<T> { [integer]: T } | arraylib
|
||||||
|
|
||||||
|
--- @class arraylib
|
||||||
|
arrays = {};
|
||||||
|
|
||||||
|
--- Converts the object to an array by putting "arrays" as its metatable
|
||||||
|
--- @generic T
|
||||||
|
--- @param obj { [integer]: T }
|
||||||
|
--- @return array<T>
|
||||||
|
--- @overload fun(val: string): array<string>
|
||||||
|
function array(obj) end
|
||||||
|
|
||||||
|
--- Converts the string to an array of characters
|
||||||
|
--- @param str string
|
||||||
|
--- @return array<string>
|
||||||
|
function array(str) end
|
||||||
|
|
||||||
|
--- Returns all the given arrays, concatenated to one
|
||||||
|
--- @generic T
|
||||||
|
--- @param ... T[]
|
||||||
|
--- @return array<T>
|
||||||
|
function arrays.concat(...) end
|
||||||
|
|
||||||
|
--- @generic T
|
||||||
|
--- @param self T[]
|
||||||
|
--- @param ... T[]
|
||||||
|
--- @return self
|
||||||
|
function arrays.append(self, ...) end
|
||||||
|
|
||||||
|
--- Adds all the given elements to the end of this array
|
||||||
|
--- @generic T
|
||||||
|
--- @param self T[]
|
||||||
|
--- @param ... T[]
|
||||||
|
--- @return self
|
||||||
|
function arrays.push(self, ...) end
|
||||||
|
|
||||||
|
--- Removes the last element of the array and returns it
|
||||||
|
--- @generic T
|
||||||
|
--- @param self array<T>
|
||||||
|
--- @return T val? The removed element, or nil if none
|
||||||
|
function arrays:pop() end
|
||||||
|
|
||||||
|
--- Returns the last element of the array
|
||||||
|
--- @generic T
|
||||||
|
--- @param self array<T>
|
||||||
|
--- @return T val? The last element of this array, or nil of none
|
||||||
|
function arrays:peek() end
|
||||||
|
|
||||||
|
--- Removes the first element of this array
|
||||||
|
--- @generic T
|
||||||
|
--- @param self array<T>
|
||||||
|
--- @return T val? The last element of this array, or nil of none
|
||||||
|
function arrays.shift(self) end
|
||||||
|
|
||||||
|
--- Adds all the given elements to the end of this array
|
||||||
|
--- @generic T
|
||||||
|
--- @param self T[]
|
||||||
|
--- @param ... T[]
|
||||||
|
--- @return self
|
||||||
|
function arrays.unshift(self, ...) end
|
||||||
|
|
||||||
|
--- Returns the result of mapping the values in table t through the function f
|
||||||
|
--- @generic In, Out
|
||||||
|
--- @param self In[]
|
||||||
|
--- @param f fun(val: In, i: integer, self: self): Out
|
||||||
|
--- @param mutate? boolean If true, will operate directly on the given array
|
||||||
|
--- @return Out[] arr
|
||||||
|
function arrays.map(self, f, mutate) end
|
||||||
|
|
||||||
|
--- Like arrays:map, but will expect the function to return arrays, and will :append them to the result array instead
|
||||||
|
--- @generic In, Out
|
||||||
|
--- @param self In[]
|
||||||
|
--- @param f fun(val: In, i: integer, self: self): Out[]
|
||||||
|
--- @return Out[] arr
|
||||||
|
function arrays.flat_map(self, f) end
|
||||||
|
|
||||||
|
--- Sorts the array in ascending order
|
||||||
|
--- @generic T
|
||||||
|
--- @param self T[]
|
||||||
|
--- @param f? fun(a: T, b: T): boolean A "less than" function, aka, a < b
|
||||||
|
--- @param copy? boolean If true will operate on a copy of the array
|
||||||
|
--- @return T[]
|
||||||
|
function arrays.sort(self, f, copy) end
|
||||||
|
|
||||||
|
--- Finds the index of the given element, or nil if it doesn't exist
|
||||||
|
--- @generic T
|
||||||
|
--- @param self T[]
|
||||||
|
--- @param f? fun(val: T, i: integer, self: self): T The predicate
|
||||||
|
--- @return integer?
|
||||||
|
function arrays.find_i(self, f) end
|
||||||
|
|
||||||
|
--- Sets each value from b to e to val in the given array
|
||||||
|
--- @generic T
|
||||||
|
--- @param self T[]
|
||||||
|
--- @param b? integer
|
||||||
|
--- @param e? integer
|
||||||
|
--- @return self
|
||||||
|
function arrays.fill(self, val, b, e) end
|
||||||
|
|
||||||
|
--- Every element from start to stop is removed from this array and are replaced with the given elements
|
||||||
|
--- @generic T
|
||||||
|
--- @param self T[]
|
||||||
|
--- @param b? integer
|
||||||
|
--- @param e? integer
|
||||||
|
--- @param ... T
|
||||||
|
--- @return self
|
||||||
|
function arrays.splice(self, b, e, ...) end
|
||||||
|
|
||||||
|
--- Returns the subarray from b to e
|
||||||
|
--- @generic T
|
||||||
|
--- @param self T[]
|
||||||
|
--- @param b? integer
|
||||||
|
--- @param e? integer
|
||||||
|
--- @return T[]
|
||||||
|
function arrays.slice(self, b, e) end
|
||||||
|
|
||||||
|
--- Equivalent of table.concat(self, sep, b, e)
|
||||||
|
--- @param sep string? Separator (defaults to empty)
|
||||||
|
--- @param b number? First element to take (defaults to beginning)
|
||||||
|
--- @param e number? Last element to take (defaults to end)
|
||||||
|
function arrays:join(sep, b, e) end
|
94
addon/TAL/library/coro.lua
Normal file
94
addon/TAL/library/coro.lua
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
--- @class coro
|
||||||
|
coro = {};
|
||||||
|
|
||||||
|
--- Creates a symmetric coroutine from the function
|
||||||
|
--- @param func function
|
||||||
|
--- @return thread
|
||||||
|
function coro.create(func) end
|
||||||
|
|
||||||
|
--- @return thread th The currently running thread
|
||||||
|
--- @return boolean is_main Whether or not this is the main thread
|
||||||
|
function coro.running() end
|
||||||
|
|
||||||
|
--- Gets the status of the thread
|
||||||
|
--- @param th thread
|
||||||
|
--- @return
|
||||||
|
--- | "running" -- The thread is the currently running one
|
||||||
|
--- | "suspended" -- The thread is not running, but may be resumed
|
||||||
|
--- | "normal" -- Is active but not running.
|
||||||
|
--- | "dead" -- The coroutine is not running and can't be resumed
|
||||||
|
function coro.status(th) end
|
||||||
|
|
||||||
|
--- Transfers the execution to the given thread
|
||||||
|
--- @param th thread The thread to transfer execution to
|
||||||
|
--- @param ... any The arguments to pass to the thread
|
||||||
|
--- @return boolean ok Whether or not the thread yielded normally
|
||||||
|
--- @return ... Result if ok is true, the error if ok is false
|
||||||
|
function coro.ptransfer(th, ...) end
|
||||||
|
|
||||||
|
--- Transfers the execution to the given thread
|
||||||
|
--- @param th thread The thread to transfer execution to
|
||||||
|
--- @param ... any The arguments to pass to the thread
|
||||||
|
--- @return ... The yielded values form the function
|
||||||
|
function coro.transfer(th, ...) end
|
||||||
|
|
||||||
|
--- Transfers the execution to the given thread
|
||||||
|
--- @param f fun(yield: fun(...): nil, ...): ... Wrapped function
|
||||||
|
--- @return fun(...): ...
|
||||||
|
function coro.wrap(f) end
|
||||||
|
|
||||||
|
--- @param f fun(yield: (fun(...): ...), ...): ...
|
||||||
|
--- @return fun(...): fun(...): ...
|
||||||
|
function coro.gen(f) end
|
||||||
|
|
||||||
|
--- Prematurely kills the coroutine
|
||||||
|
--- @param th thread
|
||||||
|
--- @return boolean ok
|
||||||
|
--- @return string? err
|
||||||
|
function coro.close(th) end
|
||||||
|
|
||||||
|
--- @class coroutine
|
||||||
|
coroutine = {};
|
||||||
|
|
||||||
|
--- Creates a asymmetric coroutine from the function
|
||||||
|
--- @param func function
|
||||||
|
--- @return thread
|
||||||
|
function coroutine.create(func) end
|
||||||
|
|
||||||
|
--- @return thread th The currently running thread
|
||||||
|
--- @return boolean is_main Whether or not this is the main thread
|
||||||
|
function coroutine.running() end
|
||||||
|
|
||||||
|
--- Gets the status of the thread
|
||||||
|
--- @param th thread
|
||||||
|
--- @return
|
||||||
|
--- | "running" -- The thread is the currently running one
|
||||||
|
--- | "suspended" -- The thread is not running, but may be resumed
|
||||||
|
--- | "normal" -- Is active but not running.
|
||||||
|
--- | "dead" -- The coroutine is not running and can't be resumed
|
||||||
|
function coroutine.status(th) end
|
||||||
|
|
||||||
|
--- Transfers to the coroutine asymmetrically; If it uses an asymmetric yield it will transfer to this coroutine
|
||||||
|
--- @param th thread The thread to transfer execution to
|
||||||
|
--- @param ... any The arguments to pass to the thread
|
||||||
|
--- @return boolean ok Whether or not the thread yielded normally
|
||||||
|
--- @return ... Result if ok is true, the error if ok is false
|
||||||
|
function coroutine.resume(th, ...) end
|
||||||
|
|
||||||
|
--- Yields to the calling coroutine
|
||||||
|
--- @param ... any The arguments to pass to the thread
|
||||||
|
--- @return ... The yielded values form the function
|
||||||
|
function coroutine.yield(...) end
|
||||||
|
|
||||||
|
--- Transfers the execution to the given thread
|
||||||
|
--- @param f fun(...): ... Wrapped function
|
||||||
|
--- @return fun(...): ...
|
||||||
|
function coroutine.wrap(f) end
|
||||||
|
|
||||||
|
--- Prematurely kills the coroutine
|
||||||
|
--- @param th thread
|
||||||
|
--- @return boolean ok
|
||||||
|
--- @return string? err
|
||||||
|
function coroutine.close(th) end
|
8
addon/TAL/library/env.lua
Normal file
8
addon/TAL/library/env.lua
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
--- @class env
|
||||||
|
--- @field jit boolean
|
||||||
|
--- @field os string
|
||||||
|
--- @field runtime string
|
||||||
|
--- @field arch string
|
||||||
|
env = {};
|
26
addon/TAL/library/function.lua
Normal file
26
addon/TAL/library/function.lua
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
--- @class functionlib
|
||||||
|
functions = {};
|
||||||
|
|
||||||
|
--- Constructs a function, such that it calls the first function with the passed arguments,
|
||||||
|
--- the second function with the return of the first, and so on. The return value of the last function is returned
|
||||||
|
---
|
||||||
|
--- In short, does the following, if the passed functions are a, b and c: return c(b(a(...)))
|
||||||
|
---
|
||||||
|
--- Sometimes less cumbersome to write (a | b | c | d)(args...) than d(c(b(a(args...))))
|
||||||
|
--- @param self function
|
||||||
|
function functions:pipe(...) end
|
||||||
|
function functions:apply(args) end
|
||||||
|
--- Constructs a function, such that it calls the first function with the passed arguments,
|
||||||
|
--- the second function with the return of the first, and so on. The return value of the last function is returned
|
||||||
|
---
|
||||||
|
--- In short, does the following, if the passed functions are a, b and c: return c(b(a(...)))
|
||||||
|
---
|
||||||
|
--- Sometimes less cumbersome to write (a | b | c | d)(args...) than d(c(b(a(args...))))
|
||||||
|
--- @param self function
|
||||||
|
function functions:pcall(...) end
|
||||||
|
--- Calls pipe with a and b; Alternative syntax for older Lua installations
|
||||||
|
function functions.__sub(a, b) end
|
||||||
|
--- Calls pipe with a and b
|
||||||
|
function functions.__bor(a, b) end
|
6
addon/TAL/library/module.lua
Normal file
6
addon/TAL/library/module.lua
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
module = { exports = {}, require = require, import = function (id) end, export = function (obj) end };
|
||||||
|
exports = module.exports;
|
||||||
|
import = require;
|
||||||
|
export = module.export;
|
4
addon/TAL/library/polyfill.lua
Normal file
4
addon/TAL/library/polyfill.lua
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
unpack = table.unpack;
|
||||||
|
table.unpack = unpack;
|
10
addon/TAL/library/printing.lua
Normal file
10
addon/TAL/library/printing.lua
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
--- Converts the value to a readable string
|
||||||
|
function to_readable(val, indent_str) end
|
||||||
|
|
||||||
|
--- Prints the given values to stderr
|
||||||
|
function print(...) end
|
||||||
|
|
||||||
|
--- Pretty-prints the given values to stderr
|
||||||
|
function pprint(...) end
|
24
addon/TAL/library/string.lua
Normal file
24
addon/TAL/library/string.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
|
||||||
|
--- Splits the given string with the given delimiter
|
||||||
|
--- @param self string
|
||||||
|
--- @param sep string The delimiter to split by. Defaults to an empty string
|
||||||
|
--- @return array<string>
|
||||||
|
function string.split(self, sep)end
|
||||||
|
|
||||||
|
--- Gives the character at the specified position
|
||||||
|
--- @param self string
|
||||||
|
--- @param i integer
|
||||||
|
--- @return string
|
||||||
|
function string.at(self, i)end
|
||||||
|
|
||||||
|
--- string.format("%q", self)
|
||||||
|
--- @param self string
|
||||||
|
--- @return string
|
||||||
|
function string.quote(self)end
|
||||||
|
|
||||||
|
--- Quotes the string, making it safe to pass to a shell script (unix only!!!)
|
||||||
|
--- @param self string
|
||||||
|
--- @return string
|
||||||
|
function string.quotesh(self)end
|
20
addon/TAL/library/utils.lua
Normal file
20
addon/TAL/library/utils.lua
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
box = table.pack;
|
||||||
|
unbox = table.unpack;
|
||||||
|
exit = os.exit;
|
||||||
|
|
||||||
|
--- @param box table
|
||||||
|
--- @param s? number
|
||||||
|
--- @param e? number
|
||||||
|
--- @return table
|
||||||
|
function rebox(box, s, e) end
|
||||||
|
|
||||||
|
--- Concatenates the arguments to a string
|
||||||
|
--- @return string
|
||||||
|
function str(...) end
|
||||||
|
|
||||||
|
---@generic T
|
||||||
|
---@param obj { [integer]: T }
|
||||||
|
---@return fun(): T
|
||||||
|
function iterate(obj) end
|
118
build/init.lua
Normal file
118
build/init.lua
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
local fs;
|
||||||
|
|
||||||
|
if not TAL then
|
||||||
|
local module = require "core.module";
|
||||||
|
local root = module.init {
|
||||||
|
tal_path = { "./mod/" },
|
||||||
|
lua_path = package.path,
|
||||||
|
target_glob = _G,
|
||||||
|
|
||||||
|
pwd = "./",
|
||||||
|
home = "",
|
||||||
|
|
||||||
|
join = require "mod.path".join,
|
||||||
|
cwd = require "mod.path".cwd,
|
||||||
|
};
|
||||||
|
|
||||||
|
require = root.require;
|
||||||
|
import = root.import;
|
||||||
|
export = root.export;
|
||||||
|
exports = root.exports;
|
||||||
|
end
|
||||||
|
|
||||||
|
fs = require "fs";
|
||||||
|
|
||||||
|
local args = require "..mod.args";
|
||||||
|
local modules = {
|
||||||
|
"core.entry",
|
||||||
|
"core.module",
|
||||||
|
"mod.path",
|
||||||
|
"mod.fs",
|
||||||
|
};
|
||||||
|
|
||||||
|
local function main(...)
|
||||||
|
local arg_readers = {};
|
||||||
|
local target;
|
||||||
|
local executable;
|
||||||
|
local paths = array {};
|
||||||
|
|
||||||
|
function arg_readers.dst(v)
|
||||||
|
target = v;
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
function arg_readers.path(v)
|
||||||
|
paths:push(v);
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
function arg_readers.executable(v)
|
||||||
|
executable = v;
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
function arg_readers.linux()
|
||||||
|
target = fs.home() .. "/.local/bin/tal";
|
||||||
|
|
||||||
|
if fs.exists "/bin/luajit" then
|
||||||
|
executable = "/bin/luajit";
|
||||||
|
elseif fs.exists "/bin/lua" then
|
||||||
|
executable = "/bin/lua"
|
||||||
|
else
|
||||||
|
print "No valid lua runtime found, WTF?";
|
||||||
|
end
|
||||||
|
|
||||||
|
paths = array {
|
||||||
|
"~/.local/lib/tal",
|
||||||
|
"~/.local/lib/.tal_mod",
|
||||||
|
"/usr/lib/tal",
|
||||||
|
"/usr/lib/.tal_mod",
|
||||||
|
"/usr/local/lib/.tal_mod",
|
||||||
|
};
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
arg_readers.o = arg_readers.dst;
|
||||||
|
arg_readers.x = arg_readers.executable;
|
||||||
|
arg_readers.p = arg_readers.path;
|
||||||
|
|
||||||
|
args(arg_readers, ...);
|
||||||
|
|
||||||
|
if not target then error "No target specified" end
|
||||||
|
|
||||||
|
local f = assert(io.open(target, "w"));
|
||||||
|
|
||||||
|
if executable then
|
||||||
|
f:write(("#!%s\n"):format(executable));
|
||||||
|
end
|
||||||
|
|
||||||
|
f:write "load(string.dump(function(...)\n";
|
||||||
|
f:write "function package.preload.__tal__PATH() return {\n";
|
||||||
|
f:write(paths:map(function (v) return "\t" .. v:quote() .. ",\n" end, true):join"");
|
||||||
|
f:write("} end\n");
|
||||||
|
|
||||||
|
for i = 1, #modules do
|
||||||
|
local name = modules[i];
|
||||||
|
local file = name:gsub("%.", "/") .. ".lua";
|
||||||
|
|
||||||
|
f:write(("package.preload[%q] = function(...)\n"):format(name));
|
||||||
|
for el in assert(io.open(file, "r")):lines(1024) do
|
||||||
|
f:write(el);
|
||||||
|
end
|
||||||
|
f:write("\nend\n");
|
||||||
|
end
|
||||||
|
|
||||||
|
f:write(("require %q(...)"):format("core.entry"));
|
||||||
|
|
||||||
|
f:write "\nend, true), '<internal>', 'b')(...)";
|
||||||
|
|
||||||
|
f:close();
|
||||||
|
|
||||||
|
if executable then
|
||||||
|
os.execute(("chmod +x %q"):format(target));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(module) ~= "table" then
|
||||||
|
main(...);
|
||||||
|
else
|
||||||
|
return main;
|
||||||
|
end
|
871
build/lexer.lua
Normal file
871
build/lexer.lua
Normal file
@ -0,0 +1,871 @@
|
|||||||
|
local TOK_ID = 1;
|
||||||
|
local TOK_OP = 2;
|
||||||
|
local TOK_STR = 3;
|
||||||
|
local TOK_NUM = 4;
|
||||||
|
|
||||||
|
local operators = {
|
||||||
|
AND = 1,
|
||||||
|
OR = 2,
|
||||||
|
NOT = 3,
|
||||||
|
|
||||||
|
CONCAT = 10,
|
||||||
|
ADD = 11,
|
||||||
|
SUB = 12,
|
||||||
|
MUL = 13,
|
||||||
|
DIV = 14,
|
||||||
|
IDIV = 15,
|
||||||
|
MOD = 16,
|
||||||
|
|
||||||
|
B_AND = 20,
|
||||||
|
B_OR = 21,
|
||||||
|
B_XOR = 22,
|
||||||
|
B_LSH = 24,
|
||||||
|
B_RSH = 25,
|
||||||
|
RSH = 26,
|
||||||
|
|
||||||
|
EQ = 30,
|
||||||
|
NEQ = 31,
|
||||||
|
LEQ = 32,
|
||||||
|
GEQ = 33,
|
||||||
|
LESS = 34,
|
||||||
|
GR = 35,
|
||||||
|
|
||||||
|
PAREN_OPEN = 40,
|
||||||
|
PAREN_CLOSE = 41,
|
||||||
|
BRACKET_OPEN = 42,
|
||||||
|
BRACKET_CLOSE = 43,
|
||||||
|
BRACE_OPEN = 44,
|
||||||
|
BRACE_CLOSE = 45,
|
||||||
|
|
||||||
|
SEMICOLON = 50,
|
||||||
|
COLON = 51,
|
||||||
|
COMMA = 52,
|
||||||
|
DOT = 53,
|
||||||
|
SPREAD = 54,
|
||||||
|
-- QUESTION = 55,
|
||||||
|
|
||||||
|
ASSIGN = 60,
|
||||||
|
ASSIGN_OR = 75,
|
||||||
|
};
|
||||||
|
local op_map = {
|
||||||
|
["+"] = { operators.ADD },
|
||||||
|
["-"] = { operators.SUB },
|
||||||
|
|
||||||
|
["*"] = {
|
||||||
|
operators.MUL,
|
||||||
|
["*"] = { operators.POW },
|
||||||
|
},
|
||||||
|
["/"] = {
|
||||||
|
operators.DIV,
|
||||||
|
["/"] = { operators.IDIV },
|
||||||
|
},
|
||||||
|
["%"] = { operators.MOD },
|
||||||
|
|
||||||
|
["&"] = { operators.B_AND },
|
||||||
|
["|"] = {operators.B_OR },
|
||||||
|
|
||||||
|
["^"] = { operators.POW },
|
||||||
|
["~"] = {
|
||||||
|
operators.B_XOR,
|
||||||
|
["="] = { operators.NEQ },
|
||||||
|
},
|
||||||
|
|
||||||
|
[">"] = {
|
||||||
|
operators.GR,
|
||||||
|
[">"] = {
|
||||||
|
operators.RSH,
|
||||||
|
[">"] = { operators.B_RSH },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["<"] = {
|
||||||
|
operators.LESS,
|
||||||
|
["<"] = { operators.B_LSH },
|
||||||
|
},
|
||||||
|
|
||||||
|
["="] = {
|
||||||
|
operators.ASSIGN,
|
||||||
|
["="] = { operators.EQ },
|
||||||
|
},
|
||||||
|
|
||||||
|
[","] = { operators.COMMA },
|
||||||
|
["."] = { operators.DOT },
|
||||||
|
[";"] = { operators.SEMICOLON },
|
||||||
|
[":"] = { operators.COLON },
|
||||||
|
["?"] = { operators.QUESTION },
|
||||||
|
|
||||||
|
["("] = { operators.PAREN_OPEN },
|
||||||
|
[")"] = { operators.PAREN_CLOSE },
|
||||||
|
["["] = { operators.BRACKET_OPEN },
|
||||||
|
["]"] = { operators.BRACKET_CLOSE },
|
||||||
|
["{"] = { operators.BRACE_OPEN },
|
||||||
|
["}"] = { operators.BRACE_CLOSE },
|
||||||
|
};
|
||||||
|
|
||||||
|
local to_byte = string.byte;
|
||||||
|
|
||||||
|
--- @class tok_base
|
||||||
|
--- @field loc string
|
||||||
|
--- @field end_loc string
|
||||||
|
--- @field comments string[]
|
||||||
|
--- @field raw string
|
||||||
|
|
||||||
|
--- @class tok_id: tok_base
|
||||||
|
--- @field type 1
|
||||||
|
--- @field val string
|
||||||
|
|
||||||
|
--- @class tok_op: tok_base
|
||||||
|
--- @field type 2
|
||||||
|
--- @field val integer
|
||||||
|
|
||||||
|
--- @class tok_str: tok_base
|
||||||
|
--- @field type 3
|
||||||
|
--- @field val string
|
||||||
|
|
||||||
|
--- @class tok_num: tok_base
|
||||||
|
--- @field type 4
|
||||||
|
--- @field val number
|
||||||
|
|
||||||
|
---@param base tok_base
|
||||||
|
---@param id string
|
||||||
|
---@return tok_id
|
||||||
|
local function tok_id(base, id)
|
||||||
|
base = base or { loc = "", comments = {} };
|
||||||
|
return {
|
||||||
|
loc = base.loc,
|
||||||
|
comments = base.comments,
|
||||||
|
type = TOK_ID,
|
||||||
|
val = id,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param base tok_base
|
||||||
|
---@param op integer
|
||||||
|
---@return tok_str
|
||||||
|
local function tok_op(base, op)
|
||||||
|
base = base or { loc = "", comments = {} };
|
||||||
|
return {
|
||||||
|
loc = base.loc,
|
||||||
|
comments = base.comments,
|
||||||
|
type = TOK_OP,
|
||||||
|
val = op,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param base tok_base?
|
||||||
|
---@param data string
|
||||||
|
---@return tok_str
|
||||||
|
local function tok_str(base, data)
|
||||||
|
base = base or { loc = "", comments = {} };
|
||||||
|
return {
|
||||||
|
loc = base.loc,
|
||||||
|
comments = base.comments,
|
||||||
|
type = TOK_STR,
|
||||||
|
val = data,
|
||||||
|
};
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param base tok_base
|
||||||
|
---@param num number
|
||||||
|
---@return tok_num
|
||||||
|
local function tok_num(base, num)
|
||||||
|
base = base or { loc = "", comments = {} };
|
||||||
|
return {
|
||||||
|
loc = base.loc,
|
||||||
|
comments = base.comments,
|
||||||
|
type = TOK_NUM,
|
||||||
|
val = num,
|
||||||
|
};
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @alias token
|
||||||
|
--- | tok_id
|
||||||
|
--- | tok_op
|
||||||
|
--- | tok_str
|
||||||
|
--- | tok_num
|
||||||
|
|
||||||
|
---@param loader string | fun(): string
|
||||||
|
---@return fun(): string?
|
||||||
|
local function char_supplier(loader)
|
||||||
|
if type(loader) == "string" then
|
||||||
|
local i = 0;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
i = i + 1;
|
||||||
|
local res = string.sub(loader, i, i);
|
||||||
|
if #res == 1 then return res end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local curr_str;
|
||||||
|
local i = 0;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
if curr_str == "" then return nil end
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
|
if curr_str == nil or i > #curr_str then
|
||||||
|
curr_str = loader();
|
||||||
|
if curr_str == false or curr_str == nil or curr_str == "" then
|
||||||
|
curr_str = "";
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
i = 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
return string.sub(curr_str, i, i);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param filename string
|
||||||
|
---@param chars fun(): string | nil
|
||||||
|
---@return fun(): token?
|
||||||
|
local function token_supplier(filename, chars, get_comments)
|
||||||
|
local line = 1;
|
||||||
|
local start = 1;
|
||||||
|
|
||||||
|
local _chars = chars;
|
||||||
|
chars = function ()
|
||||||
|
local c = _chars();
|
||||||
|
if c == "\n" then
|
||||||
|
line = line + 1;
|
||||||
|
start = 1;
|
||||||
|
else
|
||||||
|
start = start + 1;
|
||||||
|
end
|
||||||
|
return c;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unconsume(c)
|
||||||
|
if c == nil then return end
|
||||||
|
|
||||||
|
local old_chars = chars;
|
||||||
|
|
||||||
|
start = start - 1;
|
||||||
|
chars = function ()
|
||||||
|
chars = old_chars;
|
||||||
|
start = start + 1;
|
||||||
|
return c;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local consume_white;
|
||||||
|
|
||||||
|
if get_comments then
|
||||||
|
local function consume_comment()
|
||||||
|
-- local data = {};
|
||||||
|
-- local c = chars();
|
||||||
|
|
||||||
|
-- if c == "[" then
|
||||||
|
-- while true do
|
||||||
|
-- if c == nil then
|
||||||
|
-- return nil, "Unclosed comment";
|
||||||
|
-- elseif c == "]" and chars() == "#" then
|
||||||
|
-- break;
|
||||||
|
-- end
|
||||||
|
-- data[#data + 1] = c;
|
||||||
|
-- c = chars();
|
||||||
|
-- end
|
||||||
|
-- else
|
||||||
|
-- while true do
|
||||||
|
-- if c == "\n" or c == nil then break end
|
||||||
|
-- data[#data + 1] = c;
|
||||||
|
-- c = chars();
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- return table.concat(data);
|
||||||
|
|
||||||
|
local data = array {};
|
||||||
|
|
||||||
|
local function singleline(c)
|
||||||
|
while true do
|
||||||
|
if c == "\n" or c == nil then break end
|
||||||
|
data:push(c);
|
||||||
|
c = chars();
|
||||||
|
end
|
||||||
|
|
||||||
|
return data:join "";
|
||||||
|
end
|
||||||
|
local function multiline_end()
|
||||||
|
if chars() ~= "]" then return false end
|
||||||
|
if chars() ~= "-" then return false end
|
||||||
|
if chars() ~= "-" then return false end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local function multiline()
|
||||||
|
while true do
|
||||||
|
local c = chars()
|
||||||
|
if c == "]" and multiline_end() then
|
||||||
|
break;
|
||||||
|
elseif c == nil then
|
||||||
|
return nil, "Missing ]]";
|
||||||
|
end
|
||||||
|
|
||||||
|
data:push(c);
|
||||||
|
end
|
||||||
|
|
||||||
|
return data:join "";
|
||||||
|
end
|
||||||
|
|
||||||
|
local c = chars()
|
||||||
|
|
||||||
|
if c == "[" then
|
||||||
|
c = chars()
|
||||||
|
if c == "[" then
|
||||||
|
return multiline();
|
||||||
|
else
|
||||||
|
return singleline(c);
|
||||||
|
end
|
||||||
|
else singleline(c) end
|
||||||
|
|
||||||
|
return data:join "";
|
||||||
|
end
|
||||||
|
---@return false | string[]?, string?
|
||||||
|
function consume_white()
|
||||||
|
local comments = {};
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local c = chars();
|
||||||
|
|
||||||
|
if c == nil then
|
||||||
|
chars = function () return nil end
|
||||||
|
break;
|
||||||
|
elseif start == 2 and line == 1 and c == "#" then
|
||||||
|
local c2 = chars();
|
||||||
|
|
||||||
|
if c2 == "!" then
|
||||||
|
while c ~= "\n" do
|
||||||
|
c = chars();
|
||||||
|
end
|
||||||
|
else
|
||||||
|
unconsume(c2);
|
||||||
|
unconsume(c);
|
||||||
|
end
|
||||||
|
elseif c == "-" then
|
||||||
|
local c2 = chars()
|
||||||
|
|
||||||
|
if c2 == "-" then
|
||||||
|
local res, err = consume_comment();
|
||||||
|
if res == nil then return nil, err end
|
||||||
|
comments[#comments + 1] = res;
|
||||||
|
else
|
||||||
|
unconsume(c2)
|
||||||
|
unconsume(c)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elseif not string.find(c, "%s") then
|
||||||
|
unconsume(c);
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return comments;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
---@return string?
|
||||||
|
local function consume_comment()
|
||||||
|
local c = chars();
|
||||||
|
|
||||||
|
if c == "[" then
|
||||||
|
while true do
|
||||||
|
c = chars();
|
||||||
|
if c == nil then
|
||||||
|
return "Unclosed comment";
|
||||||
|
elseif c == "]" and chars() == "#" then break end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
while true do
|
||||||
|
if c == "\n" or c == nil then break end
|
||||||
|
c = chars();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
---@return false | string[]?, string?
|
||||||
|
function consume_white()
|
||||||
|
while true do
|
||||||
|
local c = chars();
|
||||||
|
|
||||||
|
if c == nil then
|
||||||
|
chars = function () return nil end
|
||||||
|
break;
|
||||||
|
elseif c == "#" then
|
||||||
|
local err = consume_comment();
|
||||||
|
if err ~= nil then return nil, err end
|
||||||
|
elseif not string.find(c, "%s") then
|
||||||
|
unconsume(c);
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hex_one(c)
|
||||||
|
local b = to_byte(c)
|
||||||
|
if b >= 48 and b <= 57 then
|
||||||
|
return b - 48
|
||||||
|
elseif b >= 97 and b <= 102 then
|
||||||
|
return b - 97 + 10
|
||||||
|
elseif b >= 65 and b <= 70 then
|
||||||
|
return b - 65 + 10
|
||||||
|
else
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function hex(base)
|
||||||
|
local res = 0
|
||||||
|
local any = false
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local c = chars()
|
||||||
|
if c == nil then break end
|
||||||
|
|
||||||
|
local digit = hex_one(c)
|
||||||
|
if digit == -1 then
|
||||||
|
unconsume(c)
|
||||||
|
break
|
||||||
|
else
|
||||||
|
res = res * 16 + digit
|
||||||
|
end
|
||||||
|
|
||||||
|
any = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not any then return end
|
||||||
|
|
||||||
|
return tok_num(base, res)
|
||||||
|
end
|
||||||
|
local function decimal(res, float, mult)
|
||||||
|
local any = true
|
||||||
|
local fract_mult = .1
|
||||||
|
|
||||||
|
if type(res) == "string" then
|
||||||
|
local b = to_byte(res)
|
||||||
|
|
||||||
|
if b >= 48 and b <= 57 then
|
||||||
|
res = b - 48
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
elseif res == nil then
|
||||||
|
res, any = 0, false
|
||||||
|
end
|
||||||
|
|
||||||
|
if mult == nil then mult = 1 end
|
||||||
|
|
||||||
|
local c
|
||||||
|
|
||||||
|
while true do
|
||||||
|
c = chars()
|
||||||
|
if c == nil then break end
|
||||||
|
|
||||||
|
local b = to_byte(c)
|
||||||
|
if b >= 48 and b <= 57 then
|
||||||
|
any = true
|
||||||
|
if float then
|
||||||
|
res = res + (b - 48) * fract_mult
|
||||||
|
fract_mult = fract_mult * .1
|
||||||
|
else
|
||||||
|
res = res * 10 + (b - 48)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if any then
|
||||||
|
return mult * res, c
|
||||||
|
else
|
||||||
|
return nil, c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function number(base, res)
|
||||||
|
local fract, e
|
||||||
|
local whole, next = decimal(res, false)
|
||||||
|
|
||||||
|
if next == "." then
|
||||||
|
fract, next = decimal(nil, true)
|
||||||
|
end
|
||||||
|
if next == "e" then
|
||||||
|
local c = chars()
|
||||||
|
if c == "-" then
|
||||||
|
e, next = decimal(nil, false, -1)
|
||||||
|
else
|
||||||
|
e, next = decimal(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
if e == nil then
|
||||||
|
return nil, "Expected number after 'e'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if fract == nil then fract = 0 end
|
||||||
|
if e == nil then
|
||||||
|
e = 1
|
||||||
|
else
|
||||||
|
e = 10 ^ e
|
||||||
|
end
|
||||||
|
|
||||||
|
unconsume(next)
|
||||||
|
return tok_num(base, (whole + fract) * e)
|
||||||
|
end
|
||||||
|
local function zero(base)
|
||||||
|
local c = chars()
|
||||||
|
if c == nil then return tok_num(base, 0) end
|
||||||
|
|
||||||
|
local b = to_byte(c)
|
||||||
|
|
||||||
|
if c == "x" then
|
||||||
|
local res = hex(base)
|
||||||
|
if res == nil then return nil, "Expected a hex literal"
|
||||||
|
else return res end
|
||||||
|
else
|
||||||
|
unconsume(c)
|
||||||
|
return number(base, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function id(base, c)
|
||||||
|
local res = c
|
||||||
|
|
||||||
|
while true do
|
||||||
|
c = chars()
|
||||||
|
if c == nil then break end
|
||||||
|
|
||||||
|
local b = to_byte(c)
|
||||||
|
if
|
||||||
|
b >= 65 and b <= 90 or -- A-Z
|
||||||
|
b >= 97 and b <= 122 or -- a-z
|
||||||
|
b >= 48 and b <= 57 or -- 0-9
|
||||||
|
c == "_"
|
||||||
|
then
|
||||||
|
res = res .. c
|
||||||
|
else
|
||||||
|
unconsume(c)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
base.raw = res
|
||||||
|
|
||||||
|
return tok_id(base, res)
|
||||||
|
end
|
||||||
|
local function dot(base)
|
||||||
|
local e, fract, next = nil, decimal(nil, true)
|
||||||
|
|
||||||
|
if fract == nil then
|
||||||
|
if next == "." then
|
||||||
|
local c = chars()
|
||||||
|
|
||||||
|
if c == "." then
|
||||||
|
return tok_op(base, operators.SPREAD)
|
||||||
|
else
|
||||||
|
unconsume(c)
|
||||||
|
return tok_op(base, operators.CONCAT)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
unconsume(next)
|
||||||
|
return tok_op(base, operators.DOT)
|
||||||
|
end
|
||||||
|
|
||||||
|
return base
|
||||||
|
end
|
||||||
|
|
||||||
|
if next == "e" then
|
||||||
|
local c = chars()
|
||||||
|
if c == "-" then
|
||||||
|
e, next = decimal(nil, false, -1)
|
||||||
|
else
|
||||||
|
e, next = decimal(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
if e == nil then
|
||||||
|
return nil, "Expected number after 'e'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if fract == nil then fract = 0 end
|
||||||
|
if e == nil then
|
||||||
|
e = 1
|
||||||
|
else
|
||||||
|
e = 10 ^ e
|
||||||
|
end
|
||||||
|
|
||||||
|
unconsume(next)
|
||||||
|
return tok_num(base, fract * e)
|
||||||
|
end
|
||||||
|
local function char(c, allow_newline)
|
||||||
|
if c == nil then return nil
|
||||||
|
elseif c == "\\" then
|
||||||
|
c = chars()
|
||||||
|
if c == "a" then return "\a"
|
||||||
|
elseif c == "b" then return "\b"
|
||||||
|
elseif c == "f" then return "\f"
|
||||||
|
elseif c == "n" then return "\n"
|
||||||
|
elseif c == "r" then return "\r"
|
||||||
|
elseif c == "t" then return "\t"
|
||||||
|
elseif c == "v" then return "\v"
|
||||||
|
elseif c == "z" then
|
||||||
|
repeat
|
||||||
|
c = chars()
|
||||||
|
until c == " " or c == "\n" or c == "\r" or c == "\t" or c == "\v"
|
||||||
|
return char(c)
|
||||||
|
elseif c == "x" then
|
||||||
|
local ca, cb = chars(), chars()
|
||||||
|
if ca == nil or cb == nil then return nil, "Expected a hex number" end
|
||||||
|
|
||||||
|
local a, b = hex_one(ca), hex_one(cb)
|
||||||
|
if a == -1 or b == -1 then return nil, "Expected a hex number" end
|
||||||
|
|
||||||
|
return string.char(a * 16 + b)
|
||||||
|
else return c end
|
||||||
|
else return c end
|
||||||
|
end
|
||||||
|
local function quote_str(base, first)
|
||||||
|
local res = {};
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local c, err;
|
||||||
|
|
||||||
|
c = chars()
|
||||||
|
if c == first then break end
|
||||||
|
if c == nil then return nil, "Unterminated string literal" end
|
||||||
|
|
||||||
|
c, err = char(c)
|
||||||
|
if c == nil then
|
||||||
|
return nil, err or "Unterminated string literal";
|
||||||
|
else
|
||||||
|
res[#res + 1] = c;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tok_str(base, table.concat(res));
|
||||||
|
end
|
||||||
|
local function quote_char(base, first)
|
||||||
|
local res = 0;
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local c, err;
|
||||||
|
|
||||||
|
c = chars();
|
||||||
|
if c == first then break end
|
||||||
|
if c == nil then return nil, "Unterminated string literal" end
|
||||||
|
|
||||||
|
c, err = char(c);
|
||||||
|
if c == nil then
|
||||||
|
return nil, err or "Unterminated string literal";
|
||||||
|
else
|
||||||
|
for _, v in ipairs { string.byte(c, 1, #c) } do
|
||||||
|
res = res * 256 + v;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tok_num(base, res);
|
||||||
|
end
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
-- local comments = consume_white()
|
||||||
|
local comments, err = consume_white();
|
||||||
|
if comments == nil then
|
||||||
|
error(table.concat({ filename, line, start }, ":") .. ": " .. err);
|
||||||
|
elseif comments == false then
|
||||||
|
comments = nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
local loc = table.concat({ filename, line, start }, ":");
|
||||||
|
--- @type table | nil
|
||||||
|
local base = { loc = loc, comments = comments, raw = "" };
|
||||||
|
|
||||||
|
local c = chars()
|
||||||
|
if c == nil then return nil end
|
||||||
|
local b = to_byte(c)
|
||||||
|
|
||||||
|
if c == "." then base, err = dot(base)
|
||||||
|
elseif c == "0" then base, err = zero(base)
|
||||||
|
elseif b >= 49 and b <= 57 then base, err = number(base, b - 48) -- 1-9
|
||||||
|
elseif
|
||||||
|
b >= 65 and b <= 90 or -- A-Z
|
||||||
|
b >= 97 and b <= 122 or -- a-z
|
||||||
|
c == "_"
|
||||||
|
then
|
||||||
|
base, err = id(base, c)
|
||||||
|
elseif c == "\"" then
|
||||||
|
base, err = quote_str(base, c);
|
||||||
|
elseif c == "\'" then
|
||||||
|
base, err = quote_char(base, c);
|
||||||
|
else
|
||||||
|
local res = op_map;
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local next = res[c];
|
||||||
|
if next == nil then
|
||||||
|
unconsume(c);
|
||||||
|
res = res[1];
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
c = chars();
|
||||||
|
res = next;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if res == nil then
|
||||||
|
base, err = nil, string.format("Unexpected char '%s'", c)
|
||||||
|
else
|
||||||
|
base.type = TOK_OP;
|
||||||
|
base.val = res;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if base == nil then
|
||||||
|
return error(loc .. ": " .. err);
|
||||||
|
else
|
||||||
|
base.end_loc = table.concat({ filename, line, start }, ":");
|
||||||
|
return base;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param ... fun(): token?, string?
|
||||||
|
local function concat_tokens(...)
|
||||||
|
local arr = {...};
|
||||||
|
local i = 1;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
while true do
|
||||||
|
local el = arr[i];
|
||||||
|
|
||||||
|
if el == nil then
|
||||||
|
return nil;
|
||||||
|
elseif type(el) == "function" then
|
||||||
|
local buff = el();
|
||||||
|
if buff ~= "" then
|
||||||
|
return buff;
|
||||||
|
else
|
||||||
|
i = i + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param first token
|
||||||
|
---@param second token
|
||||||
|
local function can_go_after(first, second)
|
||||||
|
if first.type == TOK_OP and second.type == TOK_OP then
|
||||||
|
if (
|
||||||
|
first.val == operators.ASSIGN or
|
||||||
|
first.val == operators.EQ or
|
||||||
|
first.val == operators.LESS or
|
||||||
|
first.val == operators.GR or
|
||||||
|
first.val == operators.B_XOR
|
||||||
|
) and (
|
||||||
|
second.val == operators.ASSIGN or
|
||||||
|
second.val == operators.EQ
|
||||||
|
) then return false end
|
||||||
|
if (
|
||||||
|
first.val == operators.LESS or
|
||||||
|
first.val == operators.GR
|
||||||
|
) and (
|
||||||
|
second.val == operators.LESS or
|
||||||
|
second.val == operators.GR
|
||||||
|
) then return false end
|
||||||
|
if (
|
||||||
|
first.val == operators.DOT or
|
||||||
|
first.val == operators.CONCAT or
|
||||||
|
first.val == operators.SPREAD
|
||||||
|
) and
|
||||||
|
(
|
||||||
|
second.val == operators.DOT or
|
||||||
|
second.val == operators.CONCAT or
|
||||||
|
second.val == operators.SPREAD
|
||||||
|
) then return false end
|
||||||
|
if (
|
||||||
|
first.val == operators.LABEL or
|
||||||
|
first.val == operators.COLON
|
||||||
|
) and (
|
||||||
|
second.val == operators.LABEL or
|
||||||
|
second.val == operators.COLON
|
||||||
|
) then return false end
|
||||||
|
if (
|
||||||
|
first.val == operators.DIV or
|
||||||
|
first.val == operators.IDIV
|
||||||
|
) and (
|
||||||
|
second.val == operators.DIV or
|
||||||
|
second.val == operators.IDIV
|
||||||
|
) then return false end
|
||||||
|
|
||||||
|
return true;
|
||||||
|
elseif first.type == TOK_NUM and second.type == TOK_ID then
|
||||||
|
return false
|
||||||
|
elseif first.type == TOK_ID and second.type == TOK_NUM then
|
||||||
|
return false
|
||||||
|
elseif first.type == TOK_ID and second.type == TOK_ID then
|
||||||
|
return false
|
||||||
|
elseif first.type == TOK_NUM and second.type == TOK_OP then
|
||||||
|
return (
|
||||||
|
second.val ~= operators.DOT and
|
||||||
|
second.val ~= operators.CONCAT and
|
||||||
|
second.val ~= operators.SPREAD
|
||||||
|
);
|
||||||
|
elseif first.type == TOK_OP and second.type == TOK_NUM then
|
||||||
|
return (
|
||||||
|
first.val ~= operators.DOT and
|
||||||
|
first.val ~= operators.CONCAT and
|
||||||
|
first.val ~= operators.SPREAD
|
||||||
|
);
|
||||||
|
elseif first.type == TOK_ID and second.type == TOK_ID then
|
||||||
|
return true;
|
||||||
|
elseif first.type == TOK_NUM and second.type == TOK_NUM then
|
||||||
|
return false;
|
||||||
|
elseif first.type == TOK_STR and second.type == TOK_STR then
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param tokens fun(): token?, string?
|
||||||
|
---@return fun(): string?, string?
|
||||||
|
local function token_stringifier(tokens)
|
||||||
|
local last_tok
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
if tokens == nil then return nil end
|
||||||
|
|
||||||
|
local tok, err = tokens()
|
||||||
|
if tok == nil then
|
||||||
|
--- @diagnostic disable-next-line: cast-local-type
|
||||||
|
tokens = nil
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
if last_tok ~= nil and not can_go_after(last_tok, tok) then
|
||||||
|
last_tok = tok
|
||||||
|
return " " .. tok.raw
|
||||||
|
else
|
||||||
|
last_tok = tok
|
||||||
|
return tok.raw
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
TOK_ID = TOK_ID,
|
||||||
|
TOK_OP = TOK_OP,
|
||||||
|
TOK_STR = TOK_STR,
|
||||||
|
TOK_NUM = TOK_NUM,
|
||||||
|
|
||||||
|
operators = operators,
|
||||||
|
|
||||||
|
char_supplier = char_supplier,
|
||||||
|
token_supplier = token_supplier,
|
||||||
|
concat_tokens = concat_tokens,
|
||||||
|
token_stringifier = token_stringifier,
|
||||||
|
|
||||||
|
tok_id = tok_id,
|
||||||
|
tok_op = tok_op,
|
||||||
|
tok_num = tok_num,
|
||||||
|
tok_str = tok_str,
|
||||||
|
}
|
29
build/require_filter.lua
Normal file
29
build/require_filter.lua
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
local lexer = require "lexer";
|
||||||
|
|
||||||
|
---@param tokens fun(): token?, string?
|
||||||
|
---@param mapper fun(name: string, base: tok_base): token?
|
||||||
|
---@return fun(): token?, string?
|
||||||
|
return function (tokens, mapper)
|
||||||
|
local last_req = false;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
local tok, err = tokens();
|
||||||
|
|
||||||
|
if tok == nil then
|
||||||
|
return tok, err;
|
||||||
|
elseif last_req then
|
||||||
|
if tok.type == lexer.TOK_STR then
|
||||||
|
last_req = false;
|
||||||
|
--- @diagnostic disable-next-line: param-type-mismatch
|
||||||
|
return mapper(tok.val, tok);
|
||||||
|
elseif tok.type ~= lexer.TOK_KW or tok.val ~= lexer.K_PAREN_OPEN then
|
||||||
|
last_req = false;
|
||||||
|
end
|
||||||
|
elseif tok.type == lexer.TOK_ID and tok.val == "require" then
|
||||||
|
last_req = true;
|
||||||
|
end
|
||||||
|
|
||||||
|
return tok;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
17
core/entry.lua
Executable file
17
core/entry.lua
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
local module = require "core.module";
|
||||||
|
local fs = require "mod.fs";
|
||||||
|
local path = require "mod.path";
|
||||||
|
|
||||||
|
TAL = "0.0.1";
|
||||||
|
|
||||||
|
return function (...)
|
||||||
|
local root, loop_run = module.init {
|
||||||
|
tal_path = require "__tal__PATH",
|
||||||
|
fs = fs,
|
||||||
|
path = path,
|
||||||
|
};
|
||||||
|
|
||||||
|
loop_run.run(function (...)
|
||||||
|
root.require "tal.cli".main(...);
|
||||||
|
end, ...);
|
||||||
|
end
|
324
core/module.lua
Normal file
324
core/module.lua
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
local exports = {};
|
||||||
|
|
||||||
|
local function mk_module(name, init, parent)
|
||||||
|
local module;
|
||||||
|
local cache = {};
|
||||||
|
|
||||||
|
parent.modules = parent.modules or {};
|
||||||
|
module = setmetatable({
|
||||||
|
name = name,
|
||||||
|
exports = {},
|
||||||
|
init = init,
|
||||||
|
env = setmetatable({}, { __index = parent.env }),
|
||||||
|
}, { __index = parent });
|
||||||
|
|
||||||
|
module.returns = { n = 1, module.exports };
|
||||||
|
|
||||||
|
function module.resolve(id, list)
|
||||||
|
if cache[id] ~= nil then return cache[id] end
|
||||||
|
|
||||||
|
for i = 1, #module.resolvers do
|
||||||
|
local res, files = module.resolvers[i](module, id);
|
||||||
|
if res then
|
||||||
|
cache[id] = res;
|
||||||
|
return res;
|
||||||
|
elseif list then
|
||||||
|
for i = 1, #files do
|
||||||
|
list[#list + 1] = files[i];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
function module.import(id)
|
||||||
|
local paths = {};
|
||||||
|
local mod = module.resolve(id, paths);
|
||||||
|
if mod == nil then
|
||||||
|
error(table.concat {
|
||||||
|
"Unable to resolve module '",
|
||||||
|
id,
|
||||||
|
"' - tried the following paths:\n- ",
|
||||||
|
table.concat(paths, "\n- ")
|
||||||
|
}, 2);
|
||||||
|
elseif type(mod.init) == "function" then
|
||||||
|
local init = mod.init;
|
||||||
|
mod.init = nil;
|
||||||
|
init(mod);
|
||||||
|
end
|
||||||
|
|
||||||
|
return (unpack or table.unpack)(mod.returns, 1, mod.returns.n);
|
||||||
|
end
|
||||||
|
|
||||||
|
function module.require(id)
|
||||||
|
local original_id = id;
|
||||||
|
local match = id:match "[^/]+$";
|
||||||
|
|
||||||
|
if match == id then
|
||||||
|
local start = id:match "^%.+" or "";
|
||||||
|
local rest = id:sub(#start + 1):gsub("%.", "/");
|
||||||
|
|
||||||
|
if #start == 0 then
|
||||||
|
id = rest;
|
||||||
|
elseif #start == 1 then
|
||||||
|
id = "./" .. rest;
|
||||||
|
else
|
||||||
|
id = ("../"):rep(#start - 1) .. rest;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
id = id:sub(1, -#match - 1) .. match:gsub("%.", "/");
|
||||||
|
end
|
||||||
|
|
||||||
|
local paths = {};
|
||||||
|
local mod = module.resolve(id .. ".lua", paths);
|
||||||
|
|
||||||
|
if mod == nil then
|
||||||
|
mod = module.resolve(id .. "/init.lua", paths);
|
||||||
|
end
|
||||||
|
|
||||||
|
if mod == nil then
|
||||||
|
error(table.concat {
|
||||||
|
"Unable to resolve module '",
|
||||||
|
original_id,
|
||||||
|
"' - tried the following paths:\n- ",
|
||||||
|
table.concat(paths, "\n- ")
|
||||||
|
}, 2);
|
||||||
|
elseif type(mod.init) == "function" then
|
||||||
|
local init = mod.init;
|
||||||
|
mod.init = nil;
|
||||||
|
init(mod);
|
||||||
|
end
|
||||||
|
|
||||||
|
return (unpack or table.unpack)(mod.returns, 1, mod.returns.n);
|
||||||
|
end
|
||||||
|
function module.export(exports)
|
||||||
|
for k, v in next, exports do
|
||||||
|
module.exports[k] = v;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function module.mk(name, init)
|
||||||
|
if module.modules[name] ~= nil then
|
||||||
|
return module.modules[name], true;
|
||||||
|
else
|
||||||
|
local mod = mk_module(name, init, module);
|
||||||
|
module.modules[name] = mod;
|
||||||
|
return mod, false;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module.env.module = module;
|
||||||
|
module.env.import = module.import;
|
||||||
|
module.env.require = module.require;
|
||||||
|
module.env.export = module.export;
|
||||||
|
module.env.exports = module.exports;
|
||||||
|
|
||||||
|
module.env.package = setmetatable({}, {
|
||||||
|
__index = function()
|
||||||
|
error "This is a TAL module, please use 'module' instead";
|
||||||
|
end,
|
||||||
|
__newindex = function()
|
||||||
|
error "This is a TAL module, please use 'module' instead";
|
||||||
|
end,
|
||||||
|
});
|
||||||
|
|
||||||
|
return module;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function try_file(self, cwd, id, base, suffixes)
|
||||||
|
suffixes = suffixes or { "" };
|
||||||
|
local file, found;
|
||||||
|
local files = {};
|
||||||
|
|
||||||
|
for i = 1, #suffixes do
|
||||||
|
file = cwd(base, id .. suffixes[i]);
|
||||||
|
|
||||||
|
local f = io.open(file, "r");
|
||||||
|
if f ~= nil then
|
||||||
|
f:close();
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
|
||||||
|
files[#files + 1] = file;
|
||||||
|
end
|
||||||
|
|
||||||
|
if not found then
|
||||||
|
return nil, files;
|
||||||
|
end
|
||||||
|
|
||||||
|
return self.mk(file, function (self)
|
||||||
|
local func = assert(loadfile(file, "t", self.env));
|
||||||
|
|
||||||
|
local function fin(...)
|
||||||
|
if select("#", ...) > 0 then
|
||||||
|
self.exports = ...;
|
||||||
|
self.returns = { n = select("#", ...), ... };
|
||||||
|
end
|
||||||
|
end
|
||||||
|
fin(func());
|
||||||
|
end), file;
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.file_resolver(cwd, join)
|
||||||
|
return function(self, id)
|
||||||
|
if id:match "^%./" or id:match "^%.%./" then
|
||||||
|
return try_file(self, cwd, id, join(self.name, "..", self.extensions), self.extensions);
|
||||||
|
else
|
||||||
|
local files = {};
|
||||||
|
|
||||||
|
for i = 1, #self.paths do
|
||||||
|
local res, file = try_file(self, cwd, id, self.paths[i], self.extensions);
|
||||||
|
if res ~= nil then
|
||||||
|
return res, file;
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #file do
|
||||||
|
files[#files + 1] = file[i];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, files;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function exports.lua_resolver(opts)
|
||||||
|
local returns = {
|
||||||
|
modules = {},
|
||||||
|
};
|
||||||
|
|
||||||
|
function returns.resolver(self, id)
|
||||||
|
if id:match "^lua:" then
|
||||||
|
id = id:sub(5);
|
||||||
|
end
|
||||||
|
|
||||||
|
if returns.modules[id] ~= nil then
|
||||||
|
return returns.modules[id], "lua:" ..id;
|
||||||
|
else
|
||||||
|
return nil, { "lua:" .. id };
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function returns.register(name, ...)
|
||||||
|
returns.modules[name] = {
|
||||||
|
name = "lua:" .. name,
|
||||||
|
exports = ...,
|
||||||
|
returns = { n = select("#", ...), ... },
|
||||||
|
};
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.glob then
|
||||||
|
returns.register("global.lua", opts.glob);
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.expose then
|
||||||
|
local expose = opts.expose;
|
||||||
|
if expose == true then
|
||||||
|
expose = {
|
||||||
|
"utf8", "bit32", "bit",
|
||||||
|
"table", "string", "os", "math", "jit", "io", "ffi", "debug", "coroutine",
|
||||||
|
"table.new", "table.clear",
|
||||||
|
"string.buffer",
|
||||||
|
"jit.profile",
|
||||||
|
};
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #expose do
|
||||||
|
local ok, mod = pcall(require, expose[i]);
|
||||||
|
if ok then
|
||||||
|
returns.register(expose[i]:gsub("%.", "/") .. ".lua", mod);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.root then
|
||||||
|
returns.register("root.lua", opts.root);
|
||||||
|
end
|
||||||
|
|
||||||
|
returns.register("lua-resolver.lua", returns);
|
||||||
|
|
||||||
|
return returns;
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.mk_root(opts)
|
||||||
|
local run_loop;
|
||||||
|
local root = mk_module(
|
||||||
|
opts.join(opts.pwd, "/<root>"),
|
||||||
|
nil,
|
||||||
|
{
|
||||||
|
paths = {},
|
||||||
|
resolvers = {},
|
||||||
|
modules = {},
|
||||||
|
env = opts.glob,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
root.paths[#root.paths + 1] = opts.pwd .. "/.tal_mod";
|
||||||
|
|
||||||
|
if opts.tal_path then
|
||||||
|
for i = 1, #opts.tal_path do
|
||||||
|
local p = opts.tal_path[i];
|
||||||
|
|
||||||
|
if p:match "^~/" then
|
||||||
|
root.paths[#root.paths + 1] = opts.join(opts.home, p:sub(2));
|
||||||
|
elseif p:match "^/" then
|
||||||
|
root.paths[#root.paths + 1] = opts.join(p);
|
||||||
|
else
|
||||||
|
root.paths[#root.paths + 1] = opts.join(opts.pwd, p);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.lua_path then
|
||||||
|
for v in opts.lua_path:gmatch "[^;]+" do
|
||||||
|
local match = v:match "^(.+)/%?%.lua$";
|
||||||
|
if match ~= nil and match ~= "." then
|
||||||
|
root.paths[#root.paths + 1] = match;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.resolvers then
|
||||||
|
root.resolvers[#root.resolvers + 1] = exports.file_resolver(opts.cwd, opts.join);
|
||||||
|
root.resolvers[#root.resolvers + 1] = exports.lua_resolver {
|
||||||
|
glob = opts.glob,
|
||||||
|
expose = true,
|
||||||
|
root = root,
|
||||||
|
}.resolver;
|
||||||
|
end
|
||||||
|
|
||||||
|
return root, run_loop;
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.init(opts, msg)
|
||||||
|
if opts.glob == nil then
|
||||||
|
---@diagnostic disable-next-line: deprecated
|
||||||
|
opts.glob = _ENV or _G or (getfenv and getfenv());
|
||||||
|
if opts.glob == nil then
|
||||||
|
error "Environment is not accessible";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local root = exports.mk_root {
|
||||||
|
tal_path = opts.tal_path,
|
||||||
|
lua_path = opts.lua_path or package.path,
|
||||||
|
resolvers = true,
|
||||||
|
evn_loop = true,
|
||||||
|
|
||||||
|
pwd = opts.pwd or opts.fs.pwd(),
|
||||||
|
home = opts.home or opts.fs.home(),
|
||||||
|
|
||||||
|
join = opts.join or opts.path.join,
|
||||||
|
cwd = opts.cwd or opts.path.cwd,
|
||||||
|
glob = opts.glob,
|
||||||
|
};
|
||||||
|
|
||||||
|
local require = root.require;
|
||||||
|
|
||||||
|
require "core"(opts.target_glob or root.env);
|
||||||
|
local run_loop = require "core.evn-loop";
|
||||||
|
root.env.promise = require "promise";
|
||||||
|
|
||||||
|
return root, run_loop;
|
||||||
|
end
|
||||||
|
|
||||||
|
return exports;
|
106
mod/args.lua
Normal file
106
mod/args.lua
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
---@param consumers table<string, (fun(arg: string): boolean?, boolean?) | string>
|
||||||
|
--- @param ... string
|
||||||
|
--- @returns nil
|
||||||
|
return function (consumers, ...)
|
||||||
|
local consumer_stack = array {};
|
||||||
|
local pass_args = false;
|
||||||
|
|
||||||
|
local function digest_arg(v)
|
||||||
|
local consumed = false;
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local el = consumer_stack:pop();
|
||||||
|
if el == nil then break end
|
||||||
|
|
||||||
|
local res, fin = el(v);
|
||||||
|
if res then
|
||||||
|
consumed = true;
|
||||||
|
end
|
||||||
|
if fin then
|
||||||
|
pass_args = true;
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
|
||||||
|
if fin or res then
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not consumed then
|
||||||
|
if consumers[1](v) then
|
||||||
|
pass_args = true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_consumer(name)
|
||||||
|
local consumer = name;
|
||||||
|
local path = {};
|
||||||
|
local n = 0;
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local curr = consumer;
|
||||||
|
if path[curr] then
|
||||||
|
local path_arr = array {};
|
||||||
|
|
||||||
|
for k, v in next, path do
|
||||||
|
path_arr[v] = k;
|
||||||
|
end
|
||||||
|
|
||||||
|
error("Alias to '" .. curr .. "' is recursive: " .. path_arr:join " -> ");
|
||||||
|
end
|
||||||
|
|
||||||
|
consumer = consumers[curr];
|
||||||
|
if consumer == nil then
|
||||||
|
local prefix;
|
||||||
|
if n == 0 then
|
||||||
|
prefix = "Unknown flag";
|
||||||
|
else
|
||||||
|
prefix = "Unknown alias";
|
||||||
|
end
|
||||||
|
|
||||||
|
if #curr == 1 then
|
||||||
|
error(prefix .. " '-" .. curr .. "'");
|
||||||
|
else
|
||||||
|
error(prefix .. " '--" .. curr .. "'");
|
||||||
|
end
|
||||||
|
elseif type(consumer) == "function" then
|
||||||
|
return consumer;
|
||||||
|
end
|
||||||
|
|
||||||
|
path[curr] = n;
|
||||||
|
n = n + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function next(...)
|
||||||
|
if ... == nil then
|
||||||
|
while #consumer_stack > 0 do
|
||||||
|
local el = consumer_stack:pop();
|
||||||
|
|
||||||
|
if el(nil) then
|
||||||
|
error("Unexpected end of arguments", 0);
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if pass_args then
|
||||||
|
consumers[1]((...));
|
||||||
|
elseif ... == "--" then
|
||||||
|
pass_args = true;
|
||||||
|
elseif (...):match "^%-%-" then
|
||||||
|
consumer_stack:push(get_consumer((...):sub(3)));
|
||||||
|
elseif (...):match "^%-" then
|
||||||
|
for c in (...):sub(2):gmatch "." do
|
||||||
|
consumer_stack:unshift(get_consumer(c));
|
||||||
|
end
|
||||||
|
else
|
||||||
|
digest_arg(...);
|
||||||
|
end
|
||||||
|
|
||||||
|
return next(select(2, ...));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return next(...);
|
||||||
|
end
|
183
mod/core/array.lua
Normal file
183
mod/core/array.lua
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
return function (glob)
|
||||||
|
--- @diagnostic disable: duplicate-set-field
|
||||||
|
|
||||||
|
--- @class arraylib
|
||||||
|
local arrays = {};
|
||||||
|
arrays.__index = arrays;
|
||||||
|
|
||||||
|
local function array(obj)
|
||||||
|
if type(obj) == "string" then
|
||||||
|
return obj:split "";
|
||||||
|
else
|
||||||
|
return arrays.mk(obj);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--- LuaLS is a piece of shit
|
||||||
|
--- @generic T
|
||||||
|
--- @param type `T`
|
||||||
|
--- @return fun(obj: T[]): array<T>
|
||||||
|
local function arrof(type)
|
||||||
|
return array;
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates an array
|
||||||
|
function arrays.mk(obj)
|
||||||
|
return setmetatable(obj, arrays);
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays.concat(...)
|
||||||
|
--- @diagnostic disable-next-line: missing-fields
|
||||||
|
return arrays.append(array {}, ...);
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:append(...)
|
||||||
|
local res = self;
|
||||||
|
local n = #res + 1;
|
||||||
|
|
||||||
|
for i = 1, select("#", ...) do
|
||||||
|
local curr = select(i, ...);
|
||||||
|
for j = 1, #curr do
|
||||||
|
res[n] = curr[j];
|
||||||
|
n = n + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return res;
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:push(...)
|
||||||
|
return self:append { ... };
|
||||||
|
end
|
||||||
|
function arrays:pop()
|
||||||
|
local res = self[#self];
|
||||||
|
self[#self] = nil;
|
||||||
|
return res;
|
||||||
|
end
|
||||||
|
function arrays:peek()
|
||||||
|
return self[#self];
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:shift()
|
||||||
|
return table.remove(self, 1);
|
||||||
|
end
|
||||||
|
function arrays:unshift(...)
|
||||||
|
local len = select("#", ...);
|
||||||
|
table.move(self, 1, #self, len + 1, self);
|
||||||
|
for i = 1, len do
|
||||||
|
self[i] = select(i, ...);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:map(f, mutate)
|
||||||
|
local out;
|
||||||
|
|
||||||
|
if mutate then
|
||||||
|
out = self;
|
||||||
|
else
|
||||||
|
out = array {};
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #self do
|
||||||
|
out[i] = f(self[i], i, self);
|
||||||
|
end
|
||||||
|
|
||||||
|
return out;
|
||||||
|
end
|
||||||
|
function arrays:flat_map(f)
|
||||||
|
local out = array {};
|
||||||
|
|
||||||
|
for i = 1, #self do
|
||||||
|
out:append(f(self[i], i, self));
|
||||||
|
end
|
||||||
|
|
||||||
|
return out;
|
||||||
|
end
|
||||||
|
function arrays:each(f)
|
||||||
|
for i = 1, #self do
|
||||||
|
f(self[i], i, self);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function arrays:sort(f, copy)
|
||||||
|
local target = self;
|
||||||
|
if copy then
|
||||||
|
target = array {}:append(self);
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(target, f);
|
||||||
|
return target;
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:find_i(f)
|
||||||
|
for i = 1, #self do
|
||||||
|
if f(self[i], i, self) then
|
||||||
|
return i;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function arrays:fill(val, b, e)
|
||||||
|
if b == nil then b = 1 end
|
||||||
|
if e == nil then e = self end
|
||||||
|
if b < 0 then b = #self + 1 - b end
|
||||||
|
if e < 0 then e = #self + 1 - e end
|
||||||
|
|
||||||
|
for i = b, e do
|
||||||
|
self[i] = val;
|
||||||
|
end
|
||||||
|
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:splice(b, e, ...)
|
||||||
|
-- TODO: optimize
|
||||||
|
if select("#") > 0 then
|
||||||
|
local n = e - b + 1;
|
||||||
|
|
||||||
|
while n > 0 do
|
||||||
|
table.remove(self, b);
|
||||||
|
n = n - 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, select("#") do
|
||||||
|
table.insert(self, b, (select(i, ...)));
|
||||||
|
end
|
||||||
|
|
||||||
|
return self;
|
||||||
|
else
|
||||||
|
local res = {};
|
||||||
|
|
||||||
|
for i = b, e do
|
||||||
|
table.insert(res, self[i]);
|
||||||
|
end
|
||||||
|
|
||||||
|
return res;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:slice(b, e)
|
||||||
|
b = b or 1;
|
||||||
|
e = e or #self;
|
||||||
|
local res = array {};
|
||||||
|
|
||||||
|
for i = b, e do
|
||||||
|
res:push(self[i]);
|
||||||
|
end
|
||||||
|
|
||||||
|
return res;
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:join(sep, b, e)
|
||||||
|
return table.concat(self, sep, b, e);
|
||||||
|
end
|
||||||
|
|
||||||
|
function arrays:__concat(other)
|
||||||
|
return arrays.concat(self, other);
|
||||||
|
end
|
||||||
|
|
||||||
|
glob.arrays = arrays;
|
||||||
|
glob.array = array;
|
||||||
|
glob.arrof = arrof;
|
||||||
|
end
|
234
mod/core/coro.lua
Normal file
234
mod/core/coro.lua
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
-- symmetric coroutines from the paper at
|
||||||
|
-- http://www.inf.puc-rio.br/~roberto/docs/corosblp.pdf
|
||||||
|
-- Written by Cosmin Apreutesei. Public Domain.
|
||||||
|
|
||||||
|
-- Reworked from the (in)famous coro lib
|
||||||
|
---@diagnostic disable: duplicate-set-field
|
||||||
|
|
||||||
|
local old_create = coroutine.create;
|
||||||
|
local old_close = coroutine.close;
|
||||||
|
local old_running = coroutine.running;
|
||||||
|
local old_status = coroutine.status;
|
||||||
|
local old_resume = coroutine.resume;
|
||||||
|
local old_yield = coroutine.yield;
|
||||||
|
--- @diagnostic disable-next-line: deprecated
|
||||||
|
|
||||||
|
local main, is_main = coroutine.running();
|
||||||
|
if main ~= nil and not is_main then
|
||||||
|
error "This library must be initialized in the main thread";
|
||||||
|
elseif main == nil then
|
||||||
|
main = old_create(function () end);
|
||||||
|
old_resume(main);
|
||||||
|
end
|
||||||
|
|
||||||
|
local threads = setmetatable({}, { __mode = "k" });
|
||||||
|
local resumers = setmetatable({}, { __mode = "k" });
|
||||||
|
local responsible = setmetatable({}, { __mode = "k" });
|
||||||
|
|
||||||
|
local current = main;
|
||||||
|
|
||||||
|
threads[main] = "main";
|
||||||
|
|
||||||
|
local function assert_thread(thread, level)
|
||||||
|
if type(thread) ~= "thread" then
|
||||||
|
local err = string.format("coroutine expected but %s given", type(thread));
|
||||||
|
error(err, level);
|
||||||
|
end
|
||||||
|
|
||||||
|
return thread;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unprotect(thread, ok, ...)
|
||||||
|
if not ok then
|
||||||
|
local s = debug.traceback(thread, (...));
|
||||||
|
s = string.gsub(s, "stack traceback:", tostring(thread) .. " stack traceback:");
|
||||||
|
error(s, 2);
|
||||||
|
end
|
||||||
|
return ...;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function finish(thread, ...)
|
||||||
|
local caller = resumers[thread];
|
||||||
|
if not caller then
|
||||||
|
error("coroutine ended without transferring control", 4);
|
||||||
|
end
|
||||||
|
return caller, true, ...;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function go(thread, arg_box)
|
||||||
|
while true do
|
||||||
|
current = thread
|
||||||
|
if thread == main then
|
||||||
|
-- transfer to the main thread: stop the scheduler.
|
||||||
|
return unbox(arg_box);
|
||||||
|
end
|
||||||
|
|
||||||
|
-- transfer to a coroutine: resume it and check the result.
|
||||||
|
arg_box = box(old_resume(thread, unbox(arg_box)));
|
||||||
|
|
||||||
|
if not arg_box[1] then
|
||||||
|
-- the coroutine finished with an error. pass the error back to the
|
||||||
|
-- caller thread, or to the main thread if there's no caller thread.
|
||||||
|
thread = resumers[thread] or main;
|
||||||
|
arg_box = box(arg_box[1], arg_box[2], debug.traceback());
|
||||||
|
else
|
||||||
|
-- loop over the next transfer request.
|
||||||
|
thread = arg_box[2];
|
||||||
|
arg_box = rebox(arg_box, 3);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local coro = {};
|
||||||
|
|
||||||
|
function coro.create(f)
|
||||||
|
local thread;
|
||||||
|
thread = old_create(function(ok, ...)
|
||||||
|
return finish(thread, f(...));
|
||||||
|
end);
|
||||||
|
responsible[thread] = current;
|
||||||
|
return thread;
|
||||||
|
end
|
||||||
|
|
||||||
|
function coro.running()
|
||||||
|
return current, current == main;
|
||||||
|
end
|
||||||
|
|
||||||
|
function coro.status(thread)
|
||||||
|
assert_thread(thread, 2);
|
||||||
|
return old_status(thread);
|
||||||
|
end
|
||||||
|
|
||||||
|
function coro.ptransfer(thread, ...)
|
||||||
|
assert(thread ~= current, "trying to transfer to the running thread");
|
||||||
|
|
||||||
|
if current ~= main then
|
||||||
|
-- we're inside a coroutine: signal the transfer request by yielding.
|
||||||
|
return old_yield(thread, true, ...);
|
||||||
|
else
|
||||||
|
-- we're in the main thread: start the scheduler.
|
||||||
|
local arg_box = box(true, ...);
|
||||||
|
|
||||||
|
while true do
|
||||||
|
current = thread;
|
||||||
|
|
||||||
|
if thread == main then
|
||||||
|
-- transfer to the main thread: stop the scheduler.
|
||||||
|
return unbox(arg_box);
|
||||||
|
end
|
||||||
|
|
||||||
|
-- transfer to a coroutine: resume it and check the result.
|
||||||
|
arg_box = box(old_resume(thread, unbox(arg_box)));
|
||||||
|
|
||||||
|
if not arg_box[1] then
|
||||||
|
-- the coroutine finished with an error. pass the error back to the
|
||||||
|
-- caller thread, or to the main thread if there's no caller thread.
|
||||||
|
thread = responsible[thread] or main;
|
||||||
|
arg_box = box(arg_box[1], arg_box[2], debug.traceback());
|
||||||
|
else
|
||||||
|
-- loop over the next transfer request.
|
||||||
|
thread = arg_box[2];
|
||||||
|
arg_box = rebox(arg_box, 3);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function coro.transfer(thread, ...)
|
||||||
|
-- print(current, ">", thread, ...);
|
||||||
|
return unprotect(thread, coro.ptransfer(thread, ...));
|
||||||
|
end
|
||||||
|
|
||||||
|
function coro.wrap(f)
|
||||||
|
local calling_thread, yielding_thread;
|
||||||
|
local function yield(...)
|
||||||
|
yielding_thread = current;
|
||||||
|
return coro.transfer(calling_thread, ...);
|
||||||
|
end
|
||||||
|
local function finish(...)
|
||||||
|
yielding_thread = nil;
|
||||||
|
return coro.transfer(calling_thread, ...);
|
||||||
|
end
|
||||||
|
local function wrapper(...)
|
||||||
|
return finish(f(yield, ...));
|
||||||
|
end
|
||||||
|
|
||||||
|
local thread = coro.create(wrapper);
|
||||||
|
yielding_thread = thread;
|
||||||
|
|
||||||
|
return function(...)
|
||||||
|
resumers[thread] = calling_thread;
|
||||||
|
calling_thread = current;
|
||||||
|
assert(yielding_thread, "cannot transfer to dead coroutine");
|
||||||
|
return coro.transfer(yielding_thread, ...);
|
||||||
|
end, thread;
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @generic T, T1, T2, T3, T4, T5, Args
|
||||||
|
--- @param f fun(yield: (fun(p1?: T1, p2?: T2, p3?: T3, p4?: T4, p5?: T5): ...), ...: Args)
|
||||||
|
--- @return fun(...: Args): fun(...): T1, T2, T3, T4, T5
|
||||||
|
function coro.gen(f)
|
||||||
|
return function (...)
|
||||||
|
local prev = box(...);
|
||||||
|
|
||||||
|
return coro.wrap(function (yield)
|
||||||
|
return f(yield, unbox(prev));
|
||||||
|
end);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function coro.close(t)
|
||||||
|
if old_close == nil then
|
||||||
|
return false, "Closing coroutines is not supported";
|
||||||
|
else
|
||||||
|
return old_close(t);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local coroutine = {};
|
||||||
|
|
||||||
|
function coroutine.close(co)
|
||||||
|
coro.close(co);
|
||||||
|
end
|
||||||
|
function coroutine.create(f)
|
||||||
|
return coro.create(f);
|
||||||
|
end
|
||||||
|
|
||||||
|
function coroutine.isyieldable()
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function coroutine.resume(co, ...)
|
||||||
|
resumers[co] = coro.running();
|
||||||
|
responsible[co] = coro.running();
|
||||||
|
return coro.ptransfer(co, ...);
|
||||||
|
end
|
||||||
|
|
||||||
|
function coroutine.yield(...)
|
||||||
|
local cb = resumers[coro.running()];
|
||||||
|
if cb == nil then
|
||||||
|
error("Must call 'yield' from a thread that has been invoked asymmetrically", 2);
|
||||||
|
end
|
||||||
|
|
||||||
|
return coro.transfer(cb, ...);
|
||||||
|
end
|
||||||
|
|
||||||
|
function coroutine.wrap(f)
|
||||||
|
local co = coroutine.create(f);
|
||||||
|
|
||||||
|
return function (...)
|
||||||
|
return assert(coroutine.resume(co, ...));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function coroutine.running()
|
||||||
|
return coro.running();
|
||||||
|
end
|
||||||
|
|
||||||
|
function coroutine.status(co)
|
||||||
|
return coro.status(co);
|
||||||
|
end
|
||||||
|
|
||||||
|
return function (glob)
|
||||||
|
glob.coroutine = coroutine;
|
||||||
|
glob.coro = coro;
|
||||||
|
end
|
44
mod/core/env.lua
Normal file
44
mod/core/env.lua
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
local env = {};
|
||||||
|
|
||||||
|
local has_jit, jit = pcall(require, "jit");
|
||||||
|
|
||||||
|
if has_jit then
|
||||||
|
env.jit = true;
|
||||||
|
env.os = jit.os;
|
||||||
|
env.runtime = jit.version;
|
||||||
|
env.arch = jit.arch;
|
||||||
|
else
|
||||||
|
env.jit = false;
|
||||||
|
print "NOT RUNNING IN LUAJIT!!";
|
||||||
|
print "Some libraries might depend on ffi. Continue at your own risk!";
|
||||||
|
|
||||||
|
env.jit = false;
|
||||||
|
env.os = "unknown";
|
||||||
|
env.arch = "unknown";
|
||||||
|
|
||||||
|
local function extract_os()
|
||||||
|
if not io.popen then return end
|
||||||
|
|
||||||
|
local uname_f = io.popen("uname -ms");
|
||||||
|
if uname_f ~= nil then
|
||||||
|
local os, arch = uname_f:read "*a":sub(1, -2):match "([^%s]+) ([^%s]+)";
|
||||||
|
|
||||||
|
env.os = os;
|
||||||
|
env.arch = arch;
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local os_name = os.getenv "OS";
|
||||||
|
if os_name == "Windows_NT" then
|
||||||
|
env.os = "windows";
|
||||||
|
env.arch = os.getenv "PROCESSOR_ARCHITECTURE";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
extract_os();
|
||||||
|
env.runtime = _VERSION;
|
||||||
|
end
|
||||||
|
|
||||||
|
return function (glob)
|
||||||
|
glob.env = env;
|
||||||
|
end
|
55
mod/core/evn-loop.lua
Normal file
55
mod/core/evn-loop.lua
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
local resolver = require "lua-resolver";
|
||||||
|
|
||||||
|
local stack = array {};
|
||||||
|
local active;
|
||||||
|
|
||||||
|
local exports = {};
|
||||||
|
|
||||||
|
--- @param ... fun()
|
||||||
|
function exports.push(...)
|
||||||
|
stack:push(...);
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.interrupt()
|
||||||
|
if coro.running() == active then
|
||||||
|
-- We are trying to interrupt the currently active event loop thread
|
||||||
|
-- We will transfer away from it and "abandon" it (leave it for non-event loop use)
|
||||||
|
return coro.transfer(exports.main);
|
||||||
|
else
|
||||||
|
-- We are currently in an abandoned or a non-loop thread. We can calmly transfer to the active thread
|
||||||
|
return coro.transfer(active);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param main fun(...)
|
||||||
|
--- @param ... any
|
||||||
|
function exports.run(main, ...)
|
||||||
|
if active then
|
||||||
|
error "Event loop is already running!";
|
||||||
|
end
|
||||||
|
exports.main = coro.running();
|
||||||
|
|
||||||
|
local args = box(...);
|
||||||
|
exports.push(function ()
|
||||||
|
main(unbox(args));
|
||||||
|
end)
|
||||||
|
|
||||||
|
while #stack > 0 do
|
||||||
|
active = coro.create(function ()
|
||||||
|
while #stack > 0 and coro.running() == active do
|
||||||
|
local msg = stack:shift();
|
||||||
|
msg();
|
||||||
|
end
|
||||||
|
|
||||||
|
coro.transfer(exports.main);
|
||||||
|
end);
|
||||||
|
|
||||||
|
coro.transfer(active);
|
||||||
|
print "Abandoned thread...";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Register the module as phony
|
||||||
|
resolver.register("evn-loop.lua", exports);
|
||||||
|
|
||||||
|
return exports;
|
57
mod/core/function.lua
Normal file
57
mod/core/function.lua
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
local unpack = unpack or table.unpack;
|
||||||
|
|
||||||
|
--- @class functions
|
||||||
|
local functions = {};
|
||||||
|
functions.__index = functions;
|
||||||
|
|
||||||
|
--- Constructs a function, such that it calls the first function with the passed arguments,
|
||||||
|
--- the second function with the return of the first, and so on. The return value of the last function is returned
|
||||||
|
---
|
||||||
|
--- In short, does the following, if the passed functions are a, b and c: return c(b(a(...)))
|
||||||
|
---
|
||||||
|
--- Sometimes less cumbersome to write (a | b | c | d)(args...) than d(c(b(a(args...))))
|
||||||
|
--- @param self function
|
||||||
|
function functions:pipe(...)
|
||||||
|
if ... == nil then
|
||||||
|
return self;
|
||||||
|
else
|
||||||
|
local next = ...;
|
||||||
|
|
||||||
|
return functions.pipe(function (...)
|
||||||
|
return next(self(...));
|
||||||
|
end, select(2, ...));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function functions:apply(args)
|
||||||
|
return self(unpack(args));
|
||||||
|
end
|
||||||
|
--- Constructs a function, such that it calls the first function with the passed arguments,
|
||||||
|
--- the second function with the return of the first, and so on. The return value of the last function is returned
|
||||||
|
---
|
||||||
|
--- In short, does the following, if the passed functions are a, b and c: return c(b(a(...)))
|
||||||
|
---
|
||||||
|
--- Sometimes less cumbersome to write (a | b | c | d)(args...) than d(c(b(a(args...))))
|
||||||
|
--- @param self function
|
||||||
|
function functions:pcall(...)
|
||||||
|
return pcall(self, ...);
|
||||||
|
end
|
||||||
|
--- Calls pipe with a and b; Alternative syntax for older Lua installation
|
||||||
|
function functions.__sub(a, b)
|
||||||
|
return functions.pipe(a, b);
|
||||||
|
end
|
||||||
|
--- Calls pipe with a and b
|
||||||
|
function functions.__bor(a, b)
|
||||||
|
return functions.pipe(a, b);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return function (glob)
|
||||||
|
-- It's not vital to have this metatable, so we will just try our very best
|
||||||
|
if debug then
|
||||||
|
debug.setmetatable(debug.setmetatable, functions);
|
||||||
|
end
|
||||||
|
|
||||||
|
glob.functions = functions;
|
||||||
|
glob["function"] = functions;
|
||||||
|
end
|
||||||
|
|
10
mod/core/init.lua
Normal file
10
mod/core/init.lua
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
return function (glob)
|
||||||
|
require ".polyfills"(glob);
|
||||||
|
require ".array"(glob);
|
||||||
|
require ".coro"(glob);
|
||||||
|
require ".env"(glob);
|
||||||
|
require ".function"(glob);
|
||||||
|
require ".printing"(glob);
|
||||||
|
require ".string"(glob);
|
||||||
|
require ".utils"(glob);
|
||||||
|
end
|
72
mod/core/polyfills.lua
Normal file
72
mod/core/polyfills.lua
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
return function (glob)
|
||||||
|
if glob.getfenv then
|
||||||
|
glob._ENV = glob.getfenv();
|
||||||
|
glob._G = _ENV;
|
||||||
|
else
|
||||||
|
glob._G = _ENV;
|
||||||
|
|
||||||
|
if debug.getupvalue then
|
||||||
|
local getupvalue = debug.getupvalue;
|
||||||
|
function glob.getfenv(func)
|
||||||
|
return getupvalue(func, 1);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function glob.getfenv()
|
||||||
|
error("getfenv is not supported", 2);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not glob.setfenv then
|
||||||
|
if glob.debug.setupvalue then
|
||||||
|
local setupvalue = glob.debug.setupvalue;
|
||||||
|
|
||||||
|
if glob.debug.getinfo then
|
||||||
|
local getinfo = glob.debug.getinfo;
|
||||||
|
|
||||||
|
function glob.setfenv(func, val)
|
||||||
|
setupvalue(func, 1, val);
|
||||||
|
if type(func) == "function" then
|
||||||
|
return func;
|
||||||
|
else
|
||||||
|
return getinfo(func, "f");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function glob.setfenv(func, val)
|
||||||
|
setupvalue(func, 1, val);
|
||||||
|
return func;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function glob.setfenv()
|
||||||
|
error("setfenv is not supported", 2);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reimplement this for lua <5.1
|
||||||
|
if glob.table.move == nil then
|
||||||
|
--- @diagnostic disable: duplicate-set-field
|
||||||
|
function glob.table.move(src, src_b, src_e, dst_b, dst)
|
||||||
|
if dst == nil then dst = src end
|
||||||
|
local offset = dst_b - src_b;
|
||||||
|
|
||||||
|
if dst_b < src_b then
|
||||||
|
for i = src_e, src_b, -1 do
|
||||||
|
dst[i + offset] = src[i];
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i = src_b, src_e do
|
||||||
|
dst[i + offset] = src[i];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return dst;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Why did we *remove* this from the spec again? Classical PUC
|
||||||
|
glob.unpack = table.unpack or unpack;
|
||||||
|
glob.table.unpack = unpack;
|
||||||
|
end
|
141
mod/core/printing.lua
Normal file
141
mod/core/printing.lua
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
local function escape_str(s)
|
||||||
|
local in_char = { "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" };
|
||||||
|
local out_char = { "\\", "\"", "/", "b", "f", "n", "r", "t" };
|
||||||
|
for i, c in ipairs(in_char) do
|
||||||
|
s = s:gsub(c, "\\" .. out_char[i]);
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stringify_impl(obj, indent_str, n, passed)
|
||||||
|
local parts = {};
|
||||||
|
local kind = type(obj);
|
||||||
|
|
||||||
|
if kind == "table" then
|
||||||
|
if passed[obj] then return "<circular>" end
|
||||||
|
passed[obj] = true;
|
||||||
|
|
||||||
|
local len = #obj;
|
||||||
|
|
||||||
|
for i = 1, len do
|
||||||
|
parts[i] = stringify_impl(obj[i], indent_str, n + 1, passed) .. ",";
|
||||||
|
end
|
||||||
|
|
||||||
|
local keys = {};
|
||||||
|
|
||||||
|
for k, v in pairs(obj) do
|
||||||
|
if type(k) ~= "number" or k > len then
|
||||||
|
keys[#keys + 1] = { k, v };
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(keys, function (a, b)
|
||||||
|
if type(a[1]) == "number" and type(b[1]) == "number" then
|
||||||
|
return a[1] < b[1];
|
||||||
|
elseif type(a[1]) == "string" and type(b[1]) == "string" then
|
||||||
|
return a[1] < b[1];
|
||||||
|
else
|
||||||
|
return type(a[1]) < type(b[1]) or tostring(a[1]) < tostring(b[1]);
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
|
||||||
|
for i = 1, #keys do
|
||||||
|
local k = keys[i][1];
|
||||||
|
local v = keys[i][2];
|
||||||
|
|
||||||
|
local val = stringify_impl(v, indent_str, n + 1, passed);
|
||||||
|
if val ~= nil then
|
||||||
|
if type(k) == "string" then
|
||||||
|
parts[#parts + 1] = table.concat { k, ": ", val, "," };
|
||||||
|
else
|
||||||
|
parts[#parts + 1] = table.concat { "[", stringify_impl(k, indent_str, n + 1, passed), "]: ", val, "," };
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local meta = getmetatable(obj);
|
||||||
|
if meta ~= nil and meta ~= arrays then
|
||||||
|
parts[#parts + 1] = "<meta> = " .. stringify_impl(meta, indent_str, n + 1, passed) .. ",";
|
||||||
|
end
|
||||||
|
|
||||||
|
passed[obj] = false;
|
||||||
|
|
||||||
|
if #parts == 0 then
|
||||||
|
if meta == arrays then
|
||||||
|
return "[]";
|
||||||
|
else
|
||||||
|
return "{}";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local contents = table.concat(parts, " "):sub(1, -2);
|
||||||
|
if #contents > 80 then
|
||||||
|
local indent = "\n" .. string.rep(indent_str, n + 1);
|
||||||
|
|
||||||
|
contents = table.concat {
|
||||||
|
indent,
|
||||||
|
table.concat(parts, indent),
|
||||||
|
"\n", string.rep(indent_str, n)
|
||||||
|
};
|
||||||
|
else
|
||||||
|
contents = " " .. contents .. " ";
|
||||||
|
end
|
||||||
|
|
||||||
|
if meta == arrays then
|
||||||
|
return table.concat { "[", contents, "]" };
|
||||||
|
else
|
||||||
|
return table.concat { "{", contents, "}" };
|
||||||
|
end
|
||||||
|
elseif kind == "string" then
|
||||||
|
return "\"" .. escape_str(obj) .. "\"";
|
||||||
|
elseif kind == "function" then
|
||||||
|
local data = debug.getinfo(obj, "S");
|
||||||
|
return table.concat { tostring(obj), " @ ", data.short_src, ":", data.linedefined };
|
||||||
|
elseif kind == "nil" then
|
||||||
|
return "nil";
|
||||||
|
else
|
||||||
|
return tostring(obj);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Turns the given value to a human-readable string.
|
||||||
|
--- Should be used only for debugging and display purposes
|
||||||
|
local function to_readable(obj, indent_str)
|
||||||
|
return stringify_impl(obj, indent_str or " ", 0, {});
|
||||||
|
end
|
||||||
|
|
||||||
|
local function print(...)
|
||||||
|
for i = 1, select("#", ...) do
|
||||||
|
if i > 1 then
|
||||||
|
io.stderr:write("\t");
|
||||||
|
end
|
||||||
|
|
||||||
|
io.stderr:write(tostring((select(i, ...))));
|
||||||
|
end
|
||||||
|
|
||||||
|
io.stderr:write("\n");
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Prints the given values in a human-readable manner to stderr
|
||||||
|
--- Should be used only for debugging
|
||||||
|
local function pprint(...)
|
||||||
|
if select("#", ...) == 0 then
|
||||||
|
io.stderr:write "<nothing>\n";
|
||||||
|
else
|
||||||
|
for i = 1, select("#", ...) do
|
||||||
|
if i > 1 then
|
||||||
|
io.stderr:write "\t";
|
||||||
|
end
|
||||||
|
|
||||||
|
io.stderr:write(to_readable((select(i, ...))));
|
||||||
|
end
|
||||||
|
|
||||||
|
io.stderr:write "\n";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return function (glob)
|
||||||
|
glob.to_readable = to_readable;
|
||||||
|
glob.print = print;
|
||||||
|
glob.pprint = pprint;
|
||||||
|
end
|
70
mod/core/string.lua
Normal file
70
mod/core/string.lua
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
--- @diagnostic disable: duplicate-set-field
|
||||||
|
|
||||||
|
return function (glob)
|
||||||
|
--- @param self string
|
||||||
|
function glob.string:split(sep)
|
||||||
|
sep = sep or "";
|
||||||
|
local lines = array {};
|
||||||
|
local pos = 1;
|
||||||
|
|
||||||
|
if sep == "" then
|
||||||
|
for i = 1, #self do
|
||||||
|
lines:push(self:sub(1, 1));
|
||||||
|
end
|
||||||
|
else
|
||||||
|
while true do
|
||||||
|
local b, e = self:find(sep, pos);
|
||||||
|
|
||||||
|
if not b then
|
||||||
|
table.insert(lines, self:sub(pos));
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
table.insert(lines, self:sub(pos, b - 1));
|
||||||
|
pos = e + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
end
|
||||||
|
--- @param self string
|
||||||
|
function glob.string:at(i)
|
||||||
|
return self:sub(i, i);
|
||||||
|
end
|
||||||
|
--- @param self string
|
||||||
|
function glob.string:replace_first(old, new)
|
||||||
|
local b, e = self:find(old, 1, true);
|
||||||
|
|
||||||
|
if b == nil then
|
||||||
|
return self;
|
||||||
|
else
|
||||||
|
return self:sub(1, b - 1) .. new .. self:sub(e + 1);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param self string
|
||||||
|
function glob.string:quote()
|
||||||
|
return ("%q"):format(self);
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param self string
|
||||||
|
function glob.string:quotesh()
|
||||||
|
return "'" .. self
|
||||||
|
:gsub("*", "\\*")
|
||||||
|
:gsub("?", "\\?")
|
||||||
|
:gsub("~", "\\~")
|
||||||
|
:gsub("$", "\\$")
|
||||||
|
:gsub("&", "\\&")
|
||||||
|
:gsub("|", "\\|")
|
||||||
|
:gsub(";", "\\;")
|
||||||
|
:gsub("<", "\\<")
|
||||||
|
:gsub(">", "\\>")
|
||||||
|
:gsub("(", "\\)")
|
||||||
|
:gsub("[", "\\]")
|
||||||
|
:gsub("{", "\\}")
|
||||||
|
:gsub("%\\", "\\\\")
|
||||||
|
:gsub("\'", "\\\'")
|
||||||
|
:gsub("\"", "\\\"")
|
||||||
|
:gsub("`", "\\`") .. "'";
|
||||||
|
end
|
||||||
|
end
|
72
mod/core/utils.lua
Normal file
72
mod/core/utils.lua
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
return function (glob)
|
||||||
|
function glob.box(...)
|
||||||
|
return { n = select("#", ...), ... };
|
||||||
|
end
|
||||||
|
function glob.unbox(obj, s, e)
|
||||||
|
return unpack(obj, s or 1, e or obj.n);
|
||||||
|
end
|
||||||
|
function glob.rebox(obj, s, e)
|
||||||
|
return box(unpack(obj, s or 1, e or obj.n));
|
||||||
|
end
|
||||||
|
|
||||||
|
function glob.exit()
|
||||||
|
os.exit(0);
|
||||||
|
end
|
||||||
|
|
||||||
|
function glob.str(...)
|
||||||
|
return table.concat { ... };
|
||||||
|
end
|
||||||
|
|
||||||
|
function glob.iterate(obj)
|
||||||
|
local i = 0;
|
||||||
|
|
||||||
|
return function()
|
||||||
|
i = i + 1;
|
||||||
|
return obj[i];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Prepares the object to be a class - puts an __index member in it pointing to the object itself
|
||||||
|
--- @generic T
|
||||||
|
--- @param obj T
|
||||||
|
--- @return T | { __index: T }
|
||||||
|
function glob.class(obj)
|
||||||
|
--- @diagnostic disable-next-line: inject-field
|
||||||
|
obj.__index = obj;
|
||||||
|
return obj;
|
||||||
|
end
|
||||||
|
|
||||||
|
function glob.try(try_fn, catch_fn, finally_fn)
|
||||||
|
if not try_fn then
|
||||||
|
if finally_fn then
|
||||||
|
return finally_fn();
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local function handle_catch(...)
|
||||||
|
if finally_fn then
|
||||||
|
finally_fn();
|
||||||
|
end
|
||||||
|
|
||||||
|
return ...;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handle_try(ok, ...)
|
||||||
|
if ok then
|
||||||
|
if finally_fn then
|
||||||
|
finally_fn();
|
||||||
|
end
|
||||||
|
|
||||||
|
return ...;
|
||||||
|
else
|
||||||
|
if catch_fn then
|
||||||
|
return handle_catch(catch_fn(...));
|
||||||
|
elseif finally_fn then
|
||||||
|
return finally_fn();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return handle_try(pcall(try_fn));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
23
mod/fs.lua
Normal file
23
mod/fs.lua
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
local exports = {};
|
||||||
|
|
||||||
|
function exports.exists(path)
|
||||||
|
local f = io.open(path, "r");
|
||||||
|
if f == nil then return false end
|
||||||
|
if f:read(1) == nil then return false end
|
||||||
|
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.home()
|
||||||
|
return os.getenv "HOME";
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.pwd()
|
||||||
|
return os.getenv "PWD" or "./";
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.ls(path)
|
||||||
|
return os.execute("ls ")
|
||||||
|
end
|
||||||
|
|
||||||
|
return exports;
|
245
mod/json.lua
Normal file
245
mod/json.lua
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
local exports = {};
|
||||||
|
|
||||||
|
-- We can have arbitrary promises in the middle of the module
|
||||||
|
-- NO MORE ASYNC AWAIT MADNESS
|
||||||
|
|
||||||
|
promise.resolve():await();
|
||||||
|
|
||||||
|
local function kind_of(obj)
|
||||||
|
if type(obj) ~= "table" then return type(obj) end
|
||||||
|
|
||||||
|
local i = 1;
|
||||||
|
for _ in pairs(obj) do
|
||||||
|
if obj[i] ~= nil then
|
||||||
|
i = i + 1;
|
||||||
|
else
|
||||||
|
return "table";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if i == 1 then
|
||||||
|
return "table";
|
||||||
|
else
|
||||||
|
return "array";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function escape_str(s)
|
||||||
|
local in_char = { "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" };
|
||||||
|
local out_char = { "\\", "\"", "/", "b", "f", "n", "r", "t" };
|
||||||
|
for i, c in ipairs(in_char) do
|
||||||
|
s = s:gsub(c, "\\" .. out_char[i]);
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns pos, did_find; there are two cases:
|
||||||
|
-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true.
|
||||||
|
-- 2. Delimiter not found: pos = pos after leading space; did_find = false.
|
||||||
|
-- This throws an error if err_if_missing is true and the delim is not found.
|
||||||
|
local function skip_delim(str, pos, delim, err_if_missing)
|
||||||
|
pos = string.find(str, "%S", pos) or pos;
|
||||||
|
|
||||||
|
if string.sub(str, pos, pos) ~= delim then
|
||||||
|
if err_if_missing then
|
||||||
|
error(table.concat { "Expected ", delim, " near position ", pos });
|
||||||
|
end
|
||||||
|
|
||||||
|
return pos, false;
|
||||||
|
end
|
||||||
|
|
||||||
|
return pos + 1, true;
|
||||||
|
end
|
||||||
|
|
||||||
|
local esc_map = { b = "\b", f = "\f", n = "\n", r = "\r", t = "\t", v = "\v" };
|
||||||
|
|
||||||
|
-- Expects the given pos to be the first character after the opening quote.
|
||||||
|
-- Returns val, pos; the returned pos is after the closing quote character.
|
||||||
|
local function parse_str_val(str, pos, check)
|
||||||
|
if pos > #str then error("End of input found while parsing string") end
|
||||||
|
|
||||||
|
if check then
|
||||||
|
if string.sub(str, pos, pos) ~= "\"" then
|
||||||
|
return nil, pos;
|
||||||
|
else
|
||||||
|
pos = pos + 1;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
pos = pos + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
local res = {};
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local c = string.sub(str, pos, pos);
|
||||||
|
if c == "\"" then
|
||||||
|
return table.concat(res), pos + 1;
|
||||||
|
elseif c == "\\" then
|
||||||
|
c = string.sub(str, pos + 1, pos + 1);
|
||||||
|
res[#res + 1] = esc_map[c] or c;
|
||||||
|
pos = pos + 2;
|
||||||
|
else
|
||||||
|
res[#res + 1] = c;
|
||||||
|
pos = pos + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns val, pos; the returned pos is after the number's final character.
|
||||||
|
local function parse_num_val(str, pos)
|
||||||
|
local num_str = string.match(str, "^-?%d+%.?%d*[eE]?[+-]?%d*", pos);
|
||||||
|
local val = tonumber(num_str);
|
||||||
|
|
||||||
|
if not val then error(table.concat { "Error parsing number at position ", pos, "." }) end
|
||||||
|
return val, pos + #num_str;
|
||||||
|
end
|
||||||
|
|
||||||
|
local json_end = { "eof" };
|
||||||
|
|
||||||
|
local function parse_impl(str, pos, end_delim)
|
||||||
|
pos = pos or 1;
|
||||||
|
if pos > #str then error("Reached unexpected end of input") end
|
||||||
|
|
||||||
|
pos = str:find("%S", pos) or pos;
|
||||||
|
local c = str:sub(pos, pos);
|
||||||
|
local delim_found;
|
||||||
|
|
||||||
|
if c == "{" then
|
||||||
|
pos = pos + 1;
|
||||||
|
|
||||||
|
local key;
|
||||||
|
local obj = {};
|
||||||
|
|
||||||
|
c = string.sub(str, pos, pos);
|
||||||
|
if c == "}" then
|
||||||
|
return obj, pos
|
||||||
|
else
|
||||||
|
while true do
|
||||||
|
key, pos = parse_str_val(str, pos, true);
|
||||||
|
if key == nil then error("Expected a string key") end
|
||||||
|
|
||||||
|
pos = skip_delim(str, pos, ":", true); -- true -> error if missing.
|
||||||
|
obj[key], pos = parse_impl(str, pos);
|
||||||
|
|
||||||
|
pos, delim_found = skip_delim(str, pos, "}");
|
||||||
|
if delim_found then return obj, pos end
|
||||||
|
|
||||||
|
pos, delim_found = skip_delim(str, pos, ",");
|
||||||
|
if not delim_found then error("Expected semicolon or comma") end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif c == "[" then
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
local arr = array {};
|
||||||
|
local val;
|
||||||
|
local delim_found = true;
|
||||||
|
|
||||||
|
while true do
|
||||||
|
val, pos = parse_impl(str, pos, "]");
|
||||||
|
if val == json_end then return arr, pos end
|
||||||
|
if not delim_found then error("Comma missing between array items: " .. str:sub(pos, pos + 25)) end
|
||||||
|
|
||||||
|
arr:push(val);
|
||||||
|
pos, delim_found = skip_delim(str, pos, ",");
|
||||||
|
end
|
||||||
|
elseif c == "\"" then -- Parse a string.
|
||||||
|
return parse_str_val(str, pos, false);
|
||||||
|
elseif c == "-" or c:find("%d") then -- Parse a number.
|
||||||
|
return parse_num_val(str, pos);
|
||||||
|
elseif c == end_delim then -- End of an object or array.
|
||||||
|
return json_end, pos + 1, true;
|
||||||
|
elseif str:sub(pos, pos + 3) == "null" then
|
||||||
|
return nil, pos + 4;
|
||||||
|
elseif str:sub(pos, pos + 3) == "true" then
|
||||||
|
return true, pos + 4;
|
||||||
|
elseif str:sub(pos, pos + 4) == "false" then
|
||||||
|
return true, pos + 5;
|
||||||
|
else
|
||||||
|
error(table.concat { "Invalid json syntax starting at position ", pos, ": ", str:sub(pos, pos + 10) });
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stringify_impl(obj, all, indent_str, n)
|
||||||
|
local s = {}; -- We'll build the string as an array of strings to be concatenated.
|
||||||
|
local kind = kind_of(obj); -- This is 'array' if it's an array or type(obj) otherwise.
|
||||||
|
|
||||||
|
if kind == "array" then
|
||||||
|
for i, val in ipairs(obj) do
|
||||||
|
s[i] = stringify_impl(val, all, indent_str, n + 1);
|
||||||
|
end
|
||||||
|
|
||||||
|
if not indent_str then
|
||||||
|
return "[" .. table.concat(s, ",") .. "]";
|
||||||
|
elseif #s == 0 then
|
||||||
|
return "[]";
|
||||||
|
else
|
||||||
|
local indent = "\n" .. string.rep(indent_str, n + 1);
|
||||||
|
return table.concat {
|
||||||
|
"[",
|
||||||
|
indent, table.concat(s, "," .. indent),
|
||||||
|
"\n", string.rep(indent_str, n), "]"
|
||||||
|
};
|
||||||
|
end
|
||||||
|
elseif kind == "table" then
|
||||||
|
for k, v in pairs(obj) do
|
||||||
|
local sep = indent_str and ": " or ":";
|
||||||
|
local val = stringify_impl(v, all, indent_str, n + 1);
|
||||||
|
if val ~= nil then
|
||||||
|
if type(k) == "string" then
|
||||||
|
s[#s + 1] = stringify_impl(k) .. sep .. val;
|
||||||
|
elseif type(k) == "number" then
|
||||||
|
s[#s + 1] = "\"" .. k .. "\"" .. sep .. val;
|
||||||
|
elseif type(k) == "boolean" then
|
||||||
|
s[#s + 1] = "\"" .. k .. "\"" .. sep .. val;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not indent_str then
|
||||||
|
return "{" .. table.concat(s, ",") .. "}";
|
||||||
|
elseif #s == 0 then
|
||||||
|
return "{}";
|
||||||
|
else
|
||||||
|
local indent = "\n" .. string.rep(indent_str, n + 1);
|
||||||
|
return table.concat {
|
||||||
|
"{",
|
||||||
|
indent, table.concat(s, "," .. indent),
|
||||||
|
"\n", string.rep(indent_str, n), "}"
|
||||||
|
};
|
||||||
|
end
|
||||||
|
return "{" .. table.concat(s, ",") .. "}";
|
||||||
|
elseif kind == "string" then
|
||||||
|
return "\"" .. escape_str(obj) .. "\"";
|
||||||
|
elseif kind == "number" then
|
||||||
|
return tostring(obj);
|
||||||
|
elseif kind == 'boolean' then
|
||||||
|
return tostring(obj);
|
||||||
|
elseif kind == "nil" then
|
||||||
|
return "null";
|
||||||
|
elseif all then
|
||||||
|
return tostring(obj);
|
||||||
|
else
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.stringify(obj, indent_str)
|
||||||
|
if indent_str == true then
|
||||||
|
indent_str = " ";
|
||||||
|
end
|
||||||
|
return stringify_impl(obj, false, indent_str, 0);
|
||||||
|
end
|
||||||
|
function exports.pretty(obj)
|
||||||
|
return stringify_impl(obj, true, " ", 0);
|
||||||
|
end
|
||||||
|
exports.null = {}; -- This is a one-off table to represent the null value.
|
||||||
|
|
||||||
|
---@param str string
|
||||||
|
---@return unknown
|
||||||
|
function exports.parse(str)
|
||||||
|
local obj = parse_impl(str, 1);
|
||||||
|
return obj;
|
||||||
|
end
|
||||||
|
|
||||||
|
return exports;
|
160
mod/path.lua
Normal file
160
mod/path.lua
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
---@diagnostic disable: cast-local-type
|
||||||
|
local exports = {};
|
||||||
|
|
||||||
|
---@param ... string
|
||||||
|
---@return array<string> parts
|
||||||
|
---@return integer? i
|
||||||
|
---@return boolean dir
|
||||||
|
function exports.split(...)
|
||||||
|
--- @type integer | nil
|
||||||
|
local i = 0;
|
||||||
|
--- @type string[]
|
||||||
|
local res = {};
|
||||||
|
local dir = false;
|
||||||
|
|
||||||
|
local _, start = (... or ""):find("^/+");
|
||||||
|
if start == nil then
|
||||||
|
start = 1;
|
||||||
|
else
|
||||||
|
i = nil;
|
||||||
|
dir = true;
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, select("#", ...) do
|
||||||
|
local p = select(i, ...);
|
||||||
|
|
||||||
|
if p ~= nil then
|
||||||
|
for part, slashes in p:gmatch("([^/]+)(/*)", start) do
|
||||||
|
dir = #slashes > 0;
|
||||||
|
if part == ".." then
|
||||||
|
if #res == 0 then
|
||||||
|
if i ~= nil then
|
||||||
|
i = i + 1;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
res[#res] = nil;
|
||||||
|
end
|
||||||
|
elseif part ~= "." and part ~= "" then
|
||||||
|
res[#res + 1] = part;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return res, i, dir;
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param parts array<string>
|
||||||
|
---@param i integer?
|
||||||
|
---@param dir boolean | "all"
|
||||||
|
function exports.stringify(parts, i, dir)
|
||||||
|
local a = table.concat(parts, "/");
|
||||||
|
|
||||||
|
if #parts == 0 then
|
||||||
|
if i == nil then
|
||||||
|
return dir and "/" or "/.";
|
||||||
|
elseif i == 0 then
|
||||||
|
return dir and "./" or "";
|
||||||
|
else
|
||||||
|
local res = (".."):rep(i, "/");
|
||||||
|
return dir and res .. "/" or res;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local res = i and ("../"):rep(i) or "/";
|
||||||
|
|
||||||
|
if dir then
|
||||||
|
return res .. a .. "/";
|
||||||
|
else
|
||||||
|
return res .. a;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param ... string
|
||||||
|
---@return string path
|
||||||
|
---@return boolean dir
|
||||||
|
function exports.join(...)
|
||||||
|
local parts, i, dir = exports.split(...);
|
||||||
|
return exports.stringify(parts, i, dir), dir;
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param ... string
|
||||||
|
---@return string
|
||||||
|
function exports.join_dir(...)
|
||||||
|
local p, i = exports.split(...);
|
||||||
|
return exports.stringify(p, i, true);
|
||||||
|
end
|
||||||
|
---@param ... string
|
||||||
|
---@return string
|
||||||
|
function exports.join_file(...)
|
||||||
|
local p, i = exports.split(...);
|
||||||
|
return exports.stringify(p, i, false);
|
||||||
|
end
|
||||||
|
---@param p string
|
||||||
|
---@return boolean
|
||||||
|
function exports.is_dir(p)
|
||||||
|
return p:match "/$" ~= nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param ... string
|
||||||
|
---@return string
|
||||||
|
---@return boolean dir
|
||||||
|
function exports.chroot(...)
|
||||||
|
local parts, i, dir = exports.split((...));
|
||||||
|
|
||||||
|
for i = 2, select("#", ...) do
|
||||||
|
local new_parts, _, new_dir = exports.split((select(i, ...)));
|
||||||
|
dir = new_dir;
|
||||||
|
|
||||||
|
for j = 1, #new_parts do
|
||||||
|
parts[#parts + 1] = new_parts[j];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return exports.stringify(parts, i, dir), dir;
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param ... string
|
||||||
|
---@return string
|
||||||
|
function exports.cwd(...)
|
||||||
|
local parts, i, dir = exports.split((...));
|
||||||
|
|
||||||
|
for i = 2, select("#", ...) do
|
||||||
|
local new_parts, new_i, new_dir = exports.split((select(i, ...)));
|
||||||
|
dir = new_dir;
|
||||||
|
|
||||||
|
if new_i == nil then
|
||||||
|
parts = new_parts;
|
||||||
|
i = nil;
|
||||||
|
else
|
||||||
|
for _ = 1, new_i do
|
||||||
|
parts[#parts] = nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
if new_parts ~= nil then
|
||||||
|
local offset = parts and #parts or 0;
|
||||||
|
for i = 1, #new_parts do
|
||||||
|
parts[i + offset] = new_parts[i];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return exports.stringify(parts, i, dir);
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param ... string
|
||||||
|
---@return string
|
||||||
|
function exports.dirname(...)
|
||||||
|
local parts, i = exports.split(...);
|
||||||
|
parts[#parts] = nil;
|
||||||
|
return exports.stringify(parts, i, true);
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param ... string
|
||||||
|
---@return string
|
||||||
|
function exports.filename(...)
|
||||||
|
return table.remove(exports.split(...)) or "";
|
||||||
|
end
|
||||||
|
|
||||||
|
return exports;
|
145
mod/pipes.lua
Normal file
145
mod/pipes.lua
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
local io = io or require "io";
|
||||||
|
local exports = {};
|
||||||
|
|
||||||
|
--- The default buffer size used by read operations
|
||||||
|
exports.BUFF_SIZE = 4096;
|
||||||
|
|
||||||
|
--- @alias Reader fun(n: number, no_throw?: boolean): string?, string?
|
||||||
|
--- @alias Writer fun(data: string | nil, no_throw?: boolean): true?, string?
|
||||||
|
|
||||||
|
--- Creates a reader from the path
|
||||||
|
--- @param path string
|
||||||
|
--- @return Reader? result
|
||||||
|
function exports.reader(path, force_file)
|
||||||
|
if path == "-" and not force_file then return exports.stdin end
|
||||||
|
return exports.reader_from(io.open(path, "rb"))
|
||||||
|
end
|
||||||
|
--- @param f? file*
|
||||||
|
--- @return Reader? result
|
||||||
|
function exports.reader_from(f, ...)
|
||||||
|
f = assert(f, ...);
|
||||||
|
return function (n)
|
||||||
|
if n == nil then n = exports.BUFF_SIZE end
|
||||||
|
|
||||||
|
if n < 0 then
|
||||||
|
if f ~= nil then
|
||||||
|
f:close();
|
||||||
|
f = nil;
|
||||||
|
end
|
||||||
|
elseif f == nil then
|
||||||
|
error("File closed", 2);
|
||||||
|
else
|
||||||
|
local res = f:read(n);
|
||||||
|
|
||||||
|
if res == nil then
|
||||||
|
f:close();
|
||||||
|
f = nil;
|
||||||
|
return "";
|
||||||
|
else
|
||||||
|
return res;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a writer from the path
|
||||||
|
--- @param path string
|
||||||
|
--- @return Writer? result
|
||||||
|
function exports.writer(path, force_file)
|
||||||
|
if path == "-" and not force_file then return exports.stdout end
|
||||||
|
return exports.writer_from(io.open(path, "wb"))
|
||||||
|
end
|
||||||
|
--- @param f? file*
|
||||||
|
--- @return Writer? result
|
||||||
|
function exports.writer_from(f, ...)
|
||||||
|
f = assert(f, ...);
|
||||||
|
return function (data)
|
||||||
|
if data == nil then
|
||||||
|
if f ~= nil then
|
||||||
|
f:close();
|
||||||
|
f = nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, nil;
|
||||||
|
elseif f == nil then
|
||||||
|
error("File closed", 2);
|
||||||
|
else
|
||||||
|
local res, w_err = f:write(data);
|
||||||
|
|
||||||
|
if res == nil then
|
||||||
|
error(w_err, 2);
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Writes the full contents of src to dst. Since src is read until its end, it will be closed after this function
|
||||||
|
--- @param src Reader
|
||||||
|
--- @param dst Writer
|
||||||
|
--- @param close_dst boolean? Wether or not to close dst after copying
|
||||||
|
function exports.copy(src, dst, close_dst)
|
||||||
|
local res, buff, err, failed;
|
||||||
|
|
||||||
|
while true do
|
||||||
|
buff, err = src(4096, true);
|
||||||
|
|
||||||
|
if buff == nil and err ~= nil then
|
||||||
|
error(err, 2);
|
||||||
|
elseif buff == nil or buff == "" then
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
res, err = dst(buff, true)
|
||||||
|
if res == nil then
|
||||||
|
error(err, 2);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
if close_dst then
|
||||||
|
res, err = dst(nil, true);
|
||||||
|
|
||||||
|
if res == nil then
|
||||||
|
error(err, 2);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if failed then
|
||||||
|
error(err, 2);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.loader(...)
|
||||||
|
local arr = {...};
|
||||||
|
local i = 1;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
while true do
|
||||||
|
local el = arr[i];
|
||||||
|
|
||||||
|
if el == "" then
|
||||||
|
i = i + 1;
|
||||||
|
elseif el == nil then
|
||||||
|
return nil;
|
||||||
|
elseif type(el) == "function" then
|
||||||
|
local buff = el();
|
||||||
|
if buff ~= "" then
|
||||||
|
return buff;
|
||||||
|
else
|
||||||
|
i = i + 1;
|
||||||
|
end
|
||||||
|
elseif type(el) == "string" then
|
||||||
|
i = i + 1;
|
||||||
|
return el;
|
||||||
|
else
|
||||||
|
error("Loader may consist only of strings and functions");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
exports.stdin = exports.writer_from(io.stdin);
|
||||||
|
exports.stdout = exports.writer_from(io.stdout);
|
||||||
|
exports.stderr = exports.writer_from(io.stderr);
|
50
mod/promise.d.lua
Normal file
50
mod/promise.d.lua
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
--- @meta promise
|
||||||
|
|
||||||
|
--- @alias promise<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> promiselib | { when: fun(ful: fun(p1: T1, p2: T2, p3: T3, p4: T4, p5: T5, p6: T6, p7: T7, p8: T8, p9: T9, p10: T10), rej: fun(err)) }
|
||||||
|
|
||||||
|
--- @class promiselib
|
||||||
|
promise = {};
|
||||||
|
promise.__index = promise;
|
||||||
|
|
||||||
|
--- @generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
|
||||||
|
--- @param on_ful? fun(p1: T1, p2: T2, p3: T3, p4: T4, p5: T5, p6: T6, p7: T7, p8: T8, p9: T9, p10: T10)
|
||||||
|
--- @param on_rej? fun(err: any)
|
||||||
|
function promise:when(on_ful, on_rej) end
|
||||||
|
|
||||||
|
--- @generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
|
||||||
|
--- @param self promise<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
|
||||||
|
--- @returns T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
|
||||||
|
function promise:await() end
|
||||||
|
|
||||||
|
--- @generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
|
||||||
|
--- @param func fun(ful: fun(v: T1, v: T2, v: T3, v: T4, v: T5, v: T6, v: T7, v: T8, v: T9, v: T10), rej: fun(val: any))
|
||||||
|
--- @return promise<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
|
||||||
|
function promise.mk(func) end
|
||||||
|
|
||||||
|
--- @generic T1, T2, T3, T4, T5, T6, T7, T8, T9, T10
|
||||||
|
--- @param p1? T1
|
||||||
|
--- @param p2? T2
|
||||||
|
--- @param p3? T3
|
||||||
|
--- @param p4? T4
|
||||||
|
--- @param p5? T5
|
||||||
|
--- @param p6? T6
|
||||||
|
--- @param p7? T7
|
||||||
|
--- @param p8? T8
|
||||||
|
--- @param p9? T9
|
||||||
|
--- @param p10? T10
|
||||||
|
--- @return promise<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
|
||||||
|
function promise.resolve(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) end
|
||||||
|
|
||||||
|
--- @param err any
|
||||||
|
--- @return promise<any>
|
||||||
|
function promise.reject(err)
|
||||||
|
return promise.mk(function (_, rej)
|
||||||
|
if err and type(err.when) == "function" then
|
||||||
|
err:when(rej, rej);
|
||||||
|
else
|
||||||
|
rej(err);
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
end
|
||||||
|
|
||||||
|
return promise;
|
134
mod/promise.lua
Normal file
134
mod/promise.lua
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
local loop = require "evn-loop";
|
||||||
|
local promise = {};
|
||||||
|
promise.__index = promise;
|
||||||
|
|
||||||
|
function promise:when(on_ful, on_rej) end
|
||||||
|
|
||||||
|
function promise:await()
|
||||||
|
local curr = coro.running();
|
||||||
|
local res = nil;
|
||||||
|
|
||||||
|
self:when(function (...)
|
||||||
|
if coro.running() == curr then
|
||||||
|
res = box(true, ...);
|
||||||
|
else
|
||||||
|
coro.transfer(curr, true, ...);
|
||||||
|
end
|
||||||
|
end, function (...)
|
||||||
|
if coro.running() == curr then
|
||||||
|
res = box(false, ...);
|
||||||
|
else
|
||||||
|
coro.transfer(curr, false, ...);
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
|
||||||
|
local function process(ok, ...)
|
||||||
|
if ok then
|
||||||
|
return ...;
|
||||||
|
else
|
||||||
|
error((...), 2);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if res then
|
||||||
|
return process(unbox(res));
|
||||||
|
else
|
||||||
|
return process(loop.interrupt());
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function promise.mk(func)
|
||||||
|
local state = 0;
|
||||||
|
local val = nil;
|
||||||
|
|
||||||
|
--- @type array<fun(...)>
|
||||||
|
local ful_handles = array {};
|
||||||
|
--- @type array<fun(val: any)>
|
||||||
|
local rej_handles = array {};
|
||||||
|
|
||||||
|
local function invoke_handle(fun, arg_box)
|
||||||
|
loop.push(function ()
|
||||||
|
pcall(fun, unbox(arg_box));
|
||||||
|
end);
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fulfill(...)
|
||||||
|
if state == 0 then
|
||||||
|
state = 1;
|
||||||
|
val = box(...);
|
||||||
|
|
||||||
|
for i = 1, #ful_handles do
|
||||||
|
invoke_handle(ful_handles[i], val);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function reject(err)
|
||||||
|
if state == 0 then
|
||||||
|
state = 2;
|
||||||
|
val = box(err);
|
||||||
|
|
||||||
|
for i = 1, #ful_handles do
|
||||||
|
invoke_handle(rej_handles[i], val);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok, err = pcall(func, fulfill, reject);
|
||||||
|
if not ok then reject(err) end
|
||||||
|
|
||||||
|
return setmetatable({
|
||||||
|
when = function (_, on_ful, on_rej)
|
||||||
|
if state == 0 then
|
||||||
|
ful_handles:push(on_ful);
|
||||||
|
rej_handles:push(on_rej);
|
||||||
|
elseif state == 1 then
|
||||||
|
return invoke_handle(on_ful, val);
|
||||||
|
elseif state == 2 then
|
||||||
|
return invoke_handle(on_rej, val);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}, promise);
|
||||||
|
end
|
||||||
|
|
||||||
|
function promise.resolve(...)
|
||||||
|
local res = box(...);
|
||||||
|
|
||||||
|
return promise.mk(function (ful, rej)
|
||||||
|
local passed = 0;
|
||||||
|
|
||||||
|
for i = 1, res.n do
|
||||||
|
if type(res[i]) == "table" and type(res[i].when) == "function" then
|
||||||
|
res[i]:when(
|
||||||
|
function (...)
|
||||||
|
res[i] = ...;
|
||||||
|
passed = passed + 1;
|
||||||
|
|
||||||
|
if passed >= res.n then
|
||||||
|
ful(unbox(res));
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
function (err) rej(err) end
|
||||||
|
);
|
||||||
|
else
|
||||||
|
passed = passed + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if passed == res.n then
|
||||||
|
ful(unbox(res));
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
end
|
||||||
|
|
||||||
|
function promise.reject(err)
|
||||||
|
return promise.mk(function (_, rej)
|
||||||
|
if err and type(err.when) == "function" then
|
||||||
|
err:when(rej, rej);
|
||||||
|
else
|
||||||
|
rej(err);
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
end
|
||||||
|
|
||||||
|
return promise;
|
222
mod/tal/cli.lua
Normal file
222
mod/tal/cli.lua
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
local arg_parse = require "args";
|
||||||
|
local path = require "path";
|
||||||
|
local traceback = require "traceback";
|
||||||
|
local root = require "root";
|
||||||
|
|
||||||
|
local exports = {};
|
||||||
|
|
||||||
|
function exports.stacktrace_call(func, ...)
|
||||||
|
return xpcall(func, function (err)
|
||||||
|
local trace = traceback(2, "\t");
|
||||||
|
|
||||||
|
io.stderr:write "Unhandled error: ";
|
||||||
|
|
||||||
|
if type(err) == "string" then
|
||||||
|
print(err);
|
||||||
|
else
|
||||||
|
pprint(err);
|
||||||
|
end
|
||||||
|
|
||||||
|
print(trace);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
end, ...);
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.load_eval(src, name, env)
|
||||||
|
local f, err = load(iterate { "return ", src }, name, "t", env);
|
||||||
|
if f == nil then
|
||||||
|
f, err = load(src, name, "t", env);
|
||||||
|
end
|
||||||
|
|
||||||
|
return f, err;
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.repl()
|
||||||
|
local mod = root.mk(path.join(root.name, "../<repl>"));
|
||||||
|
function mod.export()
|
||||||
|
error "Can't export in the REPL";
|
||||||
|
end
|
||||||
|
|
||||||
|
mod.exports = nil;
|
||||||
|
mod.returns = {};
|
||||||
|
|
||||||
|
mod.env.export = mod.export;
|
||||||
|
mod.env.import = mod.import;
|
||||||
|
mod.env.require = mod.require;
|
||||||
|
mod.env.module = mod;
|
||||||
|
local local_err_shown = false;
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local cont = true;
|
||||||
|
|
||||||
|
exports.stacktrace_call(function ()
|
||||||
|
local src = "";
|
||||||
|
local err;
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local done = false;
|
||||||
|
local f;
|
||||||
|
|
||||||
|
if src == "" then
|
||||||
|
io.stderr:write "> ";
|
||||||
|
else
|
||||||
|
io.stderr:write "... ";
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @type string
|
||||||
|
local line = io.stdin:read("l");
|
||||||
|
if line == nil then
|
||||||
|
cont = false;
|
||||||
|
return;
|
||||||
|
elseif line == "" then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
src = src .. "\n" .. line;
|
||||||
|
f, err = exports.load_eval(src, "<repl>", mod.env);
|
||||||
|
|
||||||
|
if f ~= nil then
|
||||||
|
if not local_err_shown then
|
||||||
|
local i = 1;
|
||||||
|
local locals = array {};
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local n = debug.getlocal(f, i);
|
||||||
|
if not n then break end
|
||||||
|
|
||||||
|
if not n:match("^%(") then
|
||||||
|
locals:push(n);
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
if #locals > 0 then
|
||||||
|
local_err_shown = true;
|
||||||
|
print "You have defined the following locals in your code:";
|
||||||
|
print(locals:join ", ");
|
||||||
|
print "Consider declaring them as globals instead (\"a = 10\" and \"function a() ... end\")";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pprint(f());
|
||||||
|
|
||||||
|
done = true;
|
||||||
|
end
|
||||||
|
until done;
|
||||||
|
|
||||||
|
if err ~= nil then
|
||||||
|
error(err, 0);
|
||||||
|
end
|
||||||
|
|
||||||
|
return true;
|
||||||
|
end);
|
||||||
|
|
||||||
|
if not cont then return end
|
||||||
|
|
||||||
|
promise.resolve():await();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.exec(mod, requires, compile, ...)
|
||||||
|
exports.stacktrace_call(function (...)
|
||||||
|
for i = 1, #requires do
|
||||||
|
mod.require(requires[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
local func = compile();
|
||||||
|
|
||||||
|
mod.main = true;
|
||||||
|
mod.returns = box(func(...));
|
||||||
|
|
||||||
|
if mod.returns.n > 0 then
|
||||||
|
mod.exports = mod.returns[1];
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(mod.exports) == "function" then
|
||||||
|
return mod.exports(...);
|
||||||
|
elseif type(mod.exports) == "table" and type(mod.exports.main) == "function" then
|
||||||
|
return mod.exports.main(...);
|
||||||
|
end
|
||||||
|
end, ...);
|
||||||
|
end
|
||||||
|
|
||||||
|
function exports.main(...)
|
||||||
|
require "core";
|
||||||
|
|
||||||
|
local file;
|
||||||
|
local args = array {};
|
||||||
|
local requires = array {};
|
||||||
|
local eval;
|
||||||
|
|
||||||
|
arg_parse({
|
||||||
|
function (v)
|
||||||
|
if eval then
|
||||||
|
eval:push(v);
|
||||||
|
elseif file ~= nil then
|
||||||
|
args:push(v);
|
||||||
|
else
|
||||||
|
file = v;
|
||||||
|
end
|
||||||
|
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
require = function (v)
|
||||||
|
requires:push(v);
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
eval = function ()
|
||||||
|
eval = array {};
|
||||||
|
return false, true;
|
||||||
|
end,
|
||||||
|
version = function ()
|
||||||
|
print(str("TAL v", TAL));
|
||||||
|
print("Created /w love and dread by TopchetoEU");
|
||||||
|
exit();
|
||||||
|
end,
|
||||||
|
help = function ()
|
||||||
|
print(str("TAL v", TAL, " by TopchetoEU"));
|
||||||
|
print "A pseudo-runtime on top of my beloved Lua. Includes a better module system and";
|
||||||
|
print "an improved set of stdlibs to make your life easier.";
|
||||||
|
print "PLEASE DON'T USE IN PRODUCTION, MIGHT BREAK AT ANY SECOND";
|
||||||
|
print "Options:";
|
||||||
|
print "\t--require (-r) [name]: Requires the given package before execution, similar to \"lua -l\"";
|
||||||
|
print "\t--help (-h): Shows this message";
|
||||||
|
print "\t--version: Shows the version";
|
||||||
|
print "\t--eval (-e): Evaluates the rest of the arguments";
|
||||||
|
print "\t--: Passes the rest of the arguments as arguments";
|
||||||
|
|
||||||
|
exit();
|
||||||
|
end,
|
||||||
|
|
||||||
|
r = "require",
|
||||||
|
h = "help",
|
||||||
|
e = "eval",
|
||||||
|
}, ...);
|
||||||
|
|
||||||
|
if eval then
|
||||||
|
local mod = root.mk(path.join(root.name, "../<eval>"));
|
||||||
|
return exports.exec(mod, requires, function ()
|
||||||
|
return assert(exports.load_eval(eval:join " ", "<eval>", mod.env));
|
||||||
|
end, unpack(args));
|
||||||
|
elseif file then
|
||||||
|
return exports.stacktrace_call(function (...)
|
||||||
|
if not file:match "^/" then
|
||||||
|
file = "./" .. file;
|
||||||
|
end
|
||||||
|
|
||||||
|
local mod = root.import(file);
|
||||||
|
|
||||||
|
if type(mod) == "function" then
|
||||||
|
return mod(...);
|
||||||
|
elseif mod and type(mod.main) == "function" then
|
||||||
|
return mod.main(...);
|
||||||
|
end
|
||||||
|
end, ...);
|
||||||
|
else
|
||||||
|
return exports.repl();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return exports;
|
34
mod/traceback.lua
Normal file
34
mod/traceback.lua
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
return function(i, prefix)
|
||||||
|
i = i + 1;
|
||||||
|
local lines = array {};
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local info = debug.getinfo(i, "Snl");
|
||||||
|
if info == nil then break end
|
||||||
|
|
||||||
|
local name = info.name;
|
||||||
|
local location = info.short_src;
|
||||||
|
|
||||||
|
if location == "[C]" then
|
||||||
|
location = "at <internal>";
|
||||||
|
elseif string.find(location, "[string", 1, true) then
|
||||||
|
location = "at <string>";
|
||||||
|
else
|
||||||
|
location = "at " .. location;
|
||||||
|
end
|
||||||
|
|
||||||
|
if info.currentline > 0 then
|
||||||
|
location = location .. ":" .. info.currentline;
|
||||||
|
end
|
||||||
|
|
||||||
|
if name ~= nil then
|
||||||
|
lines:push(prefix .. location .. " in " .. name);
|
||||||
|
else
|
||||||
|
lines:push(prefix .. location);
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return lines:join "\n";
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user