Make improvements

- Add hierarchical auto-completion to redbean's repl
- Fetch latest localtime() and strftime() from Eggert
- Shave a few milliseconds off redbean start latency
- Fix redbean repl with multi-line statements
- Make the Lua unix module code more elegant
- Harden Lua data structure serialization
This commit is contained in:
Justine Tunney 2022-04-27 05:39:39 -07:00
parent d57b81aac7
commit 6a145a9262
44 changed files with 2987 additions and 1941 deletions

View file

@ -8,8 +8,8 @@ COSMOPOLITAN_C_START_
char *LuaFormatStack(lua_State *) dontdiscard;
int LuaCallWithTrace(lua_State *, int, int, lua_State *);
int LuaEncodeJsonData(lua_State *, char **, int, char *, int);
int LuaEncodeLuaData(lua_State *, char **, int, char *, int);
int LuaEncodeJsonData(lua_State *, char **, char *, int);
int LuaEncodeLuaData(lua_State *, char **, char *, int);
int LuaEncodeUrl(lua_State *);
int LuaParseUrl(lua_State *);
int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int);

View file

@ -27,6 +27,7 @@
*/
#define ldo_c
#define LUA_CORE
#include "libc/log/log.h"
#include "libc/runtime/gc.h"
#include "third_party/lua/lapi.h"
#include "third_party/lua/ldebug.h"
@ -148,7 +149,7 @@ l_noret luaD_throw (lua_State *L, int errcode) {
lua_unlock(L);
g->panic(L); /* call panic function (last chance to jump out) */
}
abort();
__die();
}
}
}

View file

@ -29,15 +29,18 @@
#include "libc/alg/alg.h"
#include "libc/calls/calls.h"
#include "libc/calls/sigbits.h"
#include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/nomultics.internal.h"
#include "libc/log/check.h"
#include "libc/macros.internal.h"
#include "libc/runtime/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.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"
@ -87,37 +90,74 @@ static const char *g_historypath;
#endif
static void lua_readline_addcompletion (linenoiseCompletions *c, const char *s) {
char **p, *t;
static void lua_readline_addcompletion (linenoiseCompletions *c, char *s) {
char **p;
if ((p = realloc(c->cvec, (c->len + 1) * sizeof(*p)))) {
c->cvec = p;
if ((t = strdup(s))) {
c->cvec[c->len++] = t;
}
c->cvec[c->len++] = s;
}
}
void lua_readline_completions (const char *p, linenoiseCompletions *c) {
int i;
bool found;
lua_State *L;
const char *name;
for (i = 0; i < ARRAYLEN(kKeywordHints); ++i) {
if (startswithi(kKeywordHints[i], p)) {
lua_readline_addcompletion(c, kKeywordHints[i]);
}
}
char *a, *b, *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) {
component = strndup(a, b - a);
found = false;
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
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
name = lua_tostring(L, -2);
if (startswithi(name, p)) {
lua_readline_addcompletion(c, name);
while (lua_next(L, -2)) {
if (lua_type(L, -2) == LUA_TSTRING) {
name = lua_tostring(L, -2);
if (startswithi(name, a)) {
lua_readline_addcompletion(c, xasprintf("%.*s%s", a - p, p, name));
}
}
lua_pop(L, 1);
}
lua_pop(L, 1);
for (i = 0; i < ARRAYLEN(kKeywordHints); ++i) {
if (startswithi(kKeywordHints[i], p)) {
lua_readline_addcompletion(c, xstrdup(kKeywordHints[i]));
}
}
if (lua_repl_completions_callback) {
lua_repl_completions_callback(p, c);
}
@ -191,7 +231,7 @@ static ssize_t pushline (lua_State *L, int firstline) {
prmt = strdup(get_prompt(L, firstline));
lua_pop(L, 1); /* remove prompt */
LUA_REPL_UNLOCK;
rc = linenoiseEdit(lua_repl_linenoise, 0, &b, !firstline || lua_repl_blocking);
rc = linenoiseEdit(lua_repl_linenoise, prmt, &b, !firstline || lua_repl_blocking);
free(prmt);
if (rc != -1) {
if (b && *b) {
@ -207,6 +247,9 @@ static ssize_t pushline (lua_State *L, int firstline) {
LUA_REPL_LOCK;
rc = b ? 1 : -1;
}
if (!(rc == -1 && errno == EAGAIN)) {
write(1, "\n", 1);
}
if (rc == -1 || (!rc && !b)) {
return rc;
}

View file

@ -1724,7 +1724,7 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) {
lua_error(L1);
}
else if EQ("abort") {
abort();
__die();
}
else if EQ("throw") {
#if defined(__cplusplus)

View file

@ -324,7 +324,6 @@ static void doREPL (lua_State *L) {
progname = oldprogname;
return;
}
lua_writeline();
if (status == LUA_OK)
status = lua_runchunk(L, 0, LUA_MULTRET);
if (status == LUA_OK) {
@ -335,7 +334,6 @@ static void doREPL (lua_State *L) {
}
lua_freerepl();
lua_settop(L, 0); /* clear stack */
lua_writeline();
progname = oldprogname;
}

View file

@ -77,7 +77,7 @@ o/$(MODE)/third_party/lua/lua.com: \
@$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@
@$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \
-o o/$(MODE)/third_party/lua/.lua/.symtab $<
@$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \
@$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \
o/$(MODE)/third_party/lua/.lua/.symtab
o//third_party/lua/lgc.o: \

View file

@ -25,8 +25,8 @@
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lua.h"
int LuaEncodeJsonData(lua_State *L, char **buf, int level, char *numformat,
int idx) {
static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
char *numformat, int idx) {
char *s;
bool isarray;
size_t tbllen, z;
@ -93,7 +93,7 @@ int LuaEncodeJsonData(lua_State *L, char **buf, int level, char *numformat,
for (size_t i = 1; i <= tbllen; i++) {
if (i > 1) appendw(buf, ',');
lua_rawgeti(L, -1, i); // table/-2, value/-1
LuaEncodeJsonData(L, buf, level - 1, numformat, -1);
LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -1);
lua_pop(L, 1);
}
} else {
@ -121,7 +121,7 @@ int LuaEncodeJsonData(lua_State *L, char **buf, int level, char *numformat,
lua_remove(L, -1); // remove copied key: tab/-3, key/-2, val/-1
}
appendw(buf, '"' | ':' << 010);
LuaEncodeJsonData(L, buf, level - 1, numformat, -1);
LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -1);
lua_pop(L, 1); // table/-2, key/-1
}
// stack: table/-1, as the key was popped by lua_next
@ -137,3 +137,9 @@ int LuaEncodeJsonData(lua_State *L, char **buf, int level, char *numformat,
unreachable;
}
}
int LuaEncodeJsonData(lua_State *L, char **buf, char *numformat, int idx) {
int rc;
rc = LuaEncodeJsonDataImpl(L, buf, 64, numformat, idx);
return rc;
}

View file

@ -16,42 +16,98 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/fmt/itoa.h"
#include "libc/math.h"
#include "libc/mem/mem.h"
#include "libc/stdio/append.internal.h"
#include "libc/x/x.h"
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.h"
#include "third_party/lua/lua.h"
int LuaEncodeLuaData(lua_State *L, char **buf, int level, char *numformat,
int idx) {
struct Visited {
int n;
void **p;
};
static bool PushVisit(struct Visited *visited, void *p) {
int i;
for (i = 0; i < visited->n; ++i) {
if (visited->p[i] == p) {
return false;
}
}
visited->p = xrealloc(visited->p, ++visited->n * sizeof(*visited->p));
visited->p[visited->n - 1] = p;
return true;
}
static void PopVisit(struct Visited *visited) {
assert(visited->n > 0);
--visited->n;
}
static int LuaEncodeLuaDataImpl(lua_State *L, char **buf, int level,
char *numformat, int idx,
struct Visited *visited) {
char *s;
int ktype;
bool didcomma;
lua_Integer i;
int ktype, vtype;
size_t tbllen, buflen, slen;
char ibuf[21], fmt[] = "%.14g";
if (level > 0) {
switch (lua_type(L, idx)) {
case LUA_TNIL:
appendw(buf, READ32LE("nil"));
return 0;
case LUA_TSTRING:
s = lua_tolstring(L, idx, &slen);
EscapeLuaString(s, slen, buf);
return 0;
case LUA_TFUNCTION:
appendf(buf, "\"func@%p\"", lua_touserdata(L, idx));
return 0;
case LUA_TUSERDATA:
appendf(buf, "\"udata@%p\"", lua_touserdata(L, idx));
return 0;
case LUA_TLIGHTUSERDATA:
appendf(buf, "\"light@%p\"", lua_touserdata(L, idx));
return 0;
case LUA_TTHREAD:
appendf(buf, "\"thread@%p\"", lua_touserdata(L, idx));
return 0;
case LUA_TUSERDATA:
if (luaL_callmeta(L, idx, "__repr")) {
if (lua_type(L, -1) == LUA_TSTRING) {
s = lua_tolstring(L, -1, &slen);
appendd(buf, s, slen);
} else {
appendf(buf, "[[error %s returned a %s value]]", "__repr",
luaL_typename(L, -1));
}
lua_pop(L, 1);
return 0;
}
if (luaL_callmeta(L, idx, "__tostring")) {
if (lua_type(L, -1) == LUA_TSTRING) {
s = lua_tolstring(L, -1, &slen);
EscapeLuaString(s, slen, buf);
} else {
appendf(buf, "[[error %s returned a %s value]]", "__tostring",
luaL_typename(L, -1));
}
lua_pop(L, 1);
return 0;
}
appendf(buf, "\"udata@%p\"", lua_touserdata(L, idx));
return 0;
case LUA_TNUMBER:
if (lua_isinteger(L, idx)) {
appendd(buf, ibuf,
@ -69,11 +125,13 @@ int LuaEncodeLuaData(lua_State *L, char **buf, int level, char *numformat,
fmt[4] = *numformat;
break;
default:
return luaL_error(L, "numformat string not allowed");
luaL_error(L, "numformat string not allowed");
unreachable;
}
appendf(buf, fmt, lua_tonumber(L, idx));
}
return 0;
case LUA_TBOOLEAN:
if (lua_toboolean(L, idx)) {
appendw(buf, READ32LE("true"));
@ -81,26 +139,47 @@ int LuaEncodeLuaData(lua_State *L, char **buf, int level, char *numformat,
appendw(buf, READ64LE("false\0\0"));
}
return 0;
case LUA_TTABLE:
i = 0;
didcomma = false;
appendw(buf, '{');
lua_pushvalue(L, idx);
lua_pushnil(L); // push the first key
while (lua_next(L, -2) != 0) {
while (lua_next(L, -2)) {
++i;
ktype = lua_type(L, -2);
if (i++ > 0) appendw(buf, ',');
vtype = lua_type(L, -1);
if (ktype != LUA_TNUMBER || lua_tointeger(L, -2) != i) {
appendw(buf, '[');
lua_pushvalue(L, -2); // table/-4, key/-3, value/-2, key/-1
LuaEncodeLuaData(L, buf, level - 1, numformat, -1);
lua_remove(L, -1); // remove copied key: table/-3, key/-2, value/-1
appendw(buf, ']' | '=' << 010);
if (PushVisit(visited, lua_touserdata(L, -2))) {
if (i > 1) appendw(buf, ',' | ' ' << 8);
didcomma = true;
appendw(buf, '[');
LuaEncodeLuaDataImpl(L, buf, level - 1, numformat, -2, visited);
appendw(buf, ']' | '=' << 010);
PopVisit(visited);
} else {
// TODO: Strange Lua data structure, do nothing.
lua_pop(L, 1);
continue;
}
}
if (PushVisit(visited, lua_touserdata(L, -1))) {
if (!didcomma && i > 1) appendw(buf, ',' | ' ' << 8);
LuaEncodeLuaDataImpl(L, buf, level - 1, numformat, -1, visited);
PopVisit(visited);
} else {
// TODO: Strange Lua data structure, do nothing.
lua_pop(L, 1);
continue;
}
LuaEncodeLuaData(L, buf, level - 1, numformat, -1);
lua_pop(L, 1); // table/-2, key/-1
}
lua_pop(L, 1); // table
// stack: table/-1, as the key was popped by lua_next
appendw(buf, '}');
return 0;
default:
luaL_error(L, "can't serialize value of this type");
unreachable;
@ -110,3 +189,12 @@ int LuaEncodeLuaData(lua_State *L, char **buf, int level, char *numformat,
unreachable;
}
}
int LuaEncodeLuaData(lua_State *L, char **buf, char *numformat, int idx) {
int rc;
struct Visited visited = {0};
rc = LuaEncodeLuaDataImpl(L, buf, 64, numformat, idx, &visited);
assert(!visited.n);
free(visited.p);
return rc;
}

View file

@ -28,7 +28,7 @@ dontdiscard char *LuaFormatStack(lua_State *L) {
for (i = 1; i <= top; i++) {
if (i > 1) appendw(&b, '\n');
appendf(&b, "\t%d\t%s\t", i, luaL_typename(L, i));
LuaEncodeLuaData(L, &b, 64, "g", -1);
LuaEncodeLuaData(L, &b, "g", i);
}
return b;
}