/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╚──────────────────────────────────────────────────────────────────────────────╝ │ │ │ Lua │ │ Copyright © 2004-2023 Lua.org, PUC-Rio. │ │ │ │ Permission is hereby granted, free of charge, to any person obtaining │ │ a copy of this software and associated documentation files (the │ │ "Software"), to deal in the Software without restriction, including │ │ without limitation the rights to use, copy, modify, merge, publish, │ │ distribute, sublicense, and/or sell copies of the Software, and to │ │ permit persons to whom the Software is furnished to do so, subject to │ │ the following conditions: │ │ │ │ The above copyright notice and this permission notice shall be │ │ included in all copies or substantial portions of the Software. │ │ │ │ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ │ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ │ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. │ │ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY │ │ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, │ │ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE │ │ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │ │ │ ╚─────────────────────────────────────────────────────────────────────────────*/ #define lua_c #include "third_party/lua/lua.h" #include "libc/calls/calls.h" #include "libc/calls/struct/sigaction.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/log/log.h" #include "libc/mem/gc.h" #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" #include "libc/sock/sock.h" #include "libc/sock/struct/pollfd.h" #include "libc/str/str.h" #include "libc/sysv/consts/exit.h" #include "libc/sysv/consts/poll.h" #include "libc/sysv/consts/sa.h" #include "libc/x/x.h" #include "third_party/linenoise/linenoise.h" #include "third_party/lua/cosmo.h" #include "third_party/lua/lauxlib.h" #include "third_party/lua/lprefix.h" #include "third_party/lua/lrepl.h" #include "third_party/lua/lualib.h" #include "third_party/lua/lunix.h" #include "libc/cosmo.h" #include "libc/mem/leaks.h" __static_yoink("lua_notice"); #if !defined(LUA_PROGNAME) #define LUA_PROGNAME "lua" #endif #if !defined(LUA_INIT_VAR) #define LUA_INIT_VAR "LUA_INIT" #endif #define LUA_INITVARVERSION LUA_INIT_VAR LUA_VERSUFFIX static bool lua_stdin_is_tty(void) { return isatty(0); } static void print_usage (const char *badoption) { lua_writestringerror("%s: ", lua_progname); if (badoption[1] == 'e' || badoption[1] == 'l') lua_writestringerror("'%s' needs argument\n", badoption); else lua_writestringerror("unrecognized option '%s'\n", badoption); lua_writestringerror( "usage: %s [options] [script [args]]\n" "Available options are:\n" " -e stat execute string 'stat'\n" " -i enter interactive mode after executing 'script'\n" " -l mod require library 'mod' into global 'mod'\n" " -l g=mod require library 'mod' into global 'g'\n" " -v show version information\n" " -E ignore environment variables\n" " -W turn warnings on\n" " -- stop handling options\n" " - stop handling options and execute stdin\n" , lua_progname); } static void print_version (void) { lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT)); lua_writeline(); } /* ** Create the 'arg' table, which stores all arguments from the ** command line ('argv'). It should be aligned so that, at index 0, ** it has 'argv[script]', which is the script name. The arguments ** to the script (everything after 'script') go to positive indices; ** other arguments (before the script name) go to negative indices. ** If there is no script name, assume interpreter's name as base. ** (If there is no interpreter's name either, 'script' is -1, so ** table sizes are zero.) */ static void createargtable (lua_State *L, char **argv, int argc, int script) { int i, narg; narg = argc - (script + 1); /* number of positive indices */ lua_createtable(L, narg, script + 1); for (i = 0; i < argc; i++) { lua_pushstring(L, argv[i]); lua_rawseti(L, -2, i - script); } lua_setglobal(L, "arg"); } static int dochunk (lua_State *L, int status) { if (status == LUA_OK) status = lua_runchunk(L, 0, 0); return lua_report(L, status); } static int dofile (lua_State *L, const char *name) { return dochunk(L, luaL_loadfile(L, name)); } static int dostring (lua_State *L, const char *s, const char *name) { return dochunk(L, luaL_loadbuffer(L, s, strlen(s), name)); } /* ** Receives 'globname[=modname]' and runs 'globname = require(modname)'. */ static int dolibrary (lua_State *L, char *globname) { int status; char *modname = strchr(globname, '='); if (modname == NULL) /* no explicit name? */ modname = globname; /* module name is equal to global name */ else { *modname = '\0'; /* global name ends here */ modname++; /* module name starts after the '=' */ } lua_getglobal(L, "require"); lua_pushstring(L, modname); status = lua_runchunk(L, 1, 1); /* call 'require(modname)' */ if (status == LUA_OK) lua_setglobal(L, globname); /* globname = require(modname) */ return lua_report(L, status); } /* ** Push on the stack the contents of table 'arg' from 1 to #arg */ static int pushargs (lua_State *L) { int i, n; if (lua_getglobal(L, "arg") != LUA_TTABLE) luaL_error(L, "'arg' is not a table"); n = (int)luaL_len(L, -1); luaL_checkstack(L, n + 3, "too many arguments to script"); for (i = 1; i <= n; i++) lua_rawgeti(L, -i, i); lua_remove(L, -i); /* remove table from the stack */ return n; } static int handle_script (lua_State *L, char **argv) { int status; const char *fname = argv[0]; if (strcmp(fname, "-") == 0 && strcmp(argv[-1], "--") != 0) fname = NULL; /* stdin */ status = luaL_loadfile(L, fname); if (status == LUA_OK) { int n = pushargs(L); /* push arguments to script */ status = lua_runchunk(L, n, LUA_MULTRET); } return lua_report(L, status); } /* bits of various argument indicators in 'args' */ #define has_error 1 /* bad option */ #define has_i 2 /* -i */ #define has_v 4 /* -v */ #define has_e 8 /* -e */ #define has_E 16 /* -E */ /* ** Traverses all arguments from 'argv', returning a mask with those ** needed before running any Lua code or an error code if it finds any ** invalid argument. In case of error, 'first' is the index of the bad ** argument. Otherwise, 'first' is -1 if there is no program name, ** 0 if there is no script name, or the index of the script name. */ static int collectargs (char **argv, int *first) { int args = 0; int i; if (argv[0] != NULL) { /* is there a program name? */ if (argv[0][0]) /* not empty? */ lua_progname = argv[0]; /* save it */ } else { /* no program name */ *first = -1; return 0; } for (i = 1; argv[i] != NULL; i++) { /* handle arguments */ *first = i; if (argv[i][0] != '-') /* not an option? */ return args; /* stop handling options */ switch (argv[i][1]) { /* else check option */ case '-': /* '--' */ if (argv[i][2] != '\0') /* extra characters after '--'? */ return has_error; /* invalid option */ *first = i + 1; return args; case '\0': /* '-' */ return args; /* script "name" is '-' */ case 'E': if (argv[i][2] != '\0') /* extra characters? */ return has_error; /* invalid option */ args |= has_E; break; case 'W': if (argv[i][2] != '\0') /* extra characters? */ return has_error; /* invalid option */ break; case 'i': args |= has_i; /* (-i implies -v) *//* FALLTHROUGH */ case 'v': if (argv[i][2] != '\0') /* extra characters? */ return has_error; /* invalid option */ args |= has_v; break; case 'e': args |= has_e; /* FALLTHROUGH */ case 'l': /* both options need an argument */ if (argv[i][2] == '\0') { /* no concatenated argument? */ i++; /* try next 'argv' */ if (argv[i] == NULL || argv[i][0] == '-') return has_error; /* no next argument or it is another option */ } break; default: /* invalid option */ return has_error; } } *first = 0; /* no script name */ return args; } /* ** Processes options 'e' and 'l', which involve running Lua code, and ** 'W', which also affects the state. ** Returns 0 if some code raises an error. */ static int runargs (lua_State *L, char **argv, int n) { int i; for (i = 1; i < n; i++) { int option = argv[i][1]; lua_assert(argv[i][0] == '-'); /* already checked */ switch (option) { case 'e': case 'l': { int status; char *extra = argv[i] + 2; /* both options need an argument */ if (*extra == '\0') extra = argv[++i]; lua_assert(extra != NULL); status = (option == 'e') ? dostring(L, extra, "=(command line)") : dolibrary(L, extra); if (status != LUA_OK) return 0; break; } case 'W': lua_warning(L, "@on", 0); /* warnings on */ break; } } return 1; } static int handle_luainit (lua_State *L) { const char *name = "=" LUA_INITVARVERSION; const char *init = getenv(name + 1); if (init == NULL) { name = "=" LUA_INIT_VAR; init = getenv(name + 1); /* try alternative name */ } if (init == NULL) return LUA_OK; else if (init[0] == '@') return dofile(L, init+1); else return dostring(L, init, name); } /* ** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and ** print any results. */ static void doREPL (lua_State *L) { int status; const char *oldprogname = lua_progname; lua_progname = NULL; /* no 'progname' on errors in interactive mode */ lua_initrepl(L); for (;;) { if (lua_repl_isterminal) linenoiseEnableRawMode(0); TryAgain: status = lua_loadline(L); if (status == -2 && errno == EAGAIN) { errno = 0; poll(&(struct pollfd){0, POLLIN}, 1, -1); goto TryAgain; } if (lua_repl_isterminal) linenoiseDisableRawMode(); if (status == -1) { break; } else if (status == -2) { lua_pushfstring(L, "read error: %s", strerror(errno)); lua_report(L, status); lua_freerepl(); lua_progname = oldprogname; return; } if (status == LUA_OK) status = lua_runchunk(L, 0, LUA_MULTRET); if (status == LUA_OK) { lua_l_print(L); } else { lua_report(L, status); } } lua_freerepl(); lua_settop(L, 0); /* clear stack */ lua_progname = oldprogname; } /* }================================================================== */ /* ** Main body of stand-alone interpreter (to be called in protected mode). ** Reads the options and handles them all. */ static int pmain (lua_State *L) { int argc = (int)lua_tointeger(L, 1); char **argv = (char **)lua_touserdata(L, 2); int script; int args = collectargs(argv, &script); int optlim = (script > 0) ? script : argc; /* first argv not an option */ luaL_checkversion(L); /* check that interpreter has correct version */ if (args == has_error) { /* bad arg? */ print_usage(argv[script]); /* 'script' has index of bad arg. */ return 0; } if (args & has_v) /* option '-v'? */ print_version(); if (args & has_E) { /* option '-E'? */ lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */ lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV"); } luaL_openlibs(L); /* open standard libraries */ luaL_requiref(L, "unix", LuaUnix, 1); lua_pop(L, 1); createargtable(L, argv, argc, script); /* create table 'arg' */ lua_gc(L, LUA_GCRESTART); /* start GC... */ lua_gc(L, LUA_GCGEN, 0, 0); /* ...in generational mode */ if (!(args & has_E)) { /* no option '-E'? */ if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */ return 0; /* error running LUA_INIT */ } if (!runargs(L, argv, optlim)) /* execute arguments -e and -l */ return 0; /* something failed */ if (script > 0) { /* execute main script (if there is one) */ if (handle_script(L, argv + script) != LUA_OK) return 0; /* interrupt in case of error */ } if (args & has_i) /* -i option? */ doREPL(L); /* do read-eval-print loop */ else if (script < 1 && !(args & (has_e | has_v))) { /* no active option? */ if (lua_stdin_is_tty()) { /* running in interactive mode? */ print_version(); doREPL(L); /* do read-eval-print loop */ } else dofile(L, NULL); /* executes stdin as a file */ } lua_pushboolean(L, 1); /* signal no errors */ return 1; } int main (int argc, char **argv) { lua_State *L; int status, result; LoadZipArgs(&argc, &argv); if (IsModeDbg()) { ShowCrashReports(); } L = luaL_newstate(); /* create state */ if (L == NULL) { lua_l_message(argv[0], "cannot create state: not enough memory"); return EXIT_FAILURE; } lua_gc(L, LUA_GCSTOP); /* stop GC while building state */ lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */ lua_pushinteger(L, argc); /* 1st argument */ lua_pushlightuserdata(L, argv); /* 2nd argument */ status = lua_pcall(L, 2, 1, 0); /* do the call */ result = lua_toboolean(L, -1); /* get result */ lua_report(L, status); lua_close(L); if (IsModeDbg()) { CheckForMemoryLeaks(); } return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE; }