From 516b68606f404523d4d008ae30d5e62b89bcf97a Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 22 Jul 2022 10:10:33 -0700 Subject: [PATCH] Add pretty printing to redbean serializers --- test/tool/net/encodejson_test.lua | 41 ++++++++++++-- test/tool/net/encodelua_test.lua | 34 +++++++++++- third_party/libcxx/libcxx.mk | 2 + third_party/lua/cosmo.h | 33 +++++++++++- third_party/lua/lua.mk | 1 + third_party/lua/luaencodejsondata.c | 84 +++++++++++++++-------------- third_party/lua/luaencodeluadata.c | 79 +++++++++++++++------------ third_party/lua/luaformatstack.c | 8 ++- third_party/lua/serialize.c | 68 +++++++++++++++++++++++ tool/net/help.txt | 28 ++++++++++ tool/net/redbean.c | 40 +++++++++++--- 11 files changed, 327 insertions(+), 91 deletions(-) create mode 100644 third_party/lua/serialize.c diff --git a/test/tool/net/encodejson_test.lua b/test/tool/net/encodejson_test.lua index f6c18b2ef..eb9691bab 100644 --- a/test/tool/net/encodejson_test.lua +++ b/test/tool/net/encodejson_test.lua @@ -45,6 +45,36 @@ assert(EncodeJson("\"") == [["\""]]) assert(EncodeJson("\'") == [["\'"]]) assert(EncodeJson("\\") == [["\\"]]) +assert(EncodeJson( + {yo=2, + bye={yo=2, + dawg=3}, + there={yo=2}, + sup={yo=2}, + hi="hello"}, + {pretty=true}) == + "{\n".. + " \"bye\": {\n".. + " \"dawg\": 3,\n".. + " \"yo\": 2\n".. + " },\n".. + " \"hi\": \"hello\",\n".. + " \"sup\": {\"yo\": 2},\n".. + " \"there\": {\"yo\": 2},\n".. + " \"yo\": 2\n".. + "}") + +assert(EncodeJson( + {yo=2, bye=1, there=10, sup=3, hi="hello"}, + {pretty=true, indent=" "}) == + "{\n".. + " \"bye\": 1,\n".. + " \"hi\": \"hello\",\n".. + " \"sup\": 3,\n".. + " \"there\": 10,\n".. + " \"yo\": 2\n".. + "}") + val, err = EncodeJson({[3]=3, [2]=3}) assert(val == nil) assert(err == 'json objects must only use string keys') @@ -69,7 +99,12 @@ x.a = 'a' x.b = 'b' assert(EncodeJson(x) == '{"a":"a","b":"b","c":"c"}') -assert(EncodeJson({{{{{{{{{{{},{{{{},{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) == +assert(EncodeJson(0, {maxdepth=1})) +val, err = EncodeJson(0, {maxdepth=0}) +assert(val == nil) +assert(err == 'table has great depth') + +assert(EncodeJson({{{{{{{{{{{},{{{{},{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, {maxdepth=64}) == '[[[[[[[[[[{},[[[{},[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]') val, err = EncodeJson({{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) assert(val == nil) @@ -82,7 +117,7 @@ assert(EncodeJson( {k={k={k={k={k={k={k={k={k={k={k={k={k={k={k= {k={k={k={k={k={k={k={k={k={k={k={k={k={k={k= {k={k={k={k=0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} -}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) == +}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, {maxdepth=64}) == "{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":".. "{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":".. "{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":".. @@ -97,7 +132,7 @@ res, err = EncodeJson( {k={k={k={k={k={k={k={k={k={k={k={k={k={k={k= {k={k={k={k={k={k={k={k={k={k={k={k={k={k={k= {k={k={k={k=0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} -}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) +}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, {maxdepth=64}) assert(res == nil) assert(err == "table has great depth") diff --git a/test/tool/net/encodelua_test.lua b/test/tool/net/encodelua_test.lua index 661e13ca9..32789b3ca 100644 --- a/test/tool/net/encodelua_test.lua +++ b/test/tool/net/encodelua_test.lua @@ -53,6 +53,36 @@ assert(EncodeLua("\e") == [["\e"]]) assert(EncodeLua("\"") == [["\""]]) assert(EncodeLua("\\") == [["\\"]]) +assert(EncodeLua( + {yo=2, + bye={yo=2, + dawg=3}, + there={yo=2}, + sup={yo=2}, + hi="hello"}, + {pretty=true}) == + "{\n".. + " bye={\n".. + " dawg=3,\n".. + " yo=2\n".. + " },\n".. + " hi=\"hello\",\n".. + " sup={yo=2},\n".. + " there={yo=2},\n".. + " yo=2\n".. + "}") + +assert(EncodeLua( + {yo=2, bye=1, there=10, sup=3, hi="hello"}, + {pretty=true, indent=" "}) == + "{\n".. + " bye=1,\n".. + " hi=\"hello\",\n".. + " sup=3,\n".. + " there=10,\n".. + " yo=2\n".. + "}") + x = {} x.c = 'c' x.a = 'a' @@ -70,7 +100,7 @@ assert(EncodeLua( {k={k={k={k={k={k={k={k={k={k={k={k={k={k={k= {k={k={k={k={k={k={k={k={k={k={k={k={k={k={k= {k={k={k={k=0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} -}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) == +}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, {maxdepth=64}) == "{k={k={k={k={k={k={k={k={k={k={k={k={k={k={k={k=".. "{k={k={k={k={k={k={k={k={k={k={k={k={k={k={k={k=".. "{k={k={k={k={k={k={k={k={k={k={k={k={k={k={k={k=".. @@ -85,7 +115,7 @@ assert(EncodeLua( {k={k={k={k={k={k={k={k={k={k={k={k={k={k={k= {k={k={k={k={k={k={k={k={k={k={k={k={k={k={k= {k={k={k={k=0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} -}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} +}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, {maxdepth=64} ) == "{k={k={k={k={k={k={k={k={k={k={k={k={k={k={k={k=".. "{k={k={k={k={k={k={k={k={k={k={k={k={k={k={k={k=".. diff --git a/third_party/libcxx/libcxx.mk b/third_party/libcxx/libcxx.mk index 34d5cef45..7c34a8a18 100644 --- a/third_party/libcxx/libcxx.mk +++ b/third_party/libcxx/libcxx.mk @@ -154,6 +154,8 @@ $(THIRD_PARTY_LIBCXX_A_OBJS): \ -ffunction-sections \ -fdata-sections +o/$(MODE)/third_party/libcxx/locale.o: QUOTA = -C32 -M1024m + THIRD_PARTY_LIBCXX_LIBS = $(foreach x,$(THIRD_PARTY_LIBCXX_ARTIFACTS),$($(x))) THIRD_PARTY_LIBCXX_SRCS = $(foreach x,$(THIRD_PARTY_LIBCXX_ARTIFACTS),$($(x)_SRCS)) THIRD_PARTY_LIBCXX_HDRS = $(foreach x,$(THIRD_PARTY_LIBCXX_ARTIFACTS),$($(x)_HDRS)) diff --git a/third_party/lua/cosmo.h b/third_party/lua/cosmo.h index 210141875..aaedd1348 100644 --- a/third_party/lua/cosmo.h +++ b/third_party/lua/cosmo.h @@ -3,13 +3,39 @@ #include "net/http/http.h" #include "net/http/url.h" #include "third_party/lua/lauxlib.h" +#include "third_party/lua/visitor.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +struct EncoderConfig { + short maxdepth; + bool sorted; + bool pretty; + const char *indent; +}; + +struct Serializer { + struct LuaVisited visited; + struct EncoderConfig conf; + const char *reason; + char *strbuf; + size_t strbuflen; +}; + +struct SerializerJoin { + struct Serializer *z; + char **buf; + bool multi; + int depth; + int i; + const char *indent; +}; + +bool LuaHasMultipleItems(lua_State *); char *LuaFormatStack(lua_State *) dontdiscard; int LuaCallWithTrace(lua_State *, int, int, lua_State *); -int LuaEncodeJsonData(lua_State *, char **, int, bool); -int LuaEncodeLuaData(lua_State *, char **, int, bool); +int LuaEncodeJsonData(lua_State *, char **, int, struct EncoderConfig); +int LuaEncodeLuaData(lua_State *, char **, int, struct EncoderConfig); int LuaEncodeUrl(lua_State *); int LuaParseUrl(lua_State *); int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int); @@ -17,6 +43,9 @@ int LuaPushHeaders(lua_State *, struct HttpMessage *, const char *); void LuaPrintStack(lua_State *); void LuaPushLatin1(lua_State *, const char *, size_t); void LuaPushUrlParams(lua_State *, struct UrlParams *); +int SerializeObjectStart(char **, struct Serializer *, int, bool); +int SerializeObjectEnd(char **, struct Serializer *, int, bool); +int SerializeObjectIndent(char **, struct Serializer *, int); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/third_party/lua/lua.mk b/third_party/lua/lua.mk index 88b88aef2..cf5d9652e 100644 --- a/third_party/lua/lua.mk +++ b/third_party/lua/lua.mk @@ -109,6 +109,7 @@ THIRD_PARTY_LUA_A_SRCS = \ third_party/lua/lutf8lib.c \ third_party/lua/lvm.c \ third_party/lua/lzio.c \ + third_party/lua/serialize.c \ third_party/lua/visitor.c THIRD_PARTY_LUA_A_OBJS = \ diff --git a/third_party/lua/luaencodejsondata.c b/third_party/lua/luaencodejsondata.c index 3589ac8d0..7fe96af4d 100644 --- a/third_party/lua/luaencodejsondata.c +++ b/third_party/lua/luaencodejsondata.c @@ -21,7 +21,6 @@ #include "libc/bits/bits.h" #include "libc/bits/likely.h" #include "libc/fmt/itoa.h" -#include "libc/intrin/kprintf.h" #include "libc/log/log.h" #include "libc/log/rop.h" #include "libc/mem/mem.h" @@ -36,19 +35,6 @@ #include "third_party/lua/lua.h" #include "third_party/lua/visitor.h" -struct Serializer { - struct LuaVisited visited; - const char *reason; - char *strbuf; - size_t strbuflen; - bool sorted; -}; - -struct Joiner { - char **buf; - int i; -}; - static int Serialize(lua_State *, char **, int, struct Serializer *, int); static int SerializeNull(lua_State *L, char **buf) { @@ -96,13 +82,13 @@ OnError: } static int SerializeArray(lua_State *L, char **buf, struct Serializer *z, - int level, size_t tbllen) { + int depth, size_t tbllen) { size_t i; RETURN_ON_ERROR(appendw(buf, '[')); for (i = 1; i <= tbllen; i++) { lua_rawgeti(L, -1, i); // +2 if (i > 1) RETURN_ON_ERROR(appendw(buf, ',')); - RETURN_ON_ERROR(Serialize(L, buf, -1, z, level - 1)); + RETURN_ON_ERROR(Serialize(L, buf, -1, z, depth + 1)); lua_pop(L, 1); } RETURN_ON_ERROR(appendw(buf, ']')); @@ -112,38 +98,44 @@ OnError: } static int SerializeObject(lua_State *L, char **buf, struct Serializer *z, - int level) { + int depth, bool multi) { bool comma = false; - RETURN_ON_ERROR(appendw(buf, '{')); + RETURN_ON_ERROR(SerializeObjectStart(buf, z, depth, multi)); lua_pushnil(L); // +2 while (lua_next(L, -2)) { // +3 if (lua_type(L, -2) == LUA_TSTRING) { if (comma) { RETURN_ON_ERROR(appendw(buf, ',')); + if (multi) { + RETURN_ON_ERROR(SerializeObjectIndent(buf, z, depth + 1)); + } } else { comma = true; } RETURN_ON_ERROR(SerializeString(L, buf, -2, z)); - RETURN_ON_ERROR(appendw(buf, ':')); - RETURN_ON_ERROR(Serialize(L, buf, -1, z, level - 1)); + RETURN_ON_ERROR(appendw(buf, z->conf.pretty ? READ16LE(": ") : ':')); + RETURN_ON_ERROR(Serialize(L, buf, -1, z, depth + 1)); lua_pop(L, 1); } else { z->reason = "json objects must only use string keys"; goto OnError; } } - RETURN_ON_ERROR(appendw(buf, '}')); + RETURN_ON_ERROR(SerializeObjectEnd(buf, z, depth, multi)); return 0; OnError: return -1; } static intptr_t Join(const char *elem, void *arg) { - struct Joiner *j = arg; + struct SerializerJoin *j = arg; if (!j->i) { ++j->i; } else { RETURN_ON_ERROR(appendw(j->buf, ',')); + if (j->multi) { + RETURN_ON_ERROR(SerializeObjectIndent(j->buf, j->z, j->depth + 1)); + } } RETURN_ON_ERROR(appends(j->buf, elem)); return 0; @@ -152,18 +144,17 @@ OnError: } static int SerializeSorted(lua_State *L, char **buf, struct Serializer *z, - int level) { + int depth, bool multi) { int i; char *b = 0; - struct Joiner j = {buf}; struct critbit0 t = {0}; lua_pushnil(L); while (lua_next(L, -2)) { if (lua_type(L, -2) == LUA_TSTRING) { RETURN_ON_ERROR(appendr(&b, 0)); RETURN_ON_ERROR(SerializeString(L, &b, -2, z)); - RETURN_ON_ERROR(appendw(&b, ':')); - RETURN_ON_ERROR(Serialize(L, &b, -1, z, level - 1)); + RETURN_ON_ERROR(appendw(&b, z->conf.pretty ? READ16LE(": ") : ':')); + RETURN_ON_ERROR(Serialize(L, &b, -1, z, depth + 1)); RETURN_ON_ERROR(critbit0_insert(&t, b)); lua_pop(L, 1); } else { @@ -171,9 +162,15 @@ static int SerializeSorted(lua_State *L, char **buf, struct Serializer *z, goto OnError; } } - RETURN_ON_ERROR(appendw(buf, '{')); + struct SerializerJoin j = { + .z = z, + .buf = buf, + .multi = multi, + .depth = depth, + }; + RETURN_ON_ERROR(SerializeObjectStart(buf, z, depth, multi)); RETURN_ON_ERROR(critbit0_allprefixed(&t, "", Join, &j)); - RETURN_ON_ERROR(appendw(buf, '}')); + RETURN_ON_ERROR(SerializeObjectEnd(buf, z, depth, multi)); critbit0_clear(&t); free(b); return 0; @@ -184,8 +181,9 @@ OnError: } static int SerializeTable(lua_State *L, char **buf, int idx, - struct Serializer *z, int level) { + struct Serializer *z, int depth) { int rc; + bool multi; bool isarray; lua_Unsigned n; if (UNLIKELY(!HaveStackMemory(PAGESIZE))) { @@ -206,11 +204,14 @@ static int SerializeTable(lua_State *L, char **buf, int idx, lua_pop(L, 1); } if (isarray) { - RETURN_ON_ERROR(SerializeArray(L, buf, z, level, n)); - } else if (z->sorted) { - RETURN_ON_ERROR(SerializeSorted(L, buf, z, level)); + RETURN_ON_ERROR(SerializeArray(L, buf, z, depth, n)); } else { - RETURN_ON_ERROR(SerializeObject(L, buf, z, level)); + multi = z->conf.pretty && LuaHasMultipleItems(L); + if (z->conf.sorted) { + RETURN_ON_ERROR(SerializeSorted(L, buf, z, depth, multi)); + } else { + RETURN_ON_ERROR(SerializeObject(L, buf, z, depth, multi)); + } } LuaPopVisit(&z->visited); lua_pop(L, 1); // table ref @@ -224,8 +225,8 @@ OnError: } static int Serialize(lua_State *L, char **buf, int idx, struct Serializer *z, - int level) { - if (level > 0) { + int depth) { + if (depth < z->conf.maxdepth) { switch (lua_type(L, idx)) { case LUA_TNIL: return SerializeNull(L, buf); @@ -236,7 +237,7 @@ static int Serialize(lua_State *L, char **buf, int idx, struct Serializer *z, case LUA_TNUMBER: return SerializeNumber(L, buf, idx); case LUA_TTABLE: - return SerializeTable(L, buf, idx, z, level); + return SerializeTable(L, buf, idx, z, depth); default: z->reason = "unsupported lua type"; return -1; @@ -275,11 +276,12 @@ static int Serialize(lua_State *L, char **buf, int idx, struct Serializer *z, * @param idx is index of item on Lua stack * @return 0 on success, or -1 on error */ -int LuaEncodeJsonData(lua_State *L, char **buf, int idx, bool sorted) { - int rc, depth = 64; - struct Serializer z = {.reason = "out of memory", .sorted = sorted}; - if (lua_checkstack(L, depth * 3 + LUA_MINSTACK)) { - rc = Serialize(L, buf, idx, &z, depth); +int LuaEncodeJsonData(lua_State *L, char **buf, int idx, + struct EncoderConfig conf) { + int rc; + struct Serializer z = {.reason = "out of memory", .conf = conf}; + if (lua_checkstack(L, conf.maxdepth * 3 + LUA_MINSTACK)) { + rc = Serialize(L, buf, idx, &z, 0); free(z.visited.p); free(z.strbuf); if (rc == -1) { diff --git a/third_party/lua/luaencodeluadata.c b/third_party/lua/luaencodeluadata.c index c672b6d93..ec1aa6f68 100644 --- a/third_party/lua/luaencodeluadata.c +++ b/third_party/lua/luaencodeluadata.c @@ -33,17 +33,6 @@ #include "third_party/lua/lua.h" #include "third_party/lua/visitor.h" -struct Serializer { - struct LuaVisited visited; - const char *reason; - bool sorted; -}; - -struct Joiner { - char **buf; - int i; -}; - static int Serialize(lua_State *, char **, int, struct Serializer *, int); static bool IsLuaIdentifier(lua_State *L, int idx) { @@ -260,7 +249,7 @@ static int SerializeArray(lua_State *L, char **buf, struct Serializer *z, for (i = 1; i <= n; i++) { lua_rawgeti(L, -1, i); if (i > 1) RETURN_ON_ERROR(appendw(buf, READ16LE(", "))); - RETURN_ON_ERROR(Serialize(L, buf, -1, z, depth - 1)); + RETURN_ON_ERROR(Serialize(L, buf, -1, z, depth + 1)); lua_pop(L, 1); } RETURN_ON_ERROR(appendw(buf, '}')); @@ -270,16 +259,21 @@ OnError: } static int SerializeObject(lua_State *L, char **buf, struct Serializer *z, - int depth) { + int depth, bool multi) { int rc; size_t n; const char *s; bool comma = false; - RETURN_ON_ERROR(appendw(buf, '{')); + RETURN_ON_ERROR(SerializeObjectStart(buf, z, depth, multi)); lua_pushnil(L); while (lua_next(L, -2)) { if (comma) { - RETURN_ON_ERROR(appendw(buf, READ16LE(", "))); + if (multi) { + RETURN_ON_ERROR(appendw(buf, ',')); + RETURN_ON_ERROR(SerializeObjectIndent(buf, z, depth + 1)); + } else { + RETURN_ON_ERROR(appendw(buf, READ16LE(", "))); + } } else { comma = true; } @@ -291,24 +285,29 @@ static int SerializeObject(lua_State *L, char **buf, struct Serializer *z, } else { // use {[𝑘′]=𝑣′} otherwise RETURN_ON_ERROR(appendw(buf, '[')); - RETURN_ON_ERROR(Serialize(L, buf, -2, z, depth - 1)); + RETURN_ON_ERROR(Serialize(L, buf, -2, z, depth + 1)); RETURN_ON_ERROR(appendw(buf, READ16LE("]="))); } - RETURN_ON_ERROR(Serialize(L, buf, -1, z, depth - 1)); + RETURN_ON_ERROR(Serialize(L, buf, -1, z, depth + 1)); lua_pop(L, 1); } - RETURN_ON_ERROR(appendw(buf, '}')); + RETURN_ON_ERROR(SerializeObjectEnd(buf, z, depth, multi)); return 0; OnError: return -1; } static intptr_t Join(const char *elem, void *arg) { - struct Joiner *j = arg; + struct SerializerJoin *j = arg; if (!j->i) { ++j->i; } else { - RETURN_ON_ERROR(appendw(j->buf, READ16LE(", "))); + if (j->multi) { + RETURN_ON_ERROR(appendw(j->buf, ',')); + RETURN_ON_ERROR(SerializeObjectIndent(j->buf, j->z, j->depth + 1)); + } else { + RETURN_ON_ERROR(appendw(j->buf, READ16LE(", "))); + } } RETURN_ON_ERROR(appends(j->buf, elem)); return 0; @@ -317,12 +316,11 @@ OnError: } static int SerializeSorted(lua_State *L, char **buf, struct Serializer *z, - int depth) { + int depth, bool multi) { size_t n; int i, rc; char *b = 0; const char *s; - struct Joiner j = {buf}; struct critbit0 t = {0}; lua_pushnil(L); while (lua_next(L, -2)) { @@ -335,16 +333,22 @@ static int SerializeSorted(lua_State *L, char **buf, struct Serializer *z, } else { // use {[𝑘′]=𝑣′} otherwise RETURN_ON_ERROR(appendw(&b, '[')); - RETURN_ON_ERROR(Serialize(L, &b, -2, z, depth - 1)); + RETURN_ON_ERROR(Serialize(L, &b, -2, z, depth + 1)); RETURN_ON_ERROR(appendw(&b, ']' | '=' << 010)); } - RETURN_ON_ERROR(Serialize(L, &b, -1, z, depth - 1)); + RETURN_ON_ERROR(Serialize(L, &b, -1, z, depth + 1)); RETURN_ON_ERROR(critbit0_insert(&t, b)); lua_pop(L, 1); } - RETURN_ON_ERROR(appendw(buf, '{')); + struct SerializerJoin j = { + .z = z, + .buf = buf, + .multi = multi, + .depth = depth, + }; + RETURN_ON_ERROR(SerializeObjectStart(buf, z, depth, multi)); RETURN_ON_ERROR(critbit0_allprefixed(&t, "", Join, &j)); - RETURN_ON_ERROR(appendw(buf, '}')); + RETURN_ON_ERROR(SerializeObjectEnd(buf, z, depth, multi)); critbit0_clear(&t); free(b); return 0; @@ -357,6 +361,7 @@ OnError: static int SerializeTable(lua_State *L, char **buf, int idx, struct Serializer *z, int depth) { int rc; + bool multi; intptr_t rsp, bot; if (UNLIKELY(!HaveStackMemory(PAGESIZE))) { z->reason = "out of stack"; @@ -367,10 +372,13 @@ static int SerializeTable(lua_State *L, char **buf, int idx, lua_pushvalue(L, idx); // idx becomes invalid once we change stack if (IsLuaArray(L)) { RETURN_ON_ERROR(SerializeArray(L, buf, z, depth)); - } else if (z->sorted) { - RETURN_ON_ERROR(SerializeSorted(L, buf, z, depth)); } else { - RETURN_ON_ERROR(SerializeObject(L, buf, z, depth)); + multi = z->conf.pretty && LuaHasMultipleItems(L); + if (z->conf.sorted) { + RETURN_ON_ERROR(SerializeSorted(L, buf, z, depth, multi)); + } else { + RETURN_ON_ERROR(SerializeObject(L, buf, z, depth, multi)); + } } LuaPopVisit(&z->visited); lua_pop(L, 1); // table ref @@ -381,7 +389,7 @@ OnError: static int Serialize(lua_State *L, char **buf, int idx, struct Serializer *z, int depth) { - if (depth > 0) { + if (depth < z->conf.maxdepth) { switch (lua_type(L, idx)) { case LUA_TNIL: return SerializeNil(L, buf); @@ -425,11 +433,12 @@ static int Serialize(lua_State *L, char **buf, int idx, struct Serializer *z, * @param sorted is ignored (always sorted) * @return 0 on success, or -1 on error */ -int LuaEncodeLuaData(lua_State *L, char **buf, int idx, bool sorted) { - int rc, depth = 64; - struct Serializer z = {.reason = "out of memory", .sorted = sorted}; - if (lua_checkstack(L, depth * 3 + LUA_MINSTACK)) { - rc = Serialize(L, buf, idx, &z, depth); +int LuaEncodeLuaData(lua_State *L, char **buf, int idx, + struct EncoderConfig conf) { + int rc; + struct Serializer z = {.reason = "out of memory", .conf = conf}; + if (lua_checkstack(L, conf.maxdepth * 3 + LUA_MINSTACK)) { + rc = Serialize(L, buf, idx, &z, 0); free(z.visited.p); if (rc == -1) { lua_pushnil(L); diff --git a/third_party/lua/luaformatstack.c b/third_party/lua/luaformatstack.c index 4456a25c7..1c274de6f 100644 --- a/third_party/lua/luaformatstack.c +++ b/third_party/lua/luaformatstack.c @@ -24,11 +24,17 @@ dontdiscard char *LuaFormatStack(lua_State *L) { size_t l; int i, top; char *p, *b = 0; + struct EncoderConfig conf = { + .maxdepth = 64, + .sorted = true, + .pretty = false, + .indent = " ", + }; top = lua_gettop(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, i, true); + LuaEncodeLuaData(L, &b, i, conf); } return b; } diff --git a/third_party/lua/serialize.c b/third_party/lua/serialize.c new file mode 100644 index 000000000..dfffe5ccc --- /dev/null +++ b/third_party/lua/serialize.c @@ -0,0 +1,68 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/log/rop.h" +#include "libc/stdio/append.internal.h" +#include "third_party/lua/cosmo.h" +#include "third_party/lua/lua.h" + +bool LuaHasMultipleItems(lua_State *L) { + int i; + lua_pushnil(L); + for (i = 0; lua_next(L, -2); ++i) { + if (i > 0) { + lua_pop(L, 2); + return true; + } + lua_pop(L, 1); + } + return false; +} + +int SerializeObjectIndent(char **buf, struct Serializer *z, int depth) { + int i; + RETURN_ON_ERROR(appendw(buf, '\n')); + for (i = 0; i < depth; ++i) { + RETURN_ON_ERROR(appends(buf, z->conf.indent)); + } + return 0; +OnError: + return -1; +} + +int SerializeObjectStart(char **buf, struct Serializer *z, int depth, + bool multi) { + RETURN_ON_ERROR(appendw(buf, '{')); + if (multi) { + RETURN_ON_ERROR(SerializeObjectIndent(buf, z, depth + 1)); + } + return 0; +OnError: + return -1; +} + +int SerializeObjectEnd(char **buf, struct Serializer *z, int depth, + bool multi) { + if (multi) { + RETURN_ON_ERROR(SerializeObjectIndent(buf, z, depth)); + } + RETURN_ON_ERROR(appendw(buf, '}')); + return 0; +OnError: + return -1; +} diff --git a/tool/net/help.txt b/tool/net/help.txt index e8c9b2a74..f5b4a8fa8 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -787,6 +787,20 @@ FUNCTIONS don't care about ordering then setting `sorted=false` should yield a performance boost in serialization. + - pretty: (bool=false) Setting this option to true will + cause tables with more than one entry to be formatted + across multiple lines for readability. + + - indent: (str=" ") This option controls the indentation of + pretty formatting. This field is ignored if `pretty` isn't + true. + + - maxdepth: (int=64) This option controls the maximum amount + of recursion the serializer is allowed to perform. The max + is 32767. You might not be able to set it that high if + there isn't enough C stack memory. Your serializer checks + for this and will return an error rather than crashing. + This function will return an error if: - `value` is cyclic @@ -844,6 +858,20 @@ FUNCTIONS don't care about ordering then setting `sorted=false` should yield a performance boost in serialization. + - pretty: (bool=false) Setting this option to true will + cause tables with more than one entry to be formatted + across multiple lines for readability. + + - indent: (str=" ") This option controls the indentation of + pretty formatting. This field is ignored if `pretty` isn't + true. + + - maxdepth: (int=64) This option controls the maximum amount + of recursion the serializer is allowed to perform. The max + is 32767. You might not be able to set it that high if + there isn't enough C stack memory. Your serializer checks + for this and will return an error rather than crashing. + If a user data object has a `__repr` or `__tostring` meta method, then that'll be used to encode the Lua code. diff --git a/tool/net/redbean.c b/tool/net/redbean.c index d84cbe776..c1d987b8f 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -4239,12 +4239,16 @@ static int LuaLog(lua_State *L) { return 0; } -static int LuaEncodeSmth(lua_State *L, - int Encoder(lua_State *, char **, int, bool)) { +static int LuaEncodeSmth(lua_State *L, int Encoder(lua_State *, char **, int, + struct EncoderConfig)) { char *p = 0; - int maxdepth = 64; - int sorted = true; int useoutput = false; + struct EncoderConfig conf = { + .maxdepth = 64, + .sorted = true, + .pretty = false, + .indent = " ", + }; if (lua_istable(L, 2)) { lua_settop(L, 2); // discard any extra arguments lua_getfield(L, 2, "useoutput"); @@ -4252,11 +4256,27 @@ static int LuaEncodeSmth(lua_State *L, if (ishandlingrequest && lua_isboolean(L, -1)) { useoutput = lua_toboolean(L, -1); } + lua_getfield(L, 2, "maxdepth"); + if (!lua_isnoneornil(L, -1)) { + lua_Integer n = lua_tointeger(L, -1); + n = MAX(0, MIN(n, SHRT_MAX)); + conf.maxdepth = n; + } lua_getfield(L, 2, "sorted"); - sorted = lua_toboolean(L, -1); + if (!lua_isnoneornil(L, -1)) { + conf.sorted = lua_toboolean(L, -1); + } + lua_getfield(L, 2, "pretty"); + if (!lua_isnoneornil(L, -1)) { + conf.pretty = lua_toboolean(L, -1); + lua_getfield(L, 2, "indent"); + if (!lua_isnoneornil(L, -1)) { + conf.indent = luaL_checkstring(L, -1); + } + } } lua_settop(L, 1); // keep the passed argument on top - if (Encoder(L, useoutput ? &outbuf : &p, -1, sorted) == -1) { + if (Encoder(L, useoutput ? &outbuf : &p, -1, conf) == -1) { free(p); return 2; } @@ -5373,7 +5393,13 @@ static void LuaPrint(lua_State *L) { if (n > 0) { for (i = 1; i <= n; i++) { if (i > 1) appendw(&b, '\t'); - LuaEncodeLuaData(L, &b, i, true); + struct EncoderConfig conf = { + .maxdepth = 64, + .sorted = true, + .pretty = true, + .indent = " ", + }; + LuaEncodeLuaData(L, &b, i, conf); } appendw(&b, '\n'); WRITE(1, b, appendz(b).i);