diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a05697e..36cee10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - + + - name: Initialize submodules + run: git submodule update --init --recursive + - name: Set up dependencies run: | sudo apt-get update diff --git a/.gitignore b/.gitignore index a861fad..f5e685f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ Makefile *.make +*.log bin obj .cache +.vscode/ +.DS_Store +Thumbs.dbjenkins_home/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..03c2dcd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/compat53"] + path = lib/compat53 + url = https://github.com/lunarmodules/lua-compat-5.3.git diff --git a/install.sh b/install.sh index ebdbaeb..405cf6e 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,8 @@ #!/bin/sh +echo "Initializing and updating submodules..." +git submodule update --init --recursive + # Function to install packages using apt (Debian/Ubuntu) install_with_apt() { sudo apt-get update diff --git a/lib/.gitkeep b/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/compat53 b/lib/compat53 new file mode 160000 index 0000000..dfd83b4 --- /dev/null +++ b/lib/compat53 @@ -0,0 +1 @@ +Subproject commit dfd83b4930c8b85fd39d208523f7293cf469b205 diff --git a/premake5.lua b/premake5.lua index 037cfca..1043c07 100644 --- a/premake5.lua +++ b/premake5.lua @@ -13,12 +13,17 @@ local lua_lib_path = "/usr/lib" if os.findlib("lua5.4") then lua_inc_path = "/usr/include/lua5.4" lua_lib_path = "/usr/lib/5.4" - links({ "lua5.4" }) +-- Readline for better interactive support, dl for dynamic loading, and m for the math library dependency + links({ "lua5.4", "readline", "dl", "m" }) else links({ "lua" }) end -includedirs({ lua_inc_path, "lib/hashmap" }) +includedirs({ + lua_inc_path, + "lib/hashmap", + "lib/compat53/c-api" +}) libdirs({ lua_lib_path }) files({ @@ -26,8 +31,15 @@ files({ "src/**.c", "lib/hashmap/**.h", "lib/hashmap/**.c", + "lib/compat53/c-api/compat-5.3.h", + "lib/compat53/c-api/compat-5.3.c", + "lib/compat53/lbitlib.c", + "lib/compat53/liolib.c", + "lib/compat53/lstrlib.c", + "lib/compat53/ltablib.c", + "lib/compat53/lutf8lib.c" }) -defines({ 'LUSH_VERSION="0.3.2"' }) +defines({ 'LUSH_VERSION="0.3.2"', 'COMPAT53_PREFIX=""', 'LUA_COMPAT_BITLIB' }) filter("configurations:Debug") defines({ "DEBUG" }) diff --git a/src/lush.c b/src/lush.c index 1c42139..b512a70 100644 --- a/src/lush.c +++ b/src/lush.c @@ -21,6 +21,7 @@ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. #include "lua.h" #include "lua_api.h" #include "lualib.h" +#include "compat-5.3.h" #include #include #include @@ -42,6 +43,10 @@ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. #include #include +// Forward declare the open functions for the compat C modules we need to preload +int luaopen_bit32 (lua_State *L); +int luaopen_utf8 (lua_State *L); + #define BUFFER_SIZE 1024 #define MAX_GLOB 512 @@ -1485,13 +1490,26 @@ int main(int argc, char *argv[]) { return 0; } + // init lua state + lua_State *L = luaL_newstate(); + if (!L) { + fprintf(stderr, "Failed to create Lua state\n"); + return -1; // or handle appropriately + } + luaL_openlibs(L); + + // --- Load compat modules and make them global --- + luaL_requiref(L, "bit32", luaopen_bit32, 1); + lua_pop(L, 1); // luaL_requiref leaves the module on the stack + + luaL_requiref(L, "utf8", luaopen_utf8, 1); + lua_pop(L, 1); + // --- End pre-loading --- + lua_register_api(L); + lua_run_init(L); + // check if being run in command string mode if (argc > 2 && strcmp(argv[1], "-c") == 0) { - // init Lua state - lua_State *L = luaL_newstate(); - luaL_openlibs(L); - lua_register_api(L); - lua_run_init(L); // execute the command provided char *command = argv[2]; @@ -1521,36 +1539,26 @@ int main(int argc, char *argv[]) { return 0; } - // init lua state - lua_State *L = luaL_newstate(); - luaL_openlibs(L); - lua_register_api(L); - lua_run_init(L); + // This is the corrected logic for running a script file non-interactively. + if (argc > 1) { + char *ext = strrchr(argv[1], '.'); + if (ext && strcmp(ext, ".lua") == 0) { + const char *script_name = argv[1]; + // The arguments for the script start at argv[2]. + // We create a pointer to that part of the array. + char **script_args = (argc > 2) ? &argv[2] : NULL; - // if a lua function is passed on load run non-interactively - if (argc > 1) { - char *ext = strrchr(argv[1], '.'); - if (ext) { - ext++; - if (strcmp(ext, "lua") == 0) { - int status = 0; - argv++; - char ***args = lush_split_args(argv, &status); - - if (status == -1) { - fprintf(stderr, "lush: Expected end of quoted string\n"); - } else if (lush_run(L, args, status) != 0) { - exit(1); - } + // Call the script loader directly with the script and its arguments. + if (lua_load_script(L, script_name, script_args) != 0) { + exit(1); // Exit if the script had an error + } - for (int i = 0; args[i]; i++) { - free(args[i]); - } - free(args); - return 0; - } - } - } + lua_close(L); // Clean up and exit + return 0; + } + } + + // --- Interactive Shell Mode --- // eat ^C in main struct sigaction sa_int; @@ -1617,4 +1625,4 @@ int main(int argc, char *argv[]) { if (alt_shell != NULL) free(alt_shell); return 0; -} +} \ No newline at end of file diff --git a/test/compat_test.lua b/test/compat_test.lua new file mode 100644 index 0000000..bd7ef90 --- /dev/null +++ b/test/compat_test.lua @@ -0,0 +1,60 @@ +-- test/compat_test.lua +-- A small test suite for the lua-compat-5.3 layer. +-- Can be run in two ways: +-- 1. ./lush test/compat_test.lua (runs all tests) +-- 2. ./lush test/compat_test.lua test_table_unpack (runs a single test) + +local tests = {} + +function tests.test_table_unpack() + print("--- Running test: table.unpack ---") + local my_table = { "a", "b", "c" } + -- The compat layer provides table.unpack for Lua 5.1. + local x, y, z = table.unpack(my_table) + assert(x == "a", "unpack failed for first element") + assert(y == "b", "unpack failed for second element") + assert(z == "c", "unpack failed for third element") + print("...SUCCESS!") +end + +function tests.test_math_log_base() + print("--- Running test: math.log with base ---") + -- Lua 5.1's math.log only takes one argument. The compat layer adds the base. + local result = math.log(100, 10) + -- Use a small epsilon for floating point comparison + assert(math.abs(result - 2) < 1e-9, "math.log(100, 10) should be 2") + print("...SUCCESS!") +end + +function tests.test_string_rep_separator() + print("--- Running test: string.rep with separator ---") + -- Lua 5.1's string.rep doesn't have the separator argument. + local result = string.rep("a", 3, "-") + assert(result == "a-a-a", "string.rep with separator failed, got: " .. tostring(result)) + print("...SUCCESS!") +end + + +-- NOTE: The lush C host provides arguments in a global table named 'args', not 'arg'. +local test_to_run = args and args[1] + +if test_to_run and tests[test_to_run] then + -- If a valid test name is provided, run only that test. + local success, err = pcall(tests[test_to_run]) + if not success then + print("...FAILURE: " .. err) + end +elseif test_to_run then + -- If an invalid name is provided, show an error. + print("ERROR: Test function '" .. test_to_run .. "' not found.") +else + -- If no specific test is requested, run all of them. + print("--- Running all compatibility tests ---") + for name, func in pairs(tests) do + local success, err = pcall(func) + if not success then + print("...FAILURE on test '" .. name .. "': " .. err) + end + end + print("--- All tests complete ---") +end