cosmopolitan/third_party/lua/lrepl.c
2024-08-04 12:52:25 -07:00

488 lines
15 KiB
C

/*-*- 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/lrepl.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/errno.h"
#include "libc/intrin/nomultics.h"
#include "libc/log/check.h"
#include "libc/macros.h"
#include "libc/mem/alg.h"
#include "libc/mem/gc.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/thread/thread.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/lua.h"
#include "third_party/lua/lualib.h"
__static_yoink("lua_notice");
static const char *const kKeywordHints[] = {
"else", //
"elseif", //
"function", //
"function", //
"repeat", //
"then", //
"until", //
"while", //
};
bool lua_repl_blocking;
bool lua_repl_isterminal;
linenoiseCompletionCallback *lua_repl_completions_callback;
struct linenoiseState *lua_repl_linenoise;
const char *lua_progname;
static lua_State *globalL;
static char *g_historypath;
/*
** {==================================================================
** Read-Eval-Print Loop (REPL)
** ===================================================================
*/
#if !defined(LUA_PROMPT)
#define LUA_PROMPT ">: "
#define LUA_PROMPT2 ">>: "
#endif
#if !defined(LUA_MAXINPUT)
#define LUA_MAXINPUT 512
#endif
static void lua_readline_addcompletion (linenoiseCompletions *c, char *s) {
char **p;
if ((p = realloc(c->cvec, (c->len + 1) * sizeof(*p)))) {
c->cvec = p;
c->cvec[c->len++] = s;
}
}
void lua_readline_completions (const char *p, linenoiseCompletions *c) {
int i;
size_t n;
bool found;
lua_State *L;
const char *a;
const char *name;
char *b, *s, *component;
// start searching globals
L = globalL;
lua_pushglobaltable(L);
// traverse parent objects
// split foo.bar and foo:bar
a = p;
b = strpbrk(a, ".:");
while (b) {
found = false;
if (lua_istable(L, -1)) {
component = strndup(a, b - a);
lua_pushnil(L); // search key
while (lua_next(L, -2)) {
if (lua_type(L, -2) == LUA_TSTRING) {
name = lua_tostring(L, -2);
if (!strcmp(name, component)) {
lua_remove(L, -3); // remove table
lua_remove(L, -2); // remove key
found = true;
break;
}
}
lua_pop(L, 1); // pop value
}
free(component);
}
if (!found) {
lua_pop(L, 1); // pop table
return;
}
a = b + 1;
b = strpbrk(a, ".:");
}
// search final object
if (lua_type(L, -1) == LUA_TTABLE) {
lua_pushnil(L);
while (lua_next(L, -2)) {
if (lua_type(L, -2) == LUA_TSTRING) {
name = lua_tolstring(L, -2, &n);
if (startswithi(name, a) && (s = malloc(a - p + n + 1))) {
memcpy(s, p, a - p);
memcpy(s + (a - p), name, n + 1);
lua_readline_addcompletion(c, s);
}
}
lua_pop(L, 1);
}
}
lua_pop(L, 1); // pop table
for (i = 0; i < ARRAYLEN(kKeywordHints); ++i) {
if (startswithi(kKeywordHints[i], p)) {
if ((s = strdup(kKeywordHints[i]))) {
lua_readline_addcompletion(c, s);
}
}
}
if (lua_repl_completions_callback) {
lua_repl_completions_callback(p, c);
}
}
char *lua_readline_hint (const char *p, const char **ansi1, const char **ansi2) {
char *h;
linenoiseCompletions c = {0};
lua_readline_completions(p, &c);
h = c.len == 1 ? strdup(c.cvec[0] + strlen(p)) : 0;
linenoiseFreeCompletions(&c);
return h;
}
static void lua_freeline (lua_State *L, char *b) {
free(b);
}
/*
** Return the string to be used as a prompt by the interpreter. Leave
** the string (or nil, if using the default value) on the stack, to keep
** it anchored.
*/
static const char *get_prompt (lua_State *L, int firstline) {
if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL)
return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */
else { /* apply 'tostring' over the value */
const char *p = luaL_tolstring(L, -1, NULL);
lua_remove(L, -2); /* remove original value */
return p;
}
}
/* mark in error messages for incomplete statements */
#define EOFMARK "<eof>"
#define marklen (sizeof(EOFMARK)/sizeof(char) - 1)
/*
** Check whether 'status' signals a syntax error and the error
** message at the top of the stack ends with the above mark for
** incomplete statements.
*/
static int incomplete (lua_State *L, int status) {
if (status == LUA_ERRSYNTAX) {
size_t lmsg;
const char *msg = lua_tolstring(L, -1, &lmsg);
if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0) {
lua_pop(L, 1);
return 1;
}
}
return 0; /* else... */
}
/*
** Prompt the user, read a line, and push it into the Lua stack.
*/
static ssize_t pushline (lua_State *L, int firstline) {
char *b;
size_t l;
ssize_t rc;
char *prmt;
globalL = L;
prmt = strdup(get_prompt(L, firstline));
lua_pop(L, 1); /* remove prompt */
if (lua_repl_isterminal) {
lua_repl_unlock();
rc = linenoiseEdit(lua_repl_linenoise, prmt, &b, !firstline || lua_repl_blocking);
free(prmt);
if (rc != -1) {
if (b && *b) {
linenoiseHistoryLoad(g_historypath);
linenoiseHistoryAdd(b);
linenoiseHistorySave(g_historypath);
}
}
lua_repl_lock();
} else {
lua_repl_unlock();
fputs(prmt, stdout);
free(prmt);
fflush(stdout);
b = linenoiseGetLine(stdin);
if (b) {
rc = 1;
} else if (ferror(stdin)) {
rc = -1;
} else {
rc = 0;
}
lua_repl_lock();
}
if (!(rc == -1 && errno == EAGAIN)) {
write(1, "\n", 1);
}
if (rc == -1 || (!rc && !b)) {
return rc;
}
l = strlen(b);
if (l > 0 && b[l-1] == '\n') /* line ends with newline? */
b[--l] = '\0'; /* remove it */
if (firstline && b[0] == '=') { /* for compatibility with 5.2, ... */
lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */
} else {
lua_pushlstring(L, b, l);
}
lua_freeline(L, b);
return 1;
}
/*
** Try to compile line on the stack as 'return <line>;'; on return, stack
** has either compiled chunk or original line (if compilation failed).
*/
static int addreturn (lua_State *L) {
const char *line = lua_tostring(L, -1); /* original line */
const char *retline = lua_pushfstring(L, "return %s;", line);
int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin");
if (status == LUA_OK) {
lua_remove(L, -2); /* remove modified line */
} else {
lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */
}
return status;
}
/*
** Hook set by signal function to stop the interpreter.
*/
static void lstop (lua_State *L, lua_Debug *ar) {
(void)ar; /* unused arg. */
lua_sethook(L, NULL, 0, 0); /* reset hook */
luaL_error(L, "interrupted!");
}
/*
** Read multiple lines until a complete Lua statement
*/
static int multiline (lua_State *L) {
for (;;) { /* repeat until gets a complete statement */
size_t len;
const char *line = lua_tolstring(L, 1, &len); /* get what it has */
int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */
if (!incomplete(L, status) || pushline(L, 0) != 1)
return status; /* cannot or should not try to add continuation line */
lua_pushliteral(L, "\n"); /* add newline... */
lua_insert(L, -2); /* ...between the two lines */
lua_concat(L, 3); /* join them */
}
}
void lua_initrepl(lua_State *L) {
const char *prompt;
lua_repl_lock();
if ((lua_repl_isterminal = linenoiseIsTerminal())) {
linenoiseSetCompletionCallback(lua_readline_completions);
linenoiseSetHintsCallback(lua_readline_hint);
linenoiseSetFreeHintsCallback(free);
prompt = get_prompt(L, 1);
if ((g_historypath = linenoiseGetHistoryPath(lua_progname))) {
if (linenoiseHistoryLoad(g_historypath) == -1) {
fprintf(stderr, "%r%s: failed to load history: %m\n", g_historypath);
free(g_historypath);
g_historypath = 0;
}
}
lua_repl_linenoise = linenoiseBegin(prompt, 0, 1);
lua_pop(L, 1); /* remove prompt */
__ttyconf.replmode = true;
if (isatty(2)) {
__ttyconf.replstderr = true;
}
}
lua_repl_unlock();
}
void lua_freerepl(void) {
lua_repl_lock();
__ttyconf.replmode = false;
linenoiseEnd(lua_repl_linenoise);
free(g_historypath);
lua_repl_unlock();
}
/*
** Read a line and try to load (compile) it first as an expression (by
** adding "return " in front of it) and second as a statement. Return
** the final status of load/call with the resulting function (if any)
** in the top of the stack.
**
** returns -1 on eof
** returns -2 on error
*/
int lua_loadline (lua_State *L) {
ssize_t rc;
int status;
lua_settop(L, 0);
lua_repl_lock();
if ((rc = pushline(L, 1)) != 1) {
lua_repl_unlock();
return rc - 1; /* eof or error */
}
if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */
status = multiline(L); /* try as command, maybe with continuation lines */
lua_remove(L, 1); /* remove line from the stack */
lua_assert(lua_gettop(L) == 1);
lua_repl_unlock();
return status;
}
void lua_sigint (lua_State *L, int sig) {
int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;
lua_sethook(L, lstop, flag, 1);
}
/*
** Function to be called at a C signal. Because a C signal cannot
** just change a Lua state (as there is no proper synchronization),
** this function only sets a hook that, when called, will stop the
** interpreter.
*/
static void laction (int i) {
lua_sigint(globalL, i);
int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;
lua_sethook(globalL, lstop, flag, 1);
}
/*
** Message handler used to run all chunks
*/
static int msghandler (lua_State *L) {
const char *msg = lua_tostring(L, 1);
if (msg == NULL) { /* is error object not a string? */
if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */
lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */
return 1; /* that is the message */
else
msg = lua_pushfstring(L, "(error object is a %s value)",
luaL_typename(L, 1));
}
luaL_traceback(L, L, msg, 1); /* append a standard traceback */
return 1; /* return the traceback */
}
/*
** Interface to 'lua_pcall', which sets appropriate message function
** and C-signal handler. Used to run all chunks.
*/
int lua_runchunk (lua_State *L, int narg, int nres) {
struct sigaction sa, saold;
int status;
int base = lua_gettop(L) - narg; /* function index */
lua_pushcfunction(L, msghandler); /* push message handler */
lua_insert(L, base); /* put it under function and args */
globalL = L; /* to be available to 'laction' */
sa.sa_flags = SA_RESETHAND; /* if another int happens, terminate */
sa.sa_handler = laction;
sigemptyset(&sa.sa_mask); /* do not mask any signal */
sigaction(SIGINT, &sa, &saold);
status = lua_pcall(L, narg, nres, base);
sigaction(SIGINT, &saold, 0); /* restore C-signal handler */
lua_remove(L, base); /* remove message handler from the stack */
return status;
}
/*
** Prints an error message, adding the program name in front of it
** (if present)
*/
void lua_l_message (const char *pname, const char *msg) {
if (pname) lua_writestringerror("%s: ", pname);
lua_writestringerror("%s\n", msg);
}
/*
** Prints (calling the Lua 'print' function) any values on the stack
*/
void lua_l_print (lua_State *L) {
int n = lua_gettop(L);
if (n > 0) { /* any result to be printed? */
luaL_checkstack(L, LUA_MINSTACK, "too many results to print");
lua_getglobal(L, "print");
lua_insert(L, 1);
if (lua_pcall(L, n, 0, 0) != LUA_OK)
lua_l_message(lua_progname, lua_pushfstring(L, "error calling 'print' (%s)",
lua_tostring(L, -1)));
}
}
/*
** Check whether 'status' is not OK and, if so, prints the error
** message on the top of the stack. It assumes that the error object
** is a string, as it was either generated by Lua or by 'msghandler'.
*/
int lua_report (lua_State *L, int status) {
if (status != LUA_OK) {
const char *msg = lua_tostring(L, -1);
lua_l_message(lua_progname, msg);
lua_pop(L, 1); /* remove message */
}
return status;
}