initial
This commit is contained in:
commit
71bf0bb188
14
Makefile
Normal file
14
Makefile
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
ifeq ($(DEBUG), yes)
|
||||||
|
dbg_flag := -g
|
||||||
|
endif
|
||||||
|
|
||||||
|
mklua: $(wildcard src/main/*.lua) $(wildcard src/libs/*.c) $(wildcard src/*.lua)
|
||||||
|
lua src/bootstrap.lua \
|
||||||
|
--lua-dir "src/main" \
|
||||||
|
--c-dir "src/libs" \
|
||||||
|
--entry "init" \
|
||||||
|
-o "mklua" \
|
||||||
|
$(dbg_flag)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm mklua
|
23
Readme.md
Normal file
23
Readme.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# MKLua
|
||||||
|
|
||||||
|
A small tool for compiling a C + Lua project to an executable
|
||||||
|
|
||||||
|
# **BIG RED LETTERS ALERT!!!**
|
||||||
|
|
||||||
|
NOT PRODUCTION READY!!!!! USE AT YOUR OWN RISK!!!!!!!!! WORKS ON LINUX ONLY!!!!!
|
||||||
|
|
||||||
|
## How does it work?
|
||||||
|
|
||||||
|
This tool assumes you're working on an executable, that may have lua and C files. Each file will correspond to a single module, that can be used using lua's `require`.
|
||||||
|
|
||||||
|
You will specify where these files are located, using the `--lua-dir` and `--c-dir` options, for lua and C files respectively. All files in those folders will be included in the final build, and the module names used will be relative to the folders, with the lua require rules respected (`.../init.lua` or `.../init.c` will be the default import of the folder, slashes become dots).
|
||||||
|
|
||||||
|
C files will be required by calling the `lua_CFunc` function, by the name of `lualib_open_[name]`, where `name` is the name of the module, with the dots replaced with underscores. This function is expected to return exactly one value, the exported value by the C module.
|
||||||
|
|
||||||
|
Finally, a module name must be specified as the entry of the executable. This module should export a function, which will act as the main function of the program.
|
||||||
|
|
||||||
|
## How does it really work tho?
|
||||||
|
|
||||||
|
The lua files will be compiled by lua's `string.dump` function, with the debug information stripped by default. Then, each bytecode sequence will get loaded into `package.preload`, which is an internal table of lua's `require` mechanism.
|
||||||
|
|
||||||
|
C files on the other hand will get linked together and the `lualib_open_...` function will get called inside the auto-generated `int main` function.
|
30
src/bootstrap.lua
Normal file
30
src/bootstrap.lua
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
local function fix_require(argv_0)
|
||||||
|
local prefix = (argv_0 or arg[0]):match "^(.+)/.-$";
|
||||||
|
local sep = package.path:match "[/\\]" or "/";
|
||||||
|
|
||||||
|
if prefix then
|
||||||
|
|
||||||
|
package.path = table.concat {
|
||||||
|
package.path, ";",
|
||||||
|
prefix, sep, "?.lua;",
|
||||||
|
prefix, sep, "?", sep, "init.lua",
|
||||||
|
};
|
||||||
|
|
||||||
|
return prefix, sep;
|
||||||
|
else
|
||||||
|
return ".", sep;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Fix package.path
|
||||||
|
|
||||||
|
local prefix, sep = fix_require();
|
||||||
|
package.path = table.concat {
|
||||||
|
package.path, ";",
|
||||||
|
prefix, sep, "main", sep, "?.lua;",
|
||||||
|
prefix, sep, "main", sep, "?", sep, "init.lua",
|
||||||
|
};
|
||||||
|
function package.preload.fs()
|
||||||
|
return require "src.bootstrap_fs";
|
||||||
|
end
|
||||||
|
|
||||||
|
return require "main" (...);
|
31
src/bootstrap_fs.lua
Normal file
31
src/bootstrap_fs.lua
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
-- Duct tape functions to give us temporary access to the FS for the bootstrap build
|
||||||
|
-- Supports only linux
|
||||||
|
|
||||||
|
local fs = {};
|
||||||
|
|
||||||
|
function fs.is_file(dir)
|
||||||
|
return os.execute("test -f " .. ("%q"):format(dir)) or false;
|
||||||
|
end
|
||||||
|
function fs.is_dir(dir)
|
||||||
|
return os.execute("test -d " .. ("%q"):format(dir)) or false;
|
||||||
|
end
|
||||||
|
|
||||||
|
function fs.readdir(dir)
|
||||||
|
if not fs.is_dir(dir) then
|
||||||
|
return nil, "not a directory";
|
||||||
|
end
|
||||||
|
|
||||||
|
local res, err = io.popen("ls " .. ("%q/"):format(dir));
|
||||||
|
if not res then return nil, err end
|
||||||
|
|
||||||
|
return res:lines("l");
|
||||||
|
end
|
||||||
|
function fs.mkdir(dir, recursive)
|
||||||
|
if recursive then
|
||||||
|
assert(os.execute("mkdir -p " .. ("%q"):format(dir)));
|
||||||
|
else
|
||||||
|
assert(os.execute("mkdir " .. ("%q"):format(dir)));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return fs;
|
5
src/def/exec.d.lua
Normal file
5
src/def/exec.d.lua
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
--- @meta exec
|
||||||
|
|
||||||
|
--- @param settings { [integer]: string, env: { [string]: string }, stdin?: boolean, stdout?: boolean, stderr?: boolean }
|
||||||
|
--- @return { stdin: file*, stdout: file*, stderr: file*, reap: fun() }
|
||||||
|
return function (settings) end;
|
29
src/def/fs.d.lua
Normal file
29
src/def/fs.d.lua
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
--- @meta fs
|
||||||
|
|
||||||
|
local fs = {};
|
||||||
|
|
||||||
|
--- @param path string
|
||||||
|
--- @param recursive? boolean
|
||||||
|
--- @return true?
|
||||||
|
--- @return string? err_msg
|
||||||
|
function fs.mkdir(path, recursive) end
|
||||||
|
--- @param path string
|
||||||
|
--- @param recursive? boolean
|
||||||
|
--- @return (fun(): string?)? iterator
|
||||||
|
--- @return string? err_msg
|
||||||
|
function fs.readdir(path, recursive) end
|
||||||
|
|
||||||
|
--- @param path string
|
||||||
|
--- @return true?
|
||||||
|
--- @return string? err_msg
|
||||||
|
function fs.is_file(path) end
|
||||||
|
--- @param path string
|
||||||
|
--- @return true?
|
||||||
|
--- @return string? err_msg
|
||||||
|
function fs.is_dir(path) end
|
||||||
|
--- @param path string
|
||||||
|
--- @return { mode: integer, size: integer, uid: integer, gid: integer, ctime: integer, mtime: integer, atime: integer }?
|
||||||
|
--- @return string? err_msg
|
||||||
|
function fs.lstat(path) end
|
||||||
|
|
||||||
|
return fs;
|
270
src/libs/exec.c
Normal file
270
src/libs/exec.c
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <err.h>
|
||||||
|
|
||||||
|
static const char *fs_check_path(lua_State *ctx, int i, size_t *psize) {
|
||||||
|
size_t n;
|
||||||
|
const char *path = luaL_checklstring(ctx, i, &n);
|
||||||
|
|
||||||
|
size_t real_len = strlen(path);
|
||||||
|
if (n != real_len) {
|
||||||
|
luaL_error(ctx, "path may not contain \\0");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (psize) *psize = n;
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_exec(char **env, size_t env_n) {
|
||||||
|
for (size_t i = 0; i < env_n; i++) free(env[i]);
|
||||||
|
if (env) free(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lib_close(lua_State *ctx) {
|
||||||
|
luaL_Stream *stream = luaL_checkudata(ctx, 1, LUA_FILEHANDLE);
|
||||||
|
fflush(stream->f);
|
||||||
|
int stream_fd = fileno(stream->f);
|
||||||
|
close(stream_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lib_close_in(lua_State *ctx) {
|
||||||
|
luaL_Stream *stream = luaL_checkudata(ctx, 1, LUA_FILEHANDLE);
|
||||||
|
int stream_fd = fileno(stream->f);
|
||||||
|
dup2(STDIN_FILENO, stream_fd);
|
||||||
|
}
|
||||||
|
static int lib_close_out(lua_State *ctx) {
|
||||||
|
luaL_Stream *stream = luaL_checkudata(ctx, 1, LUA_FILEHANDLE);
|
||||||
|
int stream_fd = fileno(stream->f);
|
||||||
|
dup2(STDOUT_FILENO, stream_fd);
|
||||||
|
}
|
||||||
|
static int lib_close_err(lua_State *ctx) {
|
||||||
|
luaL_Stream *stream = luaL_checkudata(ctx, 1, LUA_FILEHANDLE);
|
||||||
|
int stream_fd = fileno(stream->f);
|
||||||
|
dup2(STDERR_FILENO, stream_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lib_reap(lua_State *ctx) {
|
||||||
|
pid_t pid = lua_tointeger(ctx, lua_upvalueindex(1));
|
||||||
|
if (pid < 0) return luaL_error(ctx, "Tried to reap child twice");
|
||||||
|
|
||||||
|
lua_pushinteger(ctx, -1);
|
||||||
|
lua_replace(ctx, lua_upvalueindex(1));
|
||||||
|
|
||||||
|
int stat;
|
||||||
|
waitpid(pid, &stat, 0);
|
||||||
|
|
||||||
|
if (!WIFEXITED(stat)) {
|
||||||
|
lua_pushinteger(ctx, -1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_pushinteger(ctx, WEXITSTATUS(stat));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void close_pipes(int in[2], int out[2], int err[2]) {
|
||||||
|
if (in[0] >= 0) close(in[0]);
|
||||||
|
if (in[1] >= 0) close(in[1]);
|
||||||
|
if (out[0] >= 0) close(out[0]);
|
||||||
|
if (out[1] >= 0) close(out[1]);
|
||||||
|
if (err[0] >= 0) close(err[0]);
|
||||||
|
if (err[1] >= 0) close(err[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lib_exec(lua_State *ctx) {
|
||||||
|
luaL_checktype(ctx, 1, LUA_TTABLE);
|
||||||
|
|
||||||
|
size_t n = lua_rawlen(ctx, 1);
|
||||||
|
if (n <= 0) return luaL_error(ctx, "Table argument must have at least one array element");
|
||||||
|
|
||||||
|
const char *args[n + 1];
|
||||||
|
char **env;
|
||||||
|
size_t env_cap = 0, env_n = 0;
|
||||||
|
|
||||||
|
args[n] = NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
lua_geti(ctx, 1, i + 1);
|
||||||
|
if (!lua_isstring(ctx, -1)) return luaL_error(ctx, "Table argument must consist only of strings");
|
||||||
|
|
||||||
|
args[i] = lua_tostring(ctx, -1);
|
||||||
|
lua_pop(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_getfield(ctx, 1, "env")) {
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
while (lua_next(ctx, 1)) {
|
||||||
|
if (lua_type(ctx, -2) == LUA_TSTRING) {
|
||||||
|
if (!lua_isstring(ctx, -1)) {
|
||||||
|
free_exec(env, env_n);
|
||||||
|
return luaL_error(ctx, "Table argument must consist only of strings");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t prev_cap = env_cap;
|
||||||
|
while (env_n >= env_cap) {
|
||||||
|
if (env_cap == 0) env_cap = 16;
|
||||||
|
else env_cap *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev_cap) env = realloc(env, sizeof *env * env_cap);
|
||||||
|
else env = malloc(sizeof *env * env_cap);
|
||||||
|
|
||||||
|
const char *key = lua_tostring(ctx, -2);
|
||||||
|
const char *val = lua_tostring(ctx, -1);
|
||||||
|
|
||||||
|
char *entry = malloc(strlen(key) + 1 + strlen(val) + 1);
|
||||||
|
snprintf(entry, sizeof entry, "%s=%s", key, val);
|
||||||
|
|
||||||
|
env[env_n++] = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pop(ctx, 1);
|
||||||
|
}
|
||||||
|
lua_pop(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int stdin_pipe[2] = { -1, -1 };
|
||||||
|
int stdout_pipe[2] = { -1, -1 };
|
||||||
|
int stderr_pipe[2] = { -1, -1 };
|
||||||
|
|
||||||
|
if (lua_getfield(ctx, 1, "stdin")) {
|
||||||
|
if (pipe(stdin_pipe) < 0) {
|
||||||
|
int _ = errno;
|
||||||
|
close_pipes(stdin_pipe, stdout_pipe, stderr_pipe);
|
||||||
|
free_exec(env, env_n);
|
||||||
|
errno = _;
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "Failed to create stdin pipe: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
lua_pop(ctx, 1);
|
||||||
|
}
|
||||||
|
if (lua_getfield(ctx, 1, "stdout")) {
|
||||||
|
if (lua_toboolean(ctx, -1)) {
|
||||||
|
if (pipe(stdout_pipe) < 0) {
|
||||||
|
int _ = errno;
|
||||||
|
close_pipes(stdin_pipe, stdout_pipe, stderr_pipe);
|
||||||
|
free_exec(env, env_n);
|
||||||
|
errno = _;
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "Failed to create stdout pipe: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pop(ctx, 1);
|
||||||
|
}
|
||||||
|
if (lua_getfield(ctx, 1, "stderr")) {
|
||||||
|
if (lua_toboolean(ctx, -1)) {
|
||||||
|
if (pipe(stderr_pipe) < 0) {
|
||||||
|
int _ = errno;
|
||||||
|
close_pipes(stdin_pipe, stdout_pipe, stderr_pipe);
|
||||||
|
free_exec(env, env_n);
|
||||||
|
errno = _;
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "Failed to create stderr pipe: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pop(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t child = fork();
|
||||||
|
if (child < 0) {
|
||||||
|
int _ = errno;
|
||||||
|
close_pipes(stdin_pipe, stdout_pipe, stderr_pipe);
|
||||||
|
free_exec(env, env_n);
|
||||||
|
errno = _;
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "Failed to fork: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
else if (child == 0) {
|
||||||
|
close(stdin_pipe[1]);
|
||||||
|
close(stdout_pipe[0]);
|
||||||
|
close(stderr_pipe[0]);
|
||||||
|
|
||||||
|
if (stdin_pipe[0] >= 0) dup2(stdin_pipe[0], STDIN_FILENO);
|
||||||
|
if (stdout_pipe[1] >= 0) dup2(stdout_pipe[1], STDOUT_FILENO);
|
||||||
|
if (stderr_pipe[1] >= 0) dup2(stderr_pipe[1], STDERR_FILENO);
|
||||||
|
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (env_n == 0) {
|
||||||
|
res = execvp(args[0], (char**)args);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (env_n <= env_cap) {
|
||||||
|
env = realloc(env, sizeof *env * env_n + 1);
|
||||||
|
}
|
||||||
|
env[env_n] = NULL;
|
||||||
|
|
||||||
|
res = execvpe(args[0], (char**)args, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't really log much, can we?
|
||||||
|
exit(errno);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_createtable(ctx, 0, 4);
|
||||||
|
|
||||||
|
const char *args[n];
|
||||||
|
|
||||||
|
if (stdin_pipe[1] >= 0) {
|
||||||
|
luaL_Stream *stdin_stream = lua_newuserdatauv(ctx, sizeof(luaL_Stream), 0);
|
||||||
|
stdin_stream->closef = lib_close;
|
||||||
|
stdin_stream->f = fdopen(stdin_pipe[1], "w");
|
||||||
|
luaL_getmetatable(ctx, LUA_FILEHANDLE);
|
||||||
|
lua_setmetatable(ctx, -2);
|
||||||
|
lua_setfield(ctx, -2, "stdin");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stdout_pipe[0] >= 0) {
|
||||||
|
luaL_Stream *stdout_stream = lua_newuserdatauv(ctx, sizeof(luaL_Stream), 0);
|
||||||
|
stdout_stream->closef = lib_close;
|
||||||
|
stdout_stream->f = fdopen(stdout_pipe[0], "r");
|
||||||
|
luaL_getmetatable(ctx, LUA_FILEHANDLE);
|
||||||
|
lua_setmetatable(ctx, -2);
|
||||||
|
lua_setfield(ctx, -2, "stdout");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stderr_pipe[0] >= 0) {
|
||||||
|
luaL_Stream *stderr_stream = lua_newuserdatauv(ctx, sizeof(luaL_Stream), 0);
|
||||||
|
stderr_stream->closef = lib_close;
|
||||||
|
stderr_stream->f = fdopen(stderr_pipe[0], "r");
|
||||||
|
luaL_getmetatable(ctx, LUA_FILEHANDLE);
|
||||||
|
lua_setmetatable(ctx, -2);
|
||||||
|
lua_setfield(ctx, -2, "stderr");
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushinteger(ctx, child);
|
||||||
|
lua_pushcclosure(ctx, lib_reap, 1);
|
||||||
|
lua_setfield(ctx, -2, "reap");
|
||||||
|
|
||||||
|
close(stdin_pipe[0]);
|
||||||
|
close(stdout_pipe[1]);
|
||||||
|
close(stderr_pipe[1]);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int lualib_open_exec(lua_State *ctx) {
|
||||||
|
lua_pushcfunction(ctx, lib_exec);
|
||||||
|
return 1;
|
||||||
|
}
|
768
src/libs/fmt.c
Normal file
768
src/libs/fmt.c
Normal file
@ -0,0 +1,768 @@
|
|||||||
|
// Similar in concept to Lua's pack and unpack
|
||||||
|
// Main differences are:
|
||||||
|
// - Some formats are reinterpreted to mean the same thing across architectures
|
||||||
|
// - Default endianness is big-endian (but that can easily be changed, of course)
|
||||||
|
// - Formats are parsed once and used many times, to reduce overhead
|
||||||
|
// - Polyfills will store packers and unpackers in weak tables
|
||||||
|
// - Default alignment is no alignment
|
||||||
|
// - The maximum size of an integer is 64 bits (instead of 128)
|
||||||
|
|
||||||
|
#include <lauxlib.h>
|
||||||
|
#include <lua.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define FMT_UDATA_NAME "fmt.meta"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FMT_INT8,
|
||||||
|
FMT_UINT8,
|
||||||
|
FMT_INT16,
|
||||||
|
FMT_UINT16,
|
||||||
|
FMT_INT32,
|
||||||
|
FMT_UINT32,
|
||||||
|
FMT_INT64,
|
||||||
|
FMT_UINT64,
|
||||||
|
|
||||||
|
FMT_FLOAT32,
|
||||||
|
FMT_FLOAT64,
|
||||||
|
|
||||||
|
FMT_STR_FIXED,
|
||||||
|
FMT_STR8,
|
||||||
|
FMT_STR16,
|
||||||
|
FMT_STR32,
|
||||||
|
FMT_STR64,
|
||||||
|
FMT_STR_ZERO,
|
||||||
|
|
||||||
|
FMT_PADDING,
|
||||||
|
} fmt_type_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FMT_OK,
|
||||||
|
FMT_BAD_ARGS,
|
||||||
|
FMT_INCOMPLETE_OP,
|
||||||
|
FMT_BAD_OP_ARG,
|
||||||
|
FMT_BAD_OP,
|
||||||
|
} fmt_code_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
fmt_type_t type;
|
||||||
|
size_t size, align;
|
||||||
|
} fmt_segment_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool little_endian;
|
||||||
|
size_t max_align;
|
||||||
|
size_t segments_n;
|
||||||
|
fmt_segment_t segments[];
|
||||||
|
} fmt_t;
|
||||||
|
|
||||||
|
static int fmt_parse_width(size_t *i, const char *raw, size_t raw_size) {
|
||||||
|
if (*i < raw_size) {
|
||||||
|
switch (raw[(*i)++]) {
|
||||||
|
case '1': return 1;
|
||||||
|
case '2': return 2;
|
||||||
|
case '4': return 4;
|
||||||
|
case '8': return 8;
|
||||||
|
default:
|
||||||
|
(*i)--;
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fmt_code_t fmt_parse_fmt(lua_State *ctx, const char *raw, size_t raw_size) {
|
||||||
|
if (raw == NULL || *raw == 0) return FMT_BAD_ARGS;
|
||||||
|
if (raw_size == SIZE_MAX) raw_size = strlen(raw);
|
||||||
|
|
||||||
|
fmt_segment_t segments[raw_size];
|
||||||
|
size_t segments_n = 0;
|
||||||
|
bool little_endian;
|
||||||
|
bool max_align;
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
bool curr_padding = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (i >= raw_size) break;
|
||||||
|
fmt_segment_t segment;
|
||||||
|
|
||||||
|
switch (raw[i++]) {
|
||||||
|
case '<': little_endian = true; continue;
|
||||||
|
case '>':
|
||||||
|
case '=': little_endian = false; continue;
|
||||||
|
case '!':
|
||||||
|
if (i < raw_size && (raw[i] < '0' || raw[i] > '9')) {
|
||||||
|
max_align = raw[i] - '0';
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else max_align = 1;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case 'b': segment = (fmt_segment_t){ .type = FMT_INT8, .align = 1 }; break;
|
||||||
|
case 'B': segment = (fmt_segment_t){ .type = FMT_UINT8, .align = 1 }; break;
|
||||||
|
case 'h': segment = (fmt_segment_t){ .type = FMT_INT16, .align = 2 }; break;
|
||||||
|
case 'H': segment = (fmt_segment_t){ .type = FMT_UINT16, .align = 2 }; break;
|
||||||
|
case 'j': segment = (fmt_segment_t){ .type = FMT_INT32, .align = 4 }; break;
|
||||||
|
case 'J': segment = (fmt_segment_t){ .type = FMT_UINT32, .align = 4 }; break;
|
||||||
|
case 'l': segment = (fmt_segment_t){ .type = FMT_INT64, .align = 8 }; break;
|
||||||
|
case 'L':
|
||||||
|
case 'T': segment = (fmt_segment_t){ .type = FMT_UINT64, .align = 8 }; break;
|
||||||
|
|
||||||
|
case 'f': segment = (fmt_segment_t){ .type = FMT_FLOAT32, .align = 4 }; break;
|
||||||
|
case 'd':
|
||||||
|
case 'n': segment = (fmt_segment_t){ .type = FMT_FLOAT64, .align = 8 }; break;
|
||||||
|
|
||||||
|
case 'i':
|
||||||
|
switch (fmt_parse_width(&i, raw, raw_size)) {
|
||||||
|
case 1: segment = (fmt_segment_t){ .type = FMT_INT8, .align = 1 }; break;
|
||||||
|
case 2: segment = (fmt_segment_t){ .type = FMT_INT16, .align = 2 }; break;
|
||||||
|
case 4: segment = (fmt_segment_t){ .type = FMT_INT32, .align = 4 }; break;
|
||||||
|
case 8: segment = (fmt_segment_t){ .type = FMT_INT64, .align = 8 }; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
switch (fmt_parse_width(&i, raw, raw_size)) {
|
||||||
|
case 1: segment = (fmt_segment_t){ .type = FMT_UINT8, .align = 1 }; break;
|
||||||
|
case 2: segment = (fmt_segment_t){ .type = FMT_UINT16, .align = 2 }; break;
|
||||||
|
case 4: segment = (fmt_segment_t){ .type = FMT_UINT32, .align = 4 }; break;
|
||||||
|
case 8: segment = (fmt_segment_t){ .type = FMT_UINT64, .align = 8 }; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'z': segment = (fmt_segment_t){ .type = FMT_STR_ZERO, .align = 1 }; break;
|
||||||
|
case 'c':
|
||||||
|
bool has_size;
|
||||||
|
size_t str_size;
|
||||||
|
|
||||||
|
while (i >= raw_size && raw[i] >= '0' && raw[i] <= '9') {
|
||||||
|
str_size *= 10;
|
||||||
|
str_size += raw[i] - '0';
|
||||||
|
has_size = true;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_size) return FMT_INCOMPLETE_OP;
|
||||||
|
|
||||||
|
segment = (fmt_segment_t){ .type = FMT_STR_FIXED, .size = str_size, .align = 1 };
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
switch (fmt_parse_width(&i, raw, raw_size)) {
|
||||||
|
case 1: segment = (fmt_segment_t){ .type = FMT_STR8, .align = 1 }; break;
|
||||||
|
case 2: segment = (fmt_segment_t){ .type = FMT_STR16, .align = 2 }; break;
|
||||||
|
case 4: segment = (fmt_segment_t){ .type = FMT_STR32, .align = 4 }; break;
|
||||||
|
case 8: segment = (fmt_segment_t){ .type = FMT_STR64, .align = 8 }; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'x': segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 1, .align = 1 }; break;
|
||||||
|
case 'X':
|
||||||
|
curr_padding = true;
|
||||||
|
continue;
|
||||||
|
case ' ':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return FMT_BAD_OP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr_padding) {
|
||||||
|
// TODO: is this correct?
|
||||||
|
switch (segment.type) {
|
||||||
|
case FMT_INT8:
|
||||||
|
case FMT_UINT8:
|
||||||
|
case FMT_STR8:
|
||||||
|
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 1, .align = 1 };
|
||||||
|
break;
|
||||||
|
case FMT_INT16:
|
||||||
|
case FMT_UINT16:
|
||||||
|
case FMT_STR16:
|
||||||
|
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 2, .align = 2 };
|
||||||
|
break;
|
||||||
|
case FMT_INT32:
|
||||||
|
case FMT_UINT32:
|
||||||
|
case FMT_FLOAT32:
|
||||||
|
case FMT_STR32:
|
||||||
|
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 4, .align = 4 };
|
||||||
|
break;
|
||||||
|
case FMT_INT64:
|
||||||
|
case FMT_UINT64:
|
||||||
|
case FMT_FLOAT64:
|
||||||
|
case FMT_STR64:
|
||||||
|
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 8, .align = 8 };
|
||||||
|
break;
|
||||||
|
case FMT_STR_FIXED:
|
||||||
|
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = segment.size, .align = segment.size };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return FMT_BAD_OP_ARG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
segments[segments_n++] = segment;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curr_padding) return FMT_INCOMPLETE_OP;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < segments_n; i++) {
|
||||||
|
// TODO: this might not be correct...
|
||||||
|
if (segments[i].align > max_align) segments[i].align = 8;
|
||||||
|
if (segments[i].align > max_align) segments[i].align = 4;
|
||||||
|
if (segments[i].align > max_align) segments[i].align = 2;
|
||||||
|
if (segments[i].align > max_align) segments[i].align = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_t *res = lua_newuserdata(ctx, sizeof *res + sizeof *segments * segments_n);
|
||||||
|
luaL_getmetatable(ctx, FMT_UDATA_NAME);
|
||||||
|
lua_setmetatable(ctx, -2);
|
||||||
|
|
||||||
|
res->little_endian = little_endian;
|
||||||
|
res->max_align = max_align;
|
||||||
|
res->segments_n = segments_n;
|
||||||
|
memcpy(res->segments, segments, sizeof *segments * segments_n);
|
||||||
|
|
||||||
|
return FMT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *arr;
|
||||||
|
size_t cap, n;
|
||||||
|
} write_buff_t;
|
||||||
|
|
||||||
|
static void write_buff_append(write_buff_t *buff, const char *data, size_t size) {
|
||||||
|
size_t new_cap = buff->cap;
|
||||||
|
|
||||||
|
while (new_cap < buff->n + size) {
|
||||||
|
if (new_cap == 0) new_cap = 16;
|
||||||
|
else new_cap *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_cap != buff->cap) {
|
||||||
|
if (buff->cap == 0) {
|
||||||
|
buff->arr = malloc(new_cap);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buff->arr = realloc(buff->arr, new_cap);
|
||||||
|
}
|
||||||
|
buff->cap = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data == NULL) {
|
||||||
|
memset(buff->arr + buff->n, 0, size);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy(buff->arr + buff->n, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
buff->n += size;
|
||||||
|
}
|
||||||
|
static void write_buff_fit(write_buff_t *buff) {
|
||||||
|
if (buff->arr == NULL) return;
|
||||||
|
|
||||||
|
if (buff->n == 0) {
|
||||||
|
free(buff->arr);
|
||||||
|
buff->cap = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buff->cap = buff->n;
|
||||||
|
buff->arr = realloc(buff->arr, buff->n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool fmt_read_uint8(const uint8_t *raw, size_t *i, size_t size, uint8_t *res) {
|
||||||
|
if ((*i) + 1 > size) return false;
|
||||||
|
|
||||||
|
*res = raw[(*i)++];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool fmt_read_int8(const uint8_t *raw, size_t *i, size_t size, int8_t *res) {
|
||||||
|
return fmt_read_uint8(raw, i, size, (uint8_t*)res);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fmt_read_uint16(const uint8_t *raw, size_t *i, size_t size, uint16_t *res, bool little_endian) {
|
||||||
|
if ((*i) + 2 > size) return false;
|
||||||
|
|
||||||
|
uint16_t a = raw[(*i)++];
|
||||||
|
uint16_t b = raw[(*i)++];
|
||||||
|
|
||||||
|
if (little_endian) *res = a | b << 8;
|
||||||
|
else *res = a << 8 | b;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool fmt_read_int16(const uint8_t *raw, size_t *i, size_t size, int16_t *res, bool little_endian) {
|
||||||
|
return fmt_read_uint16(raw, i, size, (uint16_t*)res, little_endian);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fmt_read_uint32(const uint8_t *raw, size_t *i, size_t size, uint32_t *res, bool little_endian) {
|
||||||
|
if ((*i) + 4 > size) return false;
|
||||||
|
|
||||||
|
uint8_t a = raw[(*i)++];
|
||||||
|
uint8_t b = raw[(*i)++];
|
||||||
|
uint8_t c = raw[(*i)++];
|
||||||
|
uint8_t d = raw[(*i)++];
|
||||||
|
|
||||||
|
if (little_endian) *res = a | b << 8 | c << 16 | d << 24;
|
||||||
|
else *res = a << 24 | b << 16 | c << 8 | d;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool fmt_read_int32(const uint8_t *raw, size_t *i, size_t size, int32_t *res, bool little_endian) {
|
||||||
|
return fmt_read_uint32(raw, i, size, (uint32_t*)res, little_endian);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fmt_read_uint64(const uint8_t *raw, size_t *i, size_t size, uint64_t *res, bool little_endian) {
|
||||||
|
if ((*i) + 8 > size) return false;
|
||||||
|
|
||||||
|
uint64_t a = raw[(*i)++];
|
||||||
|
uint64_t b = raw[(*i)++];
|
||||||
|
uint64_t c = raw[(*i)++];
|
||||||
|
uint64_t d = raw[(*i)++];
|
||||||
|
uint64_t e = raw[(*i)++];
|
||||||
|
uint64_t f = raw[(*i)++];
|
||||||
|
uint64_t g = raw[(*i)++];
|
||||||
|
uint64_t h = raw[(*i)++];
|
||||||
|
|
||||||
|
if (little_endian) *res = a | b << 8 | c << 16 | d << 24 | e << 32 | f << 40 | g << 48 | h << 56;
|
||||||
|
else *res = a << 56 | b << 48 | c << 40 | d << 32 | e << 24 | f << 16 | g << 8 | h;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool fmt_read_int64(const uint8_t *raw, size_t *i, size_t size, int64_t *res, bool little_endian) {
|
||||||
|
return fmt_read_uint64(raw, i, size, (uint64_t*)res, little_endian);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fmt_read_float32(const uint8_t *raw, size_t *i, size_t size, float *res, bool little_endian) {
|
||||||
|
if ((*i) + 4 > size) return false;
|
||||||
|
|
||||||
|
uint8_t a = raw[(*i)++];
|
||||||
|
uint8_t b = raw[(*i)++];
|
||||||
|
uint8_t c = raw[(*i)++];
|
||||||
|
uint8_t d = raw[(*i)++];
|
||||||
|
|
||||||
|
// TODO: is this portable enough?
|
||||||
|
if (little_endian) *res = *(float*)(uint8_t[]) { a, b, c, d };
|
||||||
|
else *res = *(float*)(uint8_t[]) { d, c, b, a };
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool fmt_read_float64(const uint8_t *raw, size_t *i, size_t size, double *res, bool little_endian) {
|
||||||
|
if ((*i) + 8 > size) return false;
|
||||||
|
|
||||||
|
uint8_t a = raw[(*i)++];
|
||||||
|
uint8_t b = raw[(*i)++];
|
||||||
|
uint8_t c = raw[(*i)++];
|
||||||
|
uint8_t d = raw[(*i)++];
|
||||||
|
uint8_t e = raw[(*i)++];
|
||||||
|
uint8_t f = raw[(*i)++];
|
||||||
|
uint8_t g = raw[(*i)++];
|
||||||
|
uint8_t h = raw[(*i)++];
|
||||||
|
|
||||||
|
// TODO: is this portable enough?
|
||||||
|
if (little_endian) *res = *(float*)(uint8_t[]) { a, b, c, d, e, f, g, h };
|
||||||
|
else *res = *(float*)(uint8_t[]) { h, g, f, e, d, c, b, a };
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fmt_read_string(lua_State *ctx, fmt_segment_t segment, const uint8_t *raw, size_t *i, size_t size, bool little_endian) {
|
||||||
|
uint64_t len;
|
||||||
|
|
||||||
|
switch (segment.type) {
|
||||||
|
case FMT_STR8: {
|
||||||
|
uint8_t len8;
|
||||||
|
if (!fmt_read_uint8(raw, i, size, &len8)) return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_STR16: {
|
||||||
|
uint16_t len16;
|
||||||
|
if (!fmt_read_uint16(raw, i, size, &len16, little_endian)) return false;
|
||||||
|
len = len16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_STR32: {
|
||||||
|
uint32_t len32;
|
||||||
|
if (!fmt_read_uint32(raw, i, size, &len32, little_endian)) return false;
|
||||||
|
len = len32;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_STR64:
|
||||||
|
if (!fmt_read_uint64(raw, i, size, &len, little_endian)) return false;
|
||||||
|
break;
|
||||||
|
case FMT_STR_FIXED:
|
||||||
|
len = segment.size;
|
||||||
|
break;
|
||||||
|
case FMT_STR_ZERO:
|
||||||
|
len = strnlen((const char*)(raw + *i), size - *i);
|
||||||
|
if (len >= size - *i) return false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "%lu %lu", len, *i);
|
||||||
|
if ((*i) + len > size) return false;
|
||||||
|
|
||||||
|
char data[len];
|
||||||
|
memcpy(data, raw + (*i), len);
|
||||||
|
|
||||||
|
lua_pushlstring(ctx, data, len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void fmt_write_uint8(write_buff_t *buff, uint8_t val) {
|
||||||
|
write_buff_append(buff, (char[]){ val }, 1);
|
||||||
|
}
|
||||||
|
static void fmt_write_int8(write_buff_t *buff, int8_t val) {
|
||||||
|
return fmt_write_uint8(buff, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmt_write_uint16(write_buff_t *buff, uint16_t val, bool little_endian) {
|
||||||
|
uint8_t a = val & 0xFF;
|
||||||
|
uint8_t b = val >> 8 & 0xFF;
|
||||||
|
|
||||||
|
if (little_endian) write_buff_append(buff, (char[]){ a, b }, 2);
|
||||||
|
else write_buff_append(buff, (char[]){ b, a }, 2);
|
||||||
|
}
|
||||||
|
static void fmt_write_int16(write_buff_t *buff, uint16_t val, bool little_endian) {
|
||||||
|
fmt_write_uint16(buff, val, little_endian);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmt_write_uint32(write_buff_t *buff, uint32_t val, bool little_endian) {
|
||||||
|
uint8_t a = val & 0xFF;
|
||||||
|
uint8_t b = val >> 8 & 0xFF;
|
||||||
|
uint8_t c = val >> 16 & 0xFF;
|
||||||
|
uint8_t d = val >> 24 & 0xFF;
|
||||||
|
|
||||||
|
if (little_endian) write_buff_append(buff, (char[]){ a, b, c, d }, 4);
|
||||||
|
else write_buff_append(buff, (char[]){ d, c, b, a }, 4);
|
||||||
|
}
|
||||||
|
static void fmt_write_int32(write_buff_t *buff, uint32_t val, bool little_endian) {
|
||||||
|
fmt_write_uint32(buff, val, little_endian);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmt_write_uint64(write_buff_t *buff, uint64_t val, bool little_endian) {
|
||||||
|
uint8_t a = val & 0xFF;
|
||||||
|
uint8_t b = val >> 8 & 0xFF;
|
||||||
|
uint8_t c = val >> 16 & 0xFF;
|
||||||
|
uint8_t d = val >> 24 & 0xFF;
|
||||||
|
uint8_t e = val >> 32 & 0xFF;
|
||||||
|
uint8_t f = val >> 40 & 0xFF;
|
||||||
|
uint8_t g = val >> 48 & 0xFF;
|
||||||
|
uint8_t h = val >> 56 & 0xFF;
|
||||||
|
|
||||||
|
if (little_endian) write_buff_append(buff, (char[]){ a, b, c, d, e, f, g, h }, 8);
|
||||||
|
else write_buff_append(buff, (char[]){ h, g, f, e, d, c, b, a }, 8);
|
||||||
|
}
|
||||||
|
static void fmt_write_int64(write_buff_t *buff, uint64_t val, bool little_endian) {
|
||||||
|
fmt_write_uint64(buff, val, little_endian);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fmt_write_float32(write_buff_t *buff, float val, bool little_endian) {
|
||||||
|
uint32_t ival = *(uint32_t*)&val;
|
||||||
|
|
||||||
|
uint8_t a = ival & 0xFF;
|
||||||
|
uint8_t b = ival >> 8 & 0xFF;
|
||||||
|
uint8_t c = ival >> 16 & 0xFF;
|
||||||
|
uint8_t d = ival >> 24 & 0xFF;
|
||||||
|
|
||||||
|
if (little_endian) write_buff_append(buff, (char[]){ a, b, c, d }, 4);
|
||||||
|
else write_buff_append(buff, (char[]){ d, c, b, a }, 4);
|
||||||
|
}
|
||||||
|
static void fmt_write_float64(write_buff_t *buff, double val, bool little_endian) {
|
||||||
|
uint64_t ival = *(uint64_t*)&val;
|
||||||
|
|
||||||
|
uint8_t a = ival & 0xFF;
|
||||||
|
uint8_t b = ival >> 8 & 0xFF;
|
||||||
|
uint8_t c = ival >> 16 & 0xFF;
|
||||||
|
uint8_t d = ival >> 24 & 0xFF;
|
||||||
|
uint8_t e = ival >> 32 & 0xFF;
|
||||||
|
uint8_t f = ival >> 40 & 0xFF;
|
||||||
|
uint8_t g = ival >> 48 & 0xFF;
|
||||||
|
uint8_t h = ival >> 56 & 0xFF;
|
||||||
|
|
||||||
|
if (little_endian) write_buff_append(buff, (char[]){ a, b, c, d, e, f, g, h }, 8);
|
||||||
|
else write_buff_append(buff, (char[]){ h, g, f, e, d, c, b, a }, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fmt_write_string(write_buff_t *buff, fmt_segment_t segment, const char *str, size_t len, bool little_endian) {
|
||||||
|
switch (segment.type) {
|
||||||
|
case FMT_STR8:
|
||||||
|
if (len > 256) return false;
|
||||||
|
fmt_write_uint8(buff, (uint8_t)len);
|
||||||
|
write_buff_append(buff, str, len);
|
||||||
|
return true;
|
||||||
|
case FMT_STR16:
|
||||||
|
if (len > 0x10000) return false;
|
||||||
|
fmt_write_uint16(buff, (uint16_t)len, little_endian);
|
||||||
|
write_buff_append(buff, str, len);
|
||||||
|
return true;
|
||||||
|
case FMT_STR32:
|
||||||
|
if (len > 0x100000000) return false;
|
||||||
|
fmt_write_uint32(buff, (uint32_t)len, little_endian);
|
||||||
|
write_buff_append(buff, str, len);
|
||||||
|
return true;
|
||||||
|
case FMT_STR64:
|
||||||
|
fmt_write_uint64(buff, len, little_endian);
|
||||||
|
write_buff_append(buff, str, len);
|
||||||
|
return true;
|
||||||
|
case FMT_STR_FIXED:
|
||||||
|
if (len > segment.size) return false;
|
||||||
|
write_buff_append(buff, str, len);
|
||||||
|
write_buff_append(buff, NULL, segment.size - len);
|
||||||
|
return true;
|
||||||
|
case FMT_STR_ZERO:
|
||||||
|
if (strlen(str) != len) return false;
|
||||||
|
write_buff_append(buff, str, len);
|
||||||
|
write_buff_append(buff, (char[]) { 0x00 }, 1);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int lib_fmt_pack(lua_State *ctx) {
|
||||||
|
fmt_t *fmt = luaL_checkudata(ctx, 1, FMT_UDATA_NAME);
|
||||||
|
|
||||||
|
size_t arg_i = 2;
|
||||||
|
write_buff_t buff = { .arr = NULL, .n = 0, .cap = 0 };
|
||||||
|
|
||||||
|
for (size_t i = 0; i < fmt->segments_n; i++) {
|
||||||
|
fmt_segment_t segment = fmt->segments[i];
|
||||||
|
|
||||||
|
size_t n_aligned = -((-buff.n / segment.align) * segment.align);
|
||||||
|
size_t align_padding = n_aligned - buff.n;
|
||||||
|
write_buff_append(&buff, NULL, align_padding);
|
||||||
|
|
||||||
|
switch (segment.type) {
|
||||||
|
case FMT_INT8:
|
||||||
|
fmt_write_int8(&buff, luaL_checkinteger(ctx, arg_i++));
|
||||||
|
break;
|
||||||
|
case FMT_UINT8:
|
||||||
|
fmt_write_uint8(&buff, luaL_checkinteger(ctx, arg_i++));
|
||||||
|
break;
|
||||||
|
case FMT_INT16:
|
||||||
|
fmt_write_int16(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||||
|
break;
|
||||||
|
case FMT_UINT16:
|
||||||
|
fmt_write_uint16(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||||
|
break;
|
||||||
|
case FMT_INT32:
|
||||||
|
fmt_write_int32(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||||
|
break;
|
||||||
|
case FMT_UINT32:
|
||||||
|
fmt_write_uint32(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||||
|
break;
|
||||||
|
case FMT_INT64:
|
||||||
|
fmt_write_int64(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||||
|
break;
|
||||||
|
case FMT_UINT64:
|
||||||
|
fmt_write_uint64(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FMT_FLOAT32:
|
||||||
|
fmt_write_float32(&buff, (float)luaL_checknumber(ctx, arg_i++), fmt->little_endian);
|
||||||
|
break;
|
||||||
|
case FMT_FLOAT64:
|
||||||
|
fmt_write_float64(&buff, (double)luaL_checknumber(ctx, arg_i++), fmt->little_endian);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FMT_STR_FIXED:
|
||||||
|
case FMT_STR8:
|
||||||
|
case FMT_STR16:
|
||||||
|
case FMT_STR32:
|
||||||
|
case FMT_STR64:
|
||||||
|
case FMT_STR_ZERO: {
|
||||||
|
size_t size;
|
||||||
|
const char *str = luaL_checklstring(ctx, arg_i++, &size);
|
||||||
|
if (!fmt_write_string(&buff, segment, str, size, fmt->little_endian)) {
|
||||||
|
luaL_error(ctx, "invalid string at %d", arg_i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FMT_PADDING:
|
||||||
|
// TODO: might need to remove this later...
|
||||||
|
write_buff_append(&buff, NULL, segment.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_buff_fit(&buff);
|
||||||
|
lua_pushlstring(ctx, buff.arr, buff.n);
|
||||||
|
free(buff.arr);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lib_fmt_unpack(lua_State *ctx) {
|
||||||
|
fmt_t *fmt = luaL_checkudata(ctx, 1, FMT_UDATA_NAME);
|
||||||
|
|
||||||
|
size_t raw_size;
|
||||||
|
const uint8_t *raw = (const uint8_t*)luaL_checklstring(ctx, 2, &raw_size);
|
||||||
|
|
||||||
|
size_t read_i = 0;
|
||||||
|
size_t res_n = 0;
|
||||||
|
if (lua_isinteger(ctx, 3)) {
|
||||||
|
read_i = lua_tointeger(ctx, 3) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_buff_t buff = { .arr = NULL, .n = 0, .cap = 0 };
|
||||||
|
|
||||||
|
for (size_t i = 0; i < fmt->segments_n; i++) {
|
||||||
|
fmt_segment_t segment = fmt->segments[i];
|
||||||
|
|
||||||
|
size_t n_aligned = -((-buff.n / segment.align) * segment.align);
|
||||||
|
size_t align_padding = n_aligned - buff.n;
|
||||||
|
read_i += align_padding;
|
||||||
|
|
||||||
|
switch (segment.type) {
|
||||||
|
case FMT_INT8: {
|
||||||
|
int8_t res;
|
||||||
|
if (!fmt_read_int8(raw, &read_i, raw_size, &res)) luaL_error(ctx, "couldn't read int8");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_UINT8: {
|
||||||
|
uint8_t res;
|
||||||
|
if (!fmt_read_uint8(raw, &read_i, raw_size, &res)) luaL_error(ctx, "couldn't read uint8");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_INT16: {
|
||||||
|
int16_t res;
|
||||||
|
if (!fmt_read_int16(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read int16");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_UINT16: {
|
||||||
|
uint16_t res;
|
||||||
|
if (!fmt_read_uint16(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read uint16");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_INT32: {
|
||||||
|
int32_t res;
|
||||||
|
if (!fmt_read_int32(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read int32");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_UINT32: {
|
||||||
|
uint32_t res;
|
||||||
|
if (!fmt_read_uint32(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read uint32");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_INT64: {
|
||||||
|
int64_t res;
|
||||||
|
if (!fmt_read_int64(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read int64");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_UINT64: {
|
||||||
|
uint64_t res;
|
||||||
|
if (!fmt_read_uint64(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read uint64");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FMT_FLOAT32: {
|
||||||
|
float res;
|
||||||
|
if (!fmt_read_float32(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read float32");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FMT_FLOAT64: {
|
||||||
|
double res;
|
||||||
|
if (!fmt_read_float64(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read float64");
|
||||||
|
lua_pushinteger(ctx, res);
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FMT_STR_FIXED:
|
||||||
|
case FMT_STR8:
|
||||||
|
case FMT_STR16:
|
||||||
|
case FMT_STR32:
|
||||||
|
case FMT_STR64:
|
||||||
|
case FMT_STR_ZERO: {
|
||||||
|
if (!fmt_read_string(ctx, segment, raw, &read_i, raw_size, fmt->little_endian)) luaL_error(ctx, "couldn't read string");
|
||||||
|
res_n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FMT_PADDING:
|
||||||
|
read_i += segment.size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushinteger(ctx, read_i + 1);
|
||||||
|
return res_n + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int lib_fmt_new(lua_State *ctx) {
|
||||||
|
size_t raw_size;
|
||||||
|
const char *raw = luaL_checklstring(ctx, 1, &raw_size);
|
||||||
|
fmt_code_t code = fmt_parse_fmt(ctx, raw, raw_size);
|
||||||
|
|
||||||
|
if (code != FMT_OK) {
|
||||||
|
switch (code) {
|
||||||
|
case FMT_BAD_ARGS: return luaL_error(ctx, "illegal C arguments");
|
||||||
|
case FMT_INCOMPLETE_OP: return luaL_error(ctx, "incomplete operand in format string");
|
||||||
|
case FMT_BAD_OP_ARG: return luaL_error(ctx, "bad operand argument in format string");
|
||||||
|
case FMT_BAD_OP: return luaL_error(ctx, "bad operand in format string");
|
||||||
|
default: return luaL_error(ctx, "unknown error while parsing format string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void fmt_init_meta(lua_State *ctx) {
|
||||||
|
luaL_newmetatable(ctx, FMT_UDATA_NAME);
|
||||||
|
|
||||||
|
luaL_newlib(ctx, ((luaL_Reg[]) {
|
||||||
|
{ "pack", lib_fmt_pack },
|
||||||
|
{ "unpack", lib_fmt_unpack },
|
||||||
|
{ NULL, NULL }
|
||||||
|
}));
|
||||||
|
lua_setfield(ctx, -2, "__index");
|
||||||
|
|
||||||
|
lua_pushboolean(ctx, false);
|
||||||
|
lua_setfield(ctx, -2, "__meta");
|
||||||
|
|
||||||
|
lua_pop(ctx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int lualib_open_fmt(lua_State *ctx) {
|
||||||
|
fmt_init_meta(ctx);
|
||||||
|
|
||||||
|
luaL_newlib(ctx, ((luaL_Reg[]) {
|
||||||
|
{ "new", lib_fmt_new },
|
||||||
|
{ "pack", lib_fmt_pack },
|
||||||
|
{ "unpack", lib_fmt_unpack },
|
||||||
|
{ NULL, NULL },
|
||||||
|
}));
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
168
src/libs/fs.c
Normal file
168
src/libs/fs.c
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
#define _GNU_SOURCE
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/dir.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <err.h>
|
||||||
|
|
||||||
|
static const char *fs_check_path(lua_State *ctx, int i, size_t *psize) {
|
||||||
|
size_t n;
|
||||||
|
const char *path = luaL_checklstring(ctx, i, &n);
|
||||||
|
|
||||||
|
size_t real_len = strlen(path);
|
||||||
|
if (n != real_len) {
|
||||||
|
luaL_error(ctx, "path may not contain \\0");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (psize) *psize = n;
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int readdir_next(lua_State *ctx) {
|
||||||
|
DIR *dir = lua_touserdata(ctx, lua_upvalueindex(1));
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
struct dirent *entry = readdir(dir);
|
||||||
|
if (!entry) {
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(entry->d_name, ".")) continue;
|
||||||
|
if (!strcmp(entry->d_name, "..")) continue;
|
||||||
|
|
||||||
|
lua_pushstring(ctx, entry->d_name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lib_fs_mkdir(lua_State *ctx) {
|
||||||
|
size_t path_size;
|
||||||
|
const char *ro_path = fs_check_path(ctx, 1, &path_size);
|
||||||
|
bool recursive = lua_toboolean(ctx, 2);
|
||||||
|
|
||||||
|
if (recursive) {
|
||||||
|
char path[path_size + 1];
|
||||||
|
memcpy(path, ro_path, path_size + 1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i <= path_size ; i++) {
|
||||||
|
if (path[i] == '/' || path[i] == '\0') {
|
||||||
|
if (i == 0) continue;
|
||||||
|
|
||||||
|
path[i] = '\0';
|
||||||
|
|
||||||
|
if (mkdir(path, 0755) < 0 && errno != EEXIST) {
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "can't make directory: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < path_size) path[i] = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (mkdir(ro_path, 0755) < 0) {
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "can't make directory: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushboolean(ctx, true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static int lib_fs_readdir(lua_State *ctx) {
|
||||||
|
const char *path = fs_check_path(ctx, 1, NULL);
|
||||||
|
|
||||||
|
DIR *dir = opendir(path);
|
||||||
|
if (!dir) {
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "failed to open directory: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushlightuserdata(ctx, dir);
|
||||||
|
lua_pushcclosure(ctx, readdir_next, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lib_fs_is_file(lua_State *ctx) {
|
||||||
|
const char *path = fs_check_path(ctx, 1, NULL);
|
||||||
|
struct stat stat;
|
||||||
|
if (lstat(path, &stat) < 0) {
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "failed to open directory: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushboolean(ctx, S_ISREG(stat.st_mode));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static int lib_fs_is_dir(lua_State *ctx) {
|
||||||
|
const char *path = fs_check_path(ctx, 1, NULL);
|
||||||
|
struct stat stat;
|
||||||
|
if (lstat(path, &stat) < 0) {
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "failed to open directory: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushboolean(ctx, S_ISDIR(stat.st_mode));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static int lib_fs_lstat(lua_State *ctx) {
|
||||||
|
const char *path = fs_check_path(ctx, 1, NULL);
|
||||||
|
struct stat stat;
|
||||||
|
if (lstat(path, &stat) < 0) {
|
||||||
|
lua_pushnil(ctx);
|
||||||
|
lua_pushfstring(ctx, "failed to stat: %s", strerror(errno));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_createtable(ctx, 0, 7);
|
||||||
|
|
||||||
|
lua_pushinteger(ctx, stat.st_mode);
|
||||||
|
lua_setfield(ctx, -2, "mode");
|
||||||
|
lua_pushinteger(ctx, stat.st_size);
|
||||||
|
lua_setfield(ctx, -2, "size");
|
||||||
|
|
||||||
|
lua_pushinteger(ctx, stat.st_uid);
|
||||||
|
lua_setfield(ctx, -2, "uid");
|
||||||
|
lua_pushinteger(ctx, stat.st_gid);
|
||||||
|
lua_setfield(ctx, -2, "gid");
|
||||||
|
|
||||||
|
lua_pushinteger(ctx, stat.st_ctim.tv_sec);
|
||||||
|
lua_setfield(ctx, -2, "ctime");
|
||||||
|
lua_pushinteger(ctx, stat.st_mtim.tv_sec);
|
||||||
|
lua_setfield(ctx, -2, "mtime");
|
||||||
|
lua_pushinteger(ctx, stat.st_atim.tv_sec);
|
||||||
|
lua_setfield(ctx, -2, "atime");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int lualib_open_fs(lua_State *ctx) {
|
||||||
|
luaL_newlib(ctx, ((luaL_Reg[]) {
|
||||||
|
{ "mkdir", lib_fs_mkdir },
|
||||||
|
{ "readdir", lib_fs_readdir },
|
||||||
|
|
||||||
|
{ "is_file", lib_fs_is_file },
|
||||||
|
{ "is_dir", lib_fs_is_dir },
|
||||||
|
{ NULL, NULL },
|
||||||
|
}));
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
149
src/main/args.lua
Normal file
149
src/main/args.lua
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
local args = {};
|
||||||
|
|
||||||
|
--- @alias consumer (fun(next: fun(): string?)) | string
|
||||||
|
--- @alias consumers { [string]: consumer, [1]: consumer }
|
||||||
|
|
||||||
|
--- @param consumers consumers
|
||||||
|
--- @param name string
|
||||||
|
local function get_consumer(consumers, name)
|
||||||
|
local consumer = nil;
|
||||||
|
local path = {};
|
||||||
|
local n = 0;
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local curr = consumer or name;
|
||||||
|
if path[curr] then
|
||||||
|
local path_arr = {};
|
||||||
|
|
||||||
|
for k, v in next, path do
|
||||||
|
path_arr[v] = k;
|
||||||
|
end
|
||||||
|
|
||||||
|
error("Alias to '" .. curr .. "' is recursive: " .. table.concat(path_arr, " -> "));
|
||||||
|
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 digest_next;
|
||||||
|
|
||||||
|
--- @param consumers consumers
|
||||||
|
--- @param name string
|
||||||
|
--- @param next fun(): string?
|
||||||
|
local function digest_arg(consumers, name, next)
|
||||||
|
get_consumer(consumers, name)(next);
|
||||||
|
return digest_next(consumers, next);
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param consumers consumers
|
||||||
|
--- @param next fun(): string?
|
||||||
|
--- @param only_rest? boolean
|
||||||
|
local function digest_rest(consumers, next, only_rest)
|
||||||
|
if not consumers[1] then
|
||||||
|
local arg = next();
|
||||||
|
if not arg then return nil end
|
||||||
|
error("Invalid argument '" .. arg .. "'");
|
||||||
|
end
|
||||||
|
|
||||||
|
local consumed = false;
|
||||||
|
local function our_consumer()
|
||||||
|
consumed = true;
|
||||||
|
return next();
|
||||||
|
end
|
||||||
|
|
||||||
|
consumers[1](our_consumer);
|
||||||
|
|
||||||
|
if not consumed then
|
||||||
|
local arg = next();
|
||||||
|
if not arg then return nil end
|
||||||
|
error("Invalid argument '" .. arg .. "'");
|
||||||
|
end
|
||||||
|
|
||||||
|
if only_rest then
|
||||||
|
return digest_rest(consumers, next, true);
|
||||||
|
else
|
||||||
|
return digest_next(consumers, next);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param consumers consumers
|
||||||
|
--- @param next fun(): string?
|
||||||
|
function digest_next(consumers, next)
|
||||||
|
local arg = next();
|
||||||
|
if not arg then return nil end
|
||||||
|
|
||||||
|
if arg == "--" then
|
||||||
|
return digest_rest(consumers, next, true);
|
||||||
|
end
|
||||||
|
|
||||||
|
local name, val;
|
||||||
|
|
||||||
|
name, val = arg:match "^%-%-(.-)=(.+)$";
|
||||||
|
if name then
|
||||||
|
local function our_next()
|
||||||
|
our_next = next;
|
||||||
|
return val;
|
||||||
|
end
|
||||||
|
|
||||||
|
return digest_arg(consumers, name, function () return our_next() end);
|
||||||
|
end
|
||||||
|
|
||||||
|
name = arg:match "^%-%-(.-)$";
|
||||||
|
if name then
|
||||||
|
return digest_arg(consumers, name, next);
|
||||||
|
end
|
||||||
|
|
||||||
|
name = arg:match "^%-(.-)$";
|
||||||
|
if name then
|
||||||
|
for c in name:gmatch "." do
|
||||||
|
digest_arg(consumers, c, next);
|
||||||
|
end
|
||||||
|
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function our_next()
|
||||||
|
our_next = next;
|
||||||
|
return arg;
|
||||||
|
end
|
||||||
|
|
||||||
|
return digest_rest(consumers, function () return our_next() end, false);
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @param consumers consumers
|
||||||
|
--- @returns fun(...: string)
|
||||||
|
function args.parser(consumers)
|
||||||
|
return function (...)
|
||||||
|
local args = { ... };
|
||||||
|
local i = 0;
|
||||||
|
|
||||||
|
local function next()
|
||||||
|
i = i + 1;
|
||||||
|
return args[i];
|
||||||
|
end
|
||||||
|
|
||||||
|
return digest_next(consumers, next);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return args;
|
430
src/main/init.lua
Normal file
430
src/main/init.lua
Normal file
@ -0,0 +1,430 @@
|
|||||||
|
local recurse = require "recurse";
|
||||||
|
local fs = require "fs";
|
||||||
|
|
||||||
|
local help_msg = [[
|
||||||
|
mklua by TopchetoEU
|
||||||
|
|
||||||
|
A tool to compile a simple application, consisting of lua and C files, as well
|
||||||
|
as some assets to one executable.
|
||||||
|
|
||||||
|
[file].lua - Shorthand for --lua-file [file].lua
|
||||||
|
[file].c - Shorthand for --c-file [file].c
|
||||||
|
--lua-dir [dir] - Specifies a folder, in which to look for lua files
|
||||||
|
--lua-file [file]
|
||||||
|
--lua-file [basename]:[file] - Includes a single lua file
|
||||||
|
--c-dir [dir] - Specifies a folder, in which to look for C files
|
||||||
|
--c-file [file]
|
||||||
|
--c-file [basename]:[file] - Includes a single C file
|
||||||
|
--asset-dir [dir] - Specifies a folder, in which to look for assets
|
||||||
|
--asset-file [file]
|
||||||
|
--asset-file [basename]:[file] - Specifies a folder, in which to look for assets
|
||||||
|
--cc-args - Arguments, which to pass to the C compiler
|
||||||
|
--entry - The module, which to use as an entry
|
||||||
|
--output - The file, to which to compile
|
||||||
|
--debug - Emits debug symbols for lua and C
|
||||||
|
--dump - Prints out the generated C entry file and exists
|
||||||
|
--help - shows this message
|
||||||
|
|
||||||
|
A mklua "project" consists of modules and assets, which are searched in their
|
||||||
|
respective directories.
|
||||||
|
|
||||||
|
Modules consist of lua and C modules. Lua modules will be searched for in the
|
||||||
|
directories, specified with "--lua-dir", and will be put in "package.preload"
|
||||||
|
(the name of the preloaded module is in the lua format, relative to the folder,
|
||||||
|
in which the file was found). The same goes with the C modules, with the
|
||||||
|
exception that a C module must declare a lua_CFunction of the name
|
||||||
|
"lualib_open_path_to_my_module" (for a C module that is called
|
||||||
|
'path.to.my.module'). That function's return value will be used to initialize
|
||||||
|
the module's value.
|
||||||
|
|
||||||
|
Assets on the other hand will be searched in the same way modules are, but will
|
||||||
|
be put in a module, called "assets". The value of this module will be a map of
|
||||||
|
the asset filenames, relative to the folder they were found in, mapped to their
|
||||||
|
content, as string.
|
||||||
|
|
||||||
|
The entry is a module, that is expected to return a function. That function will
|
||||||
|
be called with the CLI args, when the program is run.
|
||||||
|
]];
|
||||||
|
|
||||||
|
|
||||||
|
local c_format = [[
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
char bytecode[] = %s;
|
||||||
|
|
||||||
|
void load_libraries(lua_State *ctx) {
|
||||||
|
luaL_openlibs(ctx);
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
|
||||||
|
static int print_error(lua_State *ctx) {
|
||||||
|
luaL_traceback(ctx, ctx, "unhandled error: ", 1);
|
||||||
|
const char *str = lua_tostring(ctx, -1);
|
||||||
|
fprintf(stderr, "%%s\n", str);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char **argv) {
|
||||||
|
lua_State *ctx = luaL_newstate();
|
||||||
|
load_libraries(ctx);
|
||||||
|
|
||||||
|
lua_pushcfunction(ctx, print_error);
|
||||||
|
int err_handler = lua_gettop(ctx);
|
||||||
|
|
||||||
|
if (luaL_loadbuffer(ctx, bytecode, sizeof bytecode - 1, "<main>")) {
|
||||||
|
fprintf(stderr, "lua bytecode error: %%s", lua_tostring(ctx, -1));
|
||||||
|
lua_close(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
lua_pushstring(ctx, argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_pcall(ctx, argc - 1, 0, err_handler)) {
|
||||||
|
lua_close(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_close(ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]];
|
||||||
|
|
||||||
|
local assets_load_func = [[
|
||||||
|
static int get_assets(lua_State *ctx) {
|
||||||
|
lua_pushvalue(ctx, lua_upvalueindex(1));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
]];
|
||||||
|
|
||||||
|
local asset_init_format = [[
|
||||||
|
lua_createtable(ctx, 0, 0);
|
||||||
|
lua_pushvalue(ctx, -1);
|
||||||
|
|
||||||
|
luaL_getsubtable(ctx, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
|
||||||
|
lua_pushvalue(ctx, -2);
|
||||||
|
lua_setfield(ctx, -2, "assets");
|
||||||
|
lua_pop(ctx, 2);
|
||||||
|
]];
|
||||||
|
|
||||||
|
local asset_add_format = [[
|
||||||
|
lua_pushlstring(ctx, %s, %d);
|
||||||
|
lua_setfield(ctx, -2, %q);
|
||||||
|
]];
|
||||||
|
|
||||||
|
local entry_invoke_format = [[
|
||||||
|
return xpcall(require %q, function (err)
|
||||||
|
print("unhandled error: " .. tostring(err));
|
||||||
|
|
||||||
|
local i = 2;
|
||||||
|
local prefix = " ";
|
||||||
|
|
||||||
|
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
|
||||||
|
print(prefix .. location .. " in " .. name);
|
||||||
|
else
|
||||||
|
print(prefix .. location);
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
|
end
|
||||||
|
end, ...);
|
||||||
|
]];
|
||||||
|
|
||||||
|
--- @generic T
|
||||||
|
--- @param arr T[]
|
||||||
|
--- @return fun(): T?
|
||||||
|
local function iterate(arr)
|
||||||
|
local i = 0;
|
||||||
|
local n = #arr;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
if arr == nil then
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
|
|
||||||
|
if i > n then
|
||||||
|
--- @diagnostic disable-next-line: cast-local-type
|
||||||
|
arr = nil;
|
||||||
|
return nil;
|
||||||
|
else
|
||||||
|
return arr[i];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_module_name(f, prefix)
|
||||||
|
return f
|
||||||
|
-- Remove prefix slashes
|
||||||
|
:gsub("^[/\\]+", "")
|
||||||
|
-- Replace slashes with dots
|
||||||
|
:gsub("[/\\]", ".")
|
||||||
|
-- Remove .init suffix
|
||||||
|
:gsub("%.init$", "");
|
||||||
|
end
|
||||||
|
|
||||||
|
local function c_escape(str)
|
||||||
|
return "\"" .. str:gsub(".", function (c)
|
||||||
|
local b = string.byte(c);
|
||||||
|
|
||||||
|
if c == "\n" then
|
||||||
|
return "\\n";
|
||||||
|
elseif c == "\\" then
|
||||||
|
return "\\\\";
|
||||||
|
elseif c == "\"" then
|
||||||
|
return "\\\"";
|
||||||
|
elseif b >= 32 and b <= 126 then
|
||||||
|
return c;
|
||||||
|
else
|
||||||
|
return ("\\%.3o"):format(b);
|
||||||
|
end
|
||||||
|
end) .. "\"";
|
||||||
|
end
|
||||||
|
|
||||||
|
return function (...)
|
||||||
|
--- @type string[]
|
||||||
|
local cc_args = {};
|
||||||
|
|
||||||
|
local lua_files = {};
|
||||||
|
local c_files = {};
|
||||||
|
local asset_files = {};
|
||||||
|
local has_assets = false;
|
||||||
|
local strip = true;
|
||||||
|
local dump = false;
|
||||||
|
local entry;
|
||||||
|
|
||||||
|
local out_path;
|
||||||
|
|
||||||
|
require "args".parser {
|
||||||
|
function (arg)
|
||||||
|
local path = assert(arg());
|
||||||
|
|
||||||
|
local basename = path:match "^(.+)%.lua$";
|
||||||
|
if basename then
|
||||||
|
lua_files[basename] = path;
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
basename = path:match "^(.+)%.c$";
|
||||||
|
if basename then
|
||||||
|
c_files[basename] = path;
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
if path:match "^%-" then
|
||||||
|
error("Unknown option '" .. path .. "'");
|
||||||
|
else
|
||||||
|
error("'" .. path .. "' is neither a .c or a .lua file. Use --asset-file if you want to add an asset");
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
help = function ()
|
||||||
|
print(help_msg);
|
||||||
|
os.exit();
|
||||||
|
end,
|
||||||
|
|
||||||
|
["lua-dir"] = function (arg)
|
||||||
|
local dir = assert(arg());
|
||||||
|
for path in recurse(dir) do
|
||||||
|
local basename = path:match "^(.+)%.lua$";
|
||||||
|
if basename and fs.is_file(path) then
|
||||||
|
lua_files[basename:sub(#dir + 1)] = path;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
["lua-file"] = function (arg)
|
||||||
|
local val = assert(arg());
|
||||||
|
|
||||||
|
local basename, realname = val:match "^(.+):(.+)$";
|
||||||
|
if basename then
|
||||||
|
lua_files[basename] = realname;
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
basename = val:match "$(.+)%.[^%.]+";
|
||||||
|
if basename then
|
||||||
|
lua_files[basename] = val;
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
lua_files[val] = val;
|
||||||
|
end,
|
||||||
|
|
||||||
|
["c-dir"] = function (arg)
|
||||||
|
local dir = assert(arg());
|
||||||
|
for path in recurse(dir) do
|
||||||
|
local basename = path:match "^(.+)%.c$";
|
||||||
|
if basename and fs.is_file(path) then
|
||||||
|
c_files[basename:sub(#dir + 1)] = path;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
["c-file"] = function (arg)
|
||||||
|
local val = assert(arg());
|
||||||
|
|
||||||
|
if not fs.is_file(val) then
|
||||||
|
error ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local basename, realname = val:match "^(.+):(.+)$";
|
||||||
|
if basename then
|
||||||
|
c_files[basename] = realname;
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
basename = val:match "$(.+)%.[^%.]+";
|
||||||
|
if basename then
|
||||||
|
c_files[basename] = val;
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
c_files[val] = val;
|
||||||
|
end,
|
||||||
|
|
||||||
|
["asset-dir"] = function (arg)
|
||||||
|
local dir = assert(arg());
|
||||||
|
for path in recurse(dir) do
|
||||||
|
if fs.is_file(path) then
|
||||||
|
asset_files[path:sub(#dir + 1)] = path;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
has_assets = true;
|
||||||
|
end,
|
||||||
|
["asset-file"] = function (arg)
|
||||||
|
local val = assert(arg());
|
||||||
|
|
||||||
|
local basename, realname = val:match "^(.+):(.+)$";
|
||||||
|
if basename then
|
||||||
|
c_files[basename] = realname;
|
||||||
|
else
|
||||||
|
c_files[val] = val;
|
||||||
|
end
|
||||||
|
|
||||||
|
has_assets = true;
|
||||||
|
end,
|
||||||
|
|
||||||
|
["cc-arg"] = function (arg)
|
||||||
|
table.insert(cc_args, assert(arg()));
|
||||||
|
end,
|
||||||
|
|
||||||
|
entry = function (arg)
|
||||||
|
entry = assert(arg());
|
||||||
|
end,
|
||||||
|
debug = function ()
|
||||||
|
strip = false;
|
||||||
|
end,
|
||||||
|
dump = function ()
|
||||||
|
dump = true;
|
||||||
|
end,
|
||||||
|
|
||||||
|
output = function (arg)
|
||||||
|
if out_path then
|
||||||
|
error "--output may be specified once";
|
||||||
|
end
|
||||||
|
|
||||||
|
out_path = assert(arg());
|
||||||
|
end,
|
||||||
|
|
||||||
|
o = "output",
|
||||||
|
e = "entry",
|
||||||
|
g = "debug",
|
||||||
|
h = "help",
|
||||||
|
} (...);
|
||||||
|
|
||||||
|
if not out_path then
|
||||||
|
error "no output file specified; aborting...";
|
||||||
|
end
|
||||||
|
if not entry then
|
||||||
|
error "no entry specified; aborting...";
|
||||||
|
end
|
||||||
|
|
||||||
|
local lib_parts = {};
|
||||||
|
local extern_parts = {};
|
||||||
|
|
||||||
|
local lua_parts = {};
|
||||||
|
|
||||||
|
local files = {};
|
||||||
|
|
||||||
|
for basename, path in pairs(lua_files) do
|
||||||
|
local module_name = get_module_name(basename);
|
||||||
|
|
||||||
|
-- We do the separate bytecode to keep line numbers and file names
|
||||||
|
local bytecode = string.dump(assert(load(io.lines(path, 4096), "@" .. path, "t")), strip);
|
||||||
|
table.insert(lua_parts, ("package.preload[%q] = load(%q, %q, 'b');"):format(module_name, bytecode, path));
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(lua_parts, entry_invoke_format:format(entry));
|
||||||
|
|
||||||
|
if has_assets then
|
||||||
|
table.insert(extern_parts, assets_load_func);
|
||||||
|
table.insert(lib_parts, asset_init_format);
|
||||||
|
end
|
||||||
|
|
||||||
|
for basename, path in pairs(asset_files) do
|
||||||
|
local f = assert(io.open(path, "r"));
|
||||||
|
local content = assert(f:read "*a");
|
||||||
|
assert(f:close());
|
||||||
|
|
||||||
|
table.insert(lib_parts, asset_add_format:format(content, #content, basename));
|
||||||
|
end
|
||||||
|
|
||||||
|
for basename, path in pairs(c_files) do
|
||||||
|
local module_name = get_module_name(basename);
|
||||||
|
local func_name = "lualib_open_" .. module_name:gsub("%.", "_");
|
||||||
|
|
||||||
|
table.insert(extern_parts, ("extern int %s(lua_State *ctx);"):format(func_name));
|
||||||
|
table.insert(lib_parts, ("luaL_requiref(ctx, %q, %s, false);"):format(module_name, func_name));
|
||||||
|
table.insert(files, path);
|
||||||
|
end
|
||||||
|
|
||||||
|
local bytecode = string.dump(assert(load(iterate(lua_parts), "@<entry>", "t")), true);
|
||||||
|
local file = c_format:format(table.concat(extern_parts, "\n"), c_escape(bytecode), table.concat(lib_parts, "\n\t"));
|
||||||
|
|
||||||
|
if dump then
|
||||||
|
io.stdout:write(file);
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local f = assert(io.popen(table.concat {
|
||||||
|
"cc -llua -x c - ",
|
||||||
|
table.concat(files, " "),
|
||||||
|
" ",
|
||||||
|
strip and "" or "-g ",
|
||||||
|
"-o ", out_path, " ",
|
||||||
|
table.concat(cc_args, " ")
|
||||||
|
}, "w"));
|
||||||
|
|
||||||
|
assert(f:write(file));
|
||||||
|
assert(f:close());
|
||||||
|
end
|
394
src/main/init.lua.old
Normal file
394
src/main/init.lua.old
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
local recurse = require "recurse";
|
||||||
|
|
||||||
|
local help_msg = [[
|
||||||
|
mklua by TopchetoEU
|
||||||
|
|
||||||
|
A tool to compile a simple application, consisting of lua and C files, as well
|
||||||
|
as some assets to one executable.
|
||||||
|
|
||||||
|
--lua_dir: Specifies a folder, in which to look for lua files
|
||||||
|
--c_dir: Specifies a folder, in which to look for C files
|
||||||
|
--assets_dir: Specifies a folder, in which to look for assets
|
||||||
|
--cc_args: Arguments, which to pass to the C compiler
|
||||||
|
--entry: The module, which to use as an entry
|
||||||
|
--output: The file, to which to compile
|
||||||
|
|
||||||
|
A mklua "project" consists of modules and assets, which are searched in their
|
||||||
|
respective directories.
|
||||||
|
|
||||||
|
Modules consist of lua and C modules. Lua modules will be searched for in the
|
||||||
|
directories, specified with "--lua_dir", and will be put in "package.preload"
|
||||||
|
(the name of the preloaded module is in the lua format, relative to the folder,
|
||||||
|
in which the file was found). The same goes with the C modules, with the
|
||||||
|
exception that a C module must declare a lua_CFunction of the name
|
||||||
|
"lualib_open_path_to_my_module" (for a C module that is called
|
||||||
|
'path.to.my.module'). That function's return value will be used to initialize
|
||||||
|
the module's value.
|
||||||
|
|
||||||
|
Assets on the other hand will be searched in the same way modules are, but will
|
||||||
|
be put in a module, called "assets". The value of this module will be a map of
|
||||||
|
the asset filenames, relative to the folder they were found in, mapped to their
|
||||||
|
content, as string.
|
||||||
|
|
||||||
|
The entry is a module, that is expected to return a function. That function will
|
||||||
|
be called with the CLI args, when the program is run.
|
||||||
|
|
||||||
|
You can create a Makefile, so that you don't have to write out the same command,
|
||||||
|
using --make. NOTE: This will overwrite any existing Makefile.
|
||||||
|
]];
|
||||||
|
|
||||||
|
|
||||||
|
local c_format = [[
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
char bytecode[] = %s;
|
||||||
|
|
||||||
|
void load_libraries(lua_State *ctx) {
|
||||||
|
luaL_openlibs(ctx);
|
||||||
|
%s
|
||||||
|
}
|
||||||
|
|
||||||
|
static int print_error(lua_State *ctx) {
|
||||||
|
luaL_traceback(ctx, ctx, "unhandled error: ", 1);
|
||||||
|
const char *str = lua_tostring(ctx, -1);
|
||||||
|
fprintf(stderr, "%%s\n", str);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char **argv) {
|
||||||
|
lua_State *ctx = luaL_newstate();
|
||||||
|
load_libraries(ctx);
|
||||||
|
|
||||||
|
lua_pushcfunction(ctx, print_error);
|
||||||
|
int err_handler = lua_gettop(ctx);
|
||||||
|
|
||||||
|
if (luaL_loadbuffer(ctx, bytecode, sizeof bytecode - 1, "<main>")) {
|
||||||
|
fprintf(stderr, "lua bytecode error: %%s", lua_tostring(ctx, -1));
|
||||||
|
lua_close(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
lua_pushstring(ctx, argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_pcall(ctx, argc - 1, 0, err_handler)) {
|
||||||
|
lua_close(ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_close(ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]];
|
||||||
|
|
||||||
|
local assets_load_func = [[
|
||||||
|
static int get_assets(lua_State *ctx) {
|
||||||
|
lua_pushvalue(ctx, lua_upvalueindex(1));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
]];
|
||||||
|
|
||||||
|
local asset_init_format = [[
|
||||||
|
lua_createtable(ctx, 0, 0);
|
||||||
|
lua_pushvalue(ctx, -1);
|
||||||
|
lua_pushcclosure(ctx, get_assets, 1);
|
||||||
|
|
||||||
|
luaL_getsubtable(ctx, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
|
||||||
|
lua_pushvalue(ctx, -2);
|
||||||
|
lua_setfield(ctx, -2, "assets");
|
||||||
|
lua_pop(ctx, 2);
|
||||||
|
]];
|
||||||
|
|
||||||
|
local asset_add_format = [[
|
||||||
|
lua_pushlstring(ctx, %s, %d);
|
||||||
|
lua_setfield(ctx, -2, %q);
|
||||||
|
]];
|
||||||
|
|
||||||
|
local entry_invoke_format = [[
|
||||||
|
return xpcall(require %q, function (err)
|
||||||
|
print("unhandled error: " .. tostring(err));
|
||||||
|
|
||||||
|
local i = 2;
|
||||||
|
local prefix = " ";
|
||||||
|
|
||||||
|
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
|
||||||
|
print(prefix .. location .. " in " .. name);
|
||||||
|
else
|
||||||
|
print(prefix .. location);
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
|
end
|
||||||
|
end, ...);
|
||||||
|
]];
|
||||||
|
|
||||||
|
local makefile_format = [[
|
||||||
|
ifeq ($(DEBUG), yes)
|
||||||
|
dbg_flag := -g
|
||||||
|
endif
|
||||||
|
|
||||||
|
%s:
|
||||||
|
%s \
|
||||||
|
%s \
|
||||||
|
$(dbg_flag)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm %s
|
||||||
|
]];
|
||||||
|
|
||||||
|
--- @generic T
|
||||||
|
--- @param arr T[]
|
||||||
|
--- @return fun(): T?
|
||||||
|
local function iterate(arr)
|
||||||
|
local i = 0;
|
||||||
|
local n = #arr;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
if arr == nil then
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
|
|
||||||
|
if i > n then
|
||||||
|
--- @diagnostic disable-next-line: cast-local-type
|
||||||
|
arr = nil;
|
||||||
|
return nil;
|
||||||
|
else
|
||||||
|
return arr[i];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_module_name(f, prefix)
|
||||||
|
return f
|
||||||
|
-- Remove currently iterated dir prefix
|
||||||
|
:sub(#prefix + 1)
|
||||||
|
-- Remove prefix slashes
|
||||||
|
:gsub("^[/\\]+", "")
|
||||||
|
-- Replace slashes with dots
|
||||||
|
:gsub("[/\\]", ".")
|
||||||
|
-- Remove .init suffix
|
||||||
|
:gsub("%.init$", "");
|
||||||
|
end
|
||||||
|
|
||||||
|
local function c_escape(str)
|
||||||
|
return "\"" .. str:gsub(".", function (c)
|
||||||
|
local b = string.byte(c);
|
||||||
|
|
||||||
|
if c == "\n" then
|
||||||
|
return "\\n";
|
||||||
|
elseif c == "\\" then
|
||||||
|
return "\\\\";
|
||||||
|
elseif c == "\"" then
|
||||||
|
return "\\\"";
|
||||||
|
elseif b >= 32 and b <= 126 then
|
||||||
|
return c;
|
||||||
|
else
|
||||||
|
return ("\\%.3o"):format(b);
|
||||||
|
end
|
||||||
|
end) .. "\"";
|
||||||
|
end
|
||||||
|
|
||||||
|
return function (...)
|
||||||
|
--- @type string[]
|
||||||
|
local cc_args = {};
|
||||||
|
|
||||||
|
local lua_dirs = {};
|
||||||
|
local c_dirs = {};
|
||||||
|
local assets_dirs = {};
|
||||||
|
local strip = true;
|
||||||
|
local dump = false;
|
||||||
|
local make = false;
|
||||||
|
local make_exec = "mklua";
|
||||||
|
local entry;
|
||||||
|
|
||||||
|
local out_path;
|
||||||
|
|
||||||
|
require "args"({
|
||||||
|
help = function ()
|
||||||
|
print(help_msg);
|
||||||
|
os.exit();
|
||||||
|
end,
|
||||||
|
|
||||||
|
lua_dir = function (arg)
|
||||||
|
table.insert(lua_dirs, arg);
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
c_dir = function (arg)
|
||||||
|
table.insert(c_dirs, arg);
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
assets_dir = function (arg)
|
||||||
|
table.insert(assets_dirs, arg);
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
cc_args = function (arg)
|
||||||
|
table.insert(cc_args, arg);
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
entry = function (arg)
|
||||||
|
entry = arg;
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
debug = function ()
|
||||||
|
strip = false;
|
||||||
|
return false;
|
||||||
|
end,
|
||||||
|
dump = function ()
|
||||||
|
dump = true;
|
||||||
|
return false;
|
||||||
|
end,
|
||||||
|
make = function ()
|
||||||
|
make = true;
|
||||||
|
return false;
|
||||||
|
end,
|
||||||
|
make_exe = function (arg)
|
||||||
|
make_exec = arg;
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
|
||||||
|
output = function (arg)
|
||||||
|
if out_path then
|
||||||
|
error "--output may be specified once";
|
||||||
|
end
|
||||||
|
|
||||||
|
out_path = arg;
|
||||||
|
return true;
|
||||||
|
end,
|
||||||
|
|
||||||
|
o = "output",
|
||||||
|
g = "debug",
|
||||||
|
h = "help",
|
||||||
|
}, ...);
|
||||||
|
|
||||||
|
if not out_path then
|
||||||
|
error "no output file specified; aborting...";
|
||||||
|
end
|
||||||
|
if not entry then
|
||||||
|
error "no entry specified; aborting...";
|
||||||
|
end
|
||||||
|
|
||||||
|
if make then
|
||||||
|
local args = {
|
||||||
|
("--entry %q"):format(entry),
|
||||||
|
("--output %q"):format(out_path),
|
||||||
|
};
|
||||||
|
|
||||||
|
for dir in iterate(lua_dirs) do
|
||||||
|
table.insert(args, ("--lua_dir %q"):format(dir));
|
||||||
|
end
|
||||||
|
for dir in iterate(c_dirs) do
|
||||||
|
table.insert(args, ("--c_dir %q"):format(dir));
|
||||||
|
end
|
||||||
|
for dir in iterate(assets_dirs) do
|
||||||
|
table.insert(args, ("--assets_dir %q"):format(dir));
|
||||||
|
end
|
||||||
|
|
||||||
|
local make_out = assert(io.open("Makefile", "w"));
|
||||||
|
make_out:write(makefile_format:format(out_path, make_exec, table.concat(args, " \\\n\t\t"), out_path));
|
||||||
|
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local lib_parts = {};
|
||||||
|
local extern_parts = {};
|
||||||
|
|
||||||
|
local lua_parts = {};
|
||||||
|
|
||||||
|
local files = {};
|
||||||
|
|
||||||
|
for lua_dir in iterate(lua_dirs) do
|
||||||
|
for path in recurse(lua_dir, fs.readdir) do
|
||||||
|
local basename = path:match "(.+)%.lua$";
|
||||||
|
if basename and fs.is_file(path) then
|
||||||
|
local module_name = get_module_name(basename, lua_dir);
|
||||||
|
|
||||||
|
-- We do the separate bytecode to keep line numbers and file names
|
||||||
|
local bytecode = string.dump(assert(load(io.lines(path, 4096), "@" .. path, "t")), strip);
|
||||||
|
table.insert(lua_parts, ("package.preload[%q] = load(%q, %q, 'b');"):format(module_name, bytecode, path));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(lua_parts, entry_invoke_format:format(entry));
|
||||||
|
|
||||||
|
if #assets_dirs > 0 then
|
||||||
|
table.insert(extern_parts, assets_load_func);
|
||||||
|
table.insert(lib_parts, asset_init_format);
|
||||||
|
end
|
||||||
|
|
||||||
|
for assets_dir in iterate(assets_dirs) do
|
||||||
|
for path in recurse(assets_dir, fs.readdir) do
|
||||||
|
if fs.is_file(path) then
|
||||||
|
local content = c_escape(assert(io.lines(path, "*a"))());
|
||||||
|
table.insert(lib_parts, asset_add_format:format(content, #content, path:sub(#assets_dir + 2)));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for c_dir in iterate(c_dirs) do
|
||||||
|
for path in recurse(c_dir, fs.readdir) do
|
||||||
|
local basename = path:match "(.+)%.c$";
|
||||||
|
if basename and fs.is_file(path) then
|
||||||
|
local module_name = get_module_name(basename, c_dir);
|
||||||
|
local func_name = "lualib_open_" .. module_name:gsub("%.", "_");
|
||||||
|
|
||||||
|
table.insert(extern_parts, ("extern int %s(lua_State *ctx);"):format(func_name));
|
||||||
|
table.insert(lib_parts, ("luaL_requiref(ctx, %q, %s, false);"):format(module_name, func_name));
|
||||||
|
table.insert(files, path);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local bytecode = string.dump(assert(load(iterate(lua_parts), "@<entry>", "t")), true);
|
||||||
|
local file = c_format:format(table.concat(extern_parts, "\n"), c_escape(bytecode), table.concat(lib_parts, "\n\t"));
|
||||||
|
|
||||||
|
if dump then
|
||||||
|
local out_f = assert(io.open(out_path, "w"));
|
||||||
|
out_f:write(file);
|
||||||
|
out_f:close();
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
local f = assert(io.popen(table.concat {
|
||||||
|
"cc -llua -x c - ",
|
||||||
|
table.concat(files, " "),
|
||||||
|
" ",
|
||||||
|
strip and "-g " or "",
|
||||||
|
"-o ", out_path, " ",
|
||||||
|
table.concat(cc_args, " ")
|
||||||
|
}, "w"));
|
||||||
|
|
||||||
|
assert(f:write(file));
|
||||||
|
assert(f:close());
|
||||||
|
end
|
77
src/main/mod/c.lua
Normal file
77
src/main/mod/c.lua
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
local utils = require "utils";
|
||||||
|
local c = {};
|
||||||
|
|
||||||
|
local function c_escape(str)
|
||||||
|
return "\"" .. str:gsub(".", function (c)
|
||||||
|
local b = string.byte(c);
|
||||||
|
|
||||||
|
if c == "\n" then
|
||||||
|
return "\\n";
|
||||||
|
elseif c == "\\" then
|
||||||
|
return "\\\\";
|
||||||
|
elseif c == "\"" then
|
||||||
|
return "\\\"";
|
||||||
|
elseif b >= 32 and b <= 126 then
|
||||||
|
return c;
|
||||||
|
else
|
||||||
|
return ("\\%.3o"):format(b);
|
||||||
|
end
|
||||||
|
end) .. "\"";
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Executes a GCC-like C compiler
|
||||||
|
--- @param settings { inputs: string[], output: string, flags?: string[], cc?: string }
|
||||||
|
--- @return task
|
||||||
|
function c.compile(settings)
|
||||||
|
local cc = settings.cc or "cc";
|
||||||
|
local inputs = settings.inputs;
|
||||||
|
local output = settings.output;
|
||||||
|
local flags = settings.flags or {};
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
if utils.check_files(inputs, { output }) then return end
|
||||||
|
|
||||||
|
local rest = {};
|
||||||
|
table.move(inputs, 1, #inputs, 1, rest);
|
||||||
|
table.move(flags, 1, #flags, #rest + 1, rest);
|
||||||
|
|
||||||
|
return utils.simple_exec(cc, "-o", output, table.unpack(rest));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a C file, defining the specified constant that will contain a string representation of the input file
|
||||||
|
--- @param settings { input: string, output: string, const_name: string }
|
||||||
|
--- @return task
|
||||||
|
function c.asset(settings)
|
||||||
|
local input = settings.input;
|
||||||
|
local output = settings.output;
|
||||||
|
local const_name = settings.const_name;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
if utils.check_files({ input }, { output }) then return end
|
||||||
|
|
||||||
|
local out_f = assert(io.open(output, "w"));
|
||||||
|
|
||||||
|
assert(out_f:write "#pragma once\n");
|
||||||
|
assert(out_f:write "unsigned char \n");
|
||||||
|
assert(out_f:write(const_name));
|
||||||
|
assert(out_f:write " = ");
|
||||||
|
|
||||||
|
local has_any = false;
|
||||||
|
|
||||||
|
for line in assert(io.lines(input, 128)) do
|
||||||
|
has_any = true;
|
||||||
|
assert(out_f:write "\n\t");
|
||||||
|
assert(out_f:write(c_escape(line)));
|
||||||
|
end
|
||||||
|
|
||||||
|
if not has_any then
|
||||||
|
assert(out_f:write "\"\"");
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(out_f:write ";\n");
|
||||||
|
assert(out_f:close());
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return c;
|
301
src/main/mod/core.lua
Normal file
301
src/main/mod/core.lua
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
local fs = require "fs";
|
||||||
|
local core = {};
|
||||||
|
|
||||||
|
--- @alias task fun(...)
|
||||||
|
|
||||||
|
--- @type { [string]: task }
|
||||||
|
local tasks = {};
|
||||||
|
|
||||||
|
--- Runs a task of the given name
|
||||||
|
--- @param name string
|
||||||
|
function core.run(name, ...)
|
||||||
|
local task = tasks[name];
|
||||||
|
if not task then
|
||||||
|
error("No task '" .. name "'");
|
||||||
|
end
|
||||||
|
|
||||||
|
return task(...);
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a task of the given name
|
||||||
|
--- @param name string
|
||||||
|
--- @param task task
|
||||||
|
function core.task(name, task)
|
||||||
|
tasks[name] = task;
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Converts a file1/file2/**/??/*.txt pattern to a function that can match it
|
||||||
|
--- % is the escape character, and can escape itself
|
||||||
|
local function mk_pattern(wildcard)
|
||||||
|
if type(wildcard) == "function" then
|
||||||
|
return wildcard;
|
||||||
|
end
|
||||||
|
|
||||||
|
local parts = {};
|
||||||
|
local prev = nil;
|
||||||
|
|
||||||
|
for c in wildcard:gmatch "." do
|
||||||
|
if prev == "%" then
|
||||||
|
table.insert(parts, "%" .. c);
|
||||||
|
elseif prev == "*" then
|
||||||
|
if c == "*" then
|
||||||
|
table.insert(parts, "%" .. ".*");
|
||||||
|
else
|
||||||
|
table.insert(parts, "%" .. "[^/]*");
|
||||||
|
table.insert(parts, c);
|
||||||
|
end
|
||||||
|
elseif c ~= "*" then
|
||||||
|
table.insert(parts, c);
|
||||||
|
end
|
||||||
|
|
||||||
|
prev = c;
|
||||||
|
end
|
||||||
|
|
||||||
|
if prev == "*" then
|
||||||
|
table.insert(parts, "%" .. "[^/]*");
|
||||||
|
end
|
||||||
|
|
||||||
|
local pattern = table.concat(parts);
|
||||||
|
|
||||||
|
return function (text)
|
||||||
|
return string.match(text, pattern) ~= nil;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Returns an iterator of file paths inside the 'root' dir.
|
||||||
|
--- If 'pattern' is specified, it is going to be tested for each node in the 'root' dir,
|
||||||
|
--- BEFORE rerooting
|
||||||
|
--- If absolute_reroot or relative_reroot are specified, the output's absolute and relative paths
|
||||||
|
--- will be prefixed with the specified directories, AFTER the pattern check has been performed
|
||||||
|
--- @param settings string | { root: string, pattern?: string }
|
||||||
|
--- @return fun(): string?
|
||||||
|
function core.files(settings)
|
||||||
|
if type(settings) == "function" then
|
||||||
|
return settings;
|
||||||
|
elseif type(settings) == "string" then
|
||||||
|
return core.files { root = settings };
|
||||||
|
end
|
||||||
|
|
||||||
|
local root = settings.root or error "no root specified";
|
||||||
|
local pattern = settings.pattern and mk_pattern(settings.pattern);
|
||||||
|
|
||||||
|
local recurser, recurse_state = require "recurse" (root);
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
for el in recurser, recurse_state do
|
||||||
|
local relative = el:sub(#root + 1);
|
||||||
|
if not pattern or pattern(relative) then
|
||||||
|
return root .. relative;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return core;
|
||||||
|
|
||||||
|
-- --- @diagnostic disable: cast-local-type
|
||||||
|
|
||||||
|
-- --- @alias pattern fun(text: string): boolean
|
||||||
|
|
||||||
|
-- local exec = require "exec";
|
||||||
|
-- local fs = require "fs";
|
||||||
|
|
||||||
|
-- local core = {};
|
||||||
|
|
||||||
|
-- --- Converts a file1/file2/**/??/*.txt pattern to a function that can match it
|
||||||
|
-- --- % is the escape character, and can escape itself
|
||||||
|
-- function core.pattern(wildcard)
|
||||||
|
-- if type(wildcard) == "function" then
|
||||||
|
-- return wildcard;
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- local parts = {};
|
||||||
|
-- local prev = nil;
|
||||||
|
|
||||||
|
-- for c in wildcard:gmatch "." do
|
||||||
|
-- if prev == "%" then
|
||||||
|
-- table.insert(parts, "%" .. c);
|
||||||
|
-- elseif prev == "*" then
|
||||||
|
-- if c == "*" then
|
||||||
|
-- table.insert(parts, "%" .. ".*");
|
||||||
|
-- else
|
||||||
|
-- table.insert(parts, "%" .. "[^/]*");
|
||||||
|
-- table.insert(parts, c);
|
||||||
|
-- end
|
||||||
|
-- elseif c ~= "*" then
|
||||||
|
-- table.insert(parts, c);
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- prev = c;
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- if prev == "*" then
|
||||||
|
-- table.insert(parts, "%" .. "[^/]*");
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- local pattern = table.concat(parts);
|
||||||
|
|
||||||
|
-- return function (text)
|
||||||
|
-- return string.match(text, pattern) ~= nil;
|
||||||
|
-- end;
|
||||||
|
-- end
|
||||||
|
|
||||||
|
|
||||||
|
-- function core.combine(arr)
|
||||||
|
-- local arr_i = 1;
|
||||||
|
-- local curr = arr[1];
|
||||||
|
|
||||||
|
-- return function ()
|
||||||
|
-- while true do
|
||||||
|
-- if arr == nil or arr_i > #arr then
|
||||||
|
-- arr = nil;
|
||||||
|
-- return nil;
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- local res = curr();
|
||||||
|
-- if res ~= nil then return res end
|
||||||
|
|
||||||
|
-- arr_i = arr_i + 1;
|
||||||
|
-- curr = arr[arr_i];
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- --- Creates a generic transformer that independently converts each file to one or more files
|
||||||
|
-- --- The passed function is called once with a settings object. The function it
|
||||||
|
-- --- returns will be called upon each transformation
|
||||||
|
-- function core.create_transformer(func, all)
|
||||||
|
-- local function finish_call(func, ...)
|
||||||
|
-- local res = func(...);
|
||||||
|
-- if type(res) == "string" then
|
||||||
|
-- res = { res };
|
||||||
|
-- end
|
||||||
|
-- if type(res) == "table" then
|
||||||
|
-- local i = 0;
|
||||||
|
-- local arr = res;
|
||||||
|
-- res = function ()
|
||||||
|
-- if not arr then return nil end
|
||||||
|
-- i = i + 1;
|
||||||
|
-- if i > #arr then return nil end
|
||||||
|
|
||||||
|
-- return arr[i];
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- if type(res) == "function" then
|
||||||
|
-- return res;
|
||||||
|
-- else
|
||||||
|
-- error "Invalid return from transformer function";
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- return function (settings)
|
||||||
|
-- local input = core.files(settings.input);
|
||||||
|
-- local output = settings.output or error "no output specified";
|
||||||
|
-- local res_func = func(settings);
|
||||||
|
|
||||||
|
-- if all then
|
||||||
|
-- return function ()
|
||||||
|
-- if not input then return nil end
|
||||||
|
|
||||||
|
-- local a, b = input, output;
|
||||||
|
-- input, output = nil, nil;
|
||||||
|
|
||||||
|
-- return finish_call(res_func, a, b);
|
||||||
|
-- end
|
||||||
|
-- else
|
||||||
|
-- return function ()
|
||||||
|
-- if not input then return nil end
|
||||||
|
|
||||||
|
-- local curr = input();
|
||||||
|
-- if not curr then
|
||||||
|
-- input, output = nil, nil;
|
||||||
|
-- return nil;
|
||||||
|
-- else
|
||||||
|
-- return finish_call(res_func, input, output);
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- --- A generic cc a.o b.o c.o -o res adapter
|
||||||
|
-- core.obj = core.create_transformer(function (settings)
|
||||||
|
-- local cc = settings.cc or "cc";
|
||||||
|
|
||||||
|
-- return function (input, output)
|
||||||
|
-- local rest = {};
|
||||||
|
-- for el in input do table.insert(rest, el) end
|
||||||
|
-- table.move(settings.flags, 1, #settings.flags, #rest + 1, rest);
|
||||||
|
|
||||||
|
-- simple_exec(cc, "-o", output, table.unpack(rest));
|
||||||
|
-- end
|
||||||
|
-- end, true);
|
||||||
|
-- --- A generic luac adapter
|
||||||
|
-- core.lua = core.create_transformer(function (settings)
|
||||||
|
-- local lua = settings.lua or "lua";
|
||||||
|
|
||||||
|
-- return function (input, output)
|
||||||
|
-- simple_exec(lua,
|
||||||
|
-- input.abs,
|
||||||
|
-- "-o", output.abs .. "/" .. input.rel .. ".o",
|
||||||
|
-- table.unpack(settings.flags)
|
||||||
|
-- );
|
||||||
|
-- end
|
||||||
|
-- end);
|
||||||
|
-- --- A generic luac adapter
|
||||||
|
-- core.lua_bundle = core.create_transformer(function (settings)
|
||||||
|
-- local entries = {};
|
||||||
|
|
||||||
|
-- if settings.entry then
|
||||||
|
-- table.insert(entries, settings.entry);
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- if settings.entries then
|
||||||
|
-- table.move(settings.entries, 1, #settings.entries, #entries + 1, entries);
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- if #entries == 0 then
|
||||||
|
-- error "No entries have been specified";
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- return function (input, output)
|
||||||
|
-- local f = assert(io.open(output.abs, "w"));
|
||||||
|
|
||||||
|
-- for path in input do
|
||||||
|
-- local mod_name = get_module_name(path.rel);
|
||||||
|
-- local bytecode = string.dump(assert(load(io.lines(path.abs, 4096), "@" .. path.abs, "t")), settings.strip);
|
||||||
|
-- f:write(("package.preload[%q] = load(%q, nil, \"b\");"):format(mod_name, bytecode));
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- for entry in entries do
|
||||||
|
-- f:write(("require %q (...);"):format(entry));
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end, true);
|
||||||
|
-- --- Generates a C file, containing a binary blob
|
||||||
|
-- core.c_blob = core.create_transformer(function (settings)
|
||||||
|
-- return function (input, output)
|
||||||
|
-- local f = assert(io.open(output.abs, "w"));
|
||||||
|
|
||||||
|
-- f:write("unsigned char %s[] = ");
|
||||||
|
-- f:write(c_escape())
|
||||||
|
|
||||||
|
-- for path in input do
|
||||||
|
-- local mod_name = get_module_name(path.rel);
|
||||||
|
-- local bytecode = string.dump(assert(load(io.lines(path.abs, 4096), "@" .. path.abs, "t")), settings.strip);
|
||||||
|
-- f:write(("package.preload[%q] = load(%q, nil, \"b\");"):format(mod_name, bytecode));
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end);
|
||||||
|
-- --- Generates a lua file, containing a binary blob
|
||||||
|
-- core.lua_blob = core.create_transformer(function (settings)
|
||||||
|
-- return function (input, output)
|
||||||
|
-- end
|
||||||
|
-- end);
|
||||||
|
|
||||||
|
-- return core;
|
6
src/main/mod/glob.lua
Normal file
6
src/main/mod/glob.lua
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
local core = require "mod.core";
|
||||||
|
|
||||||
|
return {
|
||||||
|
run = core.run,
|
||||||
|
task = core.task,
|
||||||
|
};
|
115
src/main/mod/lua.lua
Normal file
115
src/main/mod/lua.lua
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
local utils = require "utils";
|
||||||
|
local lua = {};
|
||||||
|
|
||||||
|
local function get_module_name(path)
|
||||||
|
return path
|
||||||
|
-- Remove extension
|
||||||
|
:gsub("%.[^%.]-$", "")
|
||||||
|
-- Remove prefix slashes
|
||||||
|
:gsub("^[/\\]+", "")
|
||||||
|
-- Replace slashes with dots
|
||||||
|
:gsub("[/\\]", ".")
|
||||||
|
-- Remove .init suffix
|
||||||
|
:gsub("%.init$", "");
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compile(input, output, filename, strip, exe)
|
||||||
|
filename = filename or input;
|
||||||
|
|
||||||
|
if not exe then
|
||||||
|
local func = assert(load(assert(io.lines(input, 4096)), filename, "t"));
|
||||||
|
local bytecode = string.dump(func, strip);
|
||||||
|
|
||||||
|
local out_f = assert(io.open(output, "w"));
|
||||||
|
assert(out_f:write(bytecode));
|
||||||
|
assert(out_f:close());
|
||||||
|
else
|
||||||
|
utils.simple_exec(exe, "-e", ([[
|
||||||
|
local func = assert(load(assert(io.lines(%q, 4096)), %q, "t"));
|
||||||
|
local bytecode = string.dump(func, %s);
|
||||||
|
local out_f = assert(io.open(%q, "w"));
|
||||||
|
assert(out_f:write(bytecode));
|
||||||
|
assert(out_f:close());
|
||||||
|
]]):format(input, filename, strip and "true" or "false", output));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Compiles the given lua file to bytecode
|
||||||
|
--- @param settings { input: string, output: string, filename?: string, strip?: boolean, exe?: string }
|
||||||
|
--- @return task
|
||||||
|
function lua.compile(settings)
|
||||||
|
local input = settings.input;
|
||||||
|
local output = settings.output;
|
||||||
|
local filename = "@" .. (settings.filename or settings.input);
|
||||||
|
local strip = settings.strip or false;
|
||||||
|
local exe = settings.exe;
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
if utils.check_files({ input }, { output }) then return end
|
||||||
|
compile(input, output, filename, strip, exe);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Bundles multiple lua files into a single one
|
||||||
|
--- @param settings { inputs: string[] | (fun(): string?), relative_to?: string, output: string, entry: string }
|
||||||
|
--- @return task
|
||||||
|
function lua.bundle(settings)
|
||||||
|
local inputs = utils.to_array(settings.inputs);
|
||||||
|
local output = settings.output;
|
||||||
|
local entry = settings.entry;
|
||||||
|
local relative_to = settings.relative_to or "";
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
local out_f = assert(io.open(output, "w"));
|
||||||
|
|
||||||
|
for i = 1, #inputs do
|
||||||
|
local path = inputs[i]:sub(#relative_to);
|
||||||
|
local name = get_module_name(path);
|
||||||
|
|
||||||
|
local in_f = assert(io.open(inputs[i], "r"));
|
||||||
|
local data = assert(in_f:read "*a");
|
||||||
|
in_f:close();
|
||||||
|
|
||||||
|
assert(out_f:write(("package.preload[%q] = load(%q, \"bt\", %q);\n"):format(name, data, path)));
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(out_f:write(("require %q (...);\n"):format(entry)));
|
||||||
|
out_f:close();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Generates a single C file that exposes the given C libraries to lua
|
||||||
|
--- @param settings { inputs: string[] | (fun(): string?), relative_to?: string, output: string, func_name?: string }
|
||||||
|
--- @return task
|
||||||
|
function lua.c_libs(settings)
|
||||||
|
local inputs = utils.to_array(settings.inputs);
|
||||||
|
local output = settings.output;
|
||||||
|
local func_name = settings.func_name or "mklua_init_libs";
|
||||||
|
local relative_to = settings.relative_to or "";
|
||||||
|
|
||||||
|
return function ()
|
||||||
|
local out_f = assert(io.open(output, "w"));
|
||||||
|
|
||||||
|
assert(out_f:write("#include <lua.h>\n"));
|
||||||
|
assert(out_f:write("#include <lualib.h>\n"));
|
||||||
|
assert(out_f:write("#include <lauxlib.h>\n"));
|
||||||
|
|
||||||
|
assert(out_f:write("\n"));
|
||||||
|
|
||||||
|
for i = 1, #inputs do
|
||||||
|
local path = inputs[i]:sub(#relative_to);
|
||||||
|
local name = get_module_name(path);
|
||||||
|
|
||||||
|
local in_f = assert(io.open(inputs[i], "r"));
|
||||||
|
local data = assert(in_f:read "*a");
|
||||||
|
in_f:close();
|
||||||
|
|
||||||
|
assert(out_f:write(("package.preload[%q] = load(%q, \"bt\", %q);\n"):format(name, data, path)));
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(out_f:write(("require %q (...);\n"):format("")));
|
||||||
|
out_f:close();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return lua;
|
38
src/main/recurse.lua
Normal file
38
src/main/recurse.lua
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
--- @class recurse_state
|
||||||
|
--- @field stack ({ prefix: string, data: any, next: fun(): string? } | string)[]
|
||||||
|
--- @field readdir fun(dir: string): (fun(state: any): string?)
|
||||||
|
|
||||||
|
--- @param state recurse_state
|
||||||
|
--- @return string?
|
||||||
|
local function recurser(state)
|
||||||
|
if not state then
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local top = table.remove(state.stack);
|
||||||
|
if not top then
|
||||||
|
return nil;
|
||||||
|
elseif type(top) == "string" then
|
||||||
|
local res = state.readdir(top);
|
||||||
|
|
||||||
|
if res then
|
||||||
|
table.insert(state.stack, { next = res, prefix = top .. "/" });
|
||||||
|
end
|
||||||
|
|
||||||
|
return top;
|
||||||
|
elseif type(top) == "table" then
|
||||||
|
local res = top.next(top.data);
|
||||||
|
if res then
|
||||||
|
table.insert(state.stack, top.prefix .. res);
|
||||||
|
table.insert(state.stack, top);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error "invalid stack state";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return function (dir, readdir)
|
||||||
|
return recurser, { stack = { dir }, readdir = readdir or require "fs".readdir };
|
||||||
|
end
|
110
src/main/utils.lua
Normal file
110
src/main/utils.lua
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
local exec = require "exec";
|
||||||
|
local fs = require "fs";
|
||||||
|
|
||||||
|
local utils = {};
|
||||||
|
|
||||||
|
utils.exec = exec;
|
||||||
|
|
||||||
|
function utils.simple_exec(program, ...)
|
||||||
|
local child = assert(exec { program, ... });
|
||||||
|
|
||||||
|
child.stdin:close(io.stdin);
|
||||||
|
child.stdout:close(io.stdout);
|
||||||
|
child.stderr:close(io.stderr);
|
||||||
|
|
||||||
|
local code = assert(child.reap());
|
||||||
|
if code ~= 0 then
|
||||||
|
error(("'%s' failed with exit code %d"):format(program, code));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function utils.ensure_dir(path)
|
||||||
|
local dirname = path:match "^(.+)/[^/]-$";
|
||||||
|
if dirname then
|
||||||
|
assert(fs.mkdir(dirname, true));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @generic T
|
||||||
|
--- @param val T[] | (fun(): T?)
|
||||||
|
function utils.iterate(val)
|
||||||
|
if type(val) == "function" then
|
||||||
|
return val;
|
||||||
|
else
|
||||||
|
local i = 0;
|
||||||
|
return function ()
|
||||||
|
if not val then
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i + 1;
|
||||||
|
if i > #val then
|
||||||
|
--- @diagnostic disable-next-line: cast-local-type
|
||||||
|
val = nil;
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
return val[i];
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @generic T
|
||||||
|
--- @param val T[] | (fun(): T?)
|
||||||
|
--- @return T[]
|
||||||
|
function utils.to_array(val)
|
||||||
|
local res = {};
|
||||||
|
for el in utils.iterate(val) do
|
||||||
|
table.insert(res, el);
|
||||||
|
end
|
||||||
|
return res;
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Checks if all the output files are present, and if input files are
|
||||||
|
--- specified, whether or not they are older than all the output files.
|
||||||
|
---
|
||||||
|
--- Returns true if all checks hold
|
||||||
|
---
|
||||||
|
--- This function can be used to prevent redundant builds
|
||||||
|
---
|
||||||
|
--- @param outputs string[] | (fun(): string?)
|
||||||
|
--- @param inputs? string[] | (fun(): string?)
|
||||||
|
--- @return boolean
|
||||||
|
function utils.check_files(inputs, outputs)
|
||||||
|
local in_n = 0;
|
||||||
|
local out_n = 0;
|
||||||
|
local in_mtime, out_mtime;
|
||||||
|
local out_missing = false;
|
||||||
|
|
||||||
|
if inputs then
|
||||||
|
for el in utils.iterate(inputs) do
|
||||||
|
local stat = fs.lstat(el);
|
||||||
|
if not stat then error("Input '" .. el "' not present") end
|
||||||
|
|
||||||
|
in_n = in_n + 1;
|
||||||
|
if not in_mtime or in_mtime < stat.mtime then
|
||||||
|
in_mtime = stat.mtime;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for el in utils.iterate(outputs) do
|
||||||
|
out_n = out_n + 1;
|
||||||
|
|
||||||
|
local stat = fs.lstat(el);
|
||||||
|
if not stat then
|
||||||
|
out_missing = true;
|
||||||
|
elseif not out_mtime or out_mtime > stat.mtime then
|
||||||
|
out_mtime = stat.mtime;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if out_missing then
|
||||||
|
return false;
|
||||||
|
elseif in_mtime and out_mtime then
|
||||||
|
return in_mtime < out_mtime;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return utils;
|
Loading…
x
Reference in New Issue
Block a user