mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-03 11:12:27 +00:00
Improve Lua and JSON serialization
This commit is contained in:
parent
3027d67037
commit
e3cd476a9b
20 changed files with 1041 additions and 476 deletions
5
third_party/lua/cosmo.h
vendored
5
third_party/lua/cosmo.h
vendored
|
@ -8,13 +8,12 @@ COSMOPOLITAN_C_START_
|
|||
|
||||
char *LuaFormatStack(lua_State *) dontdiscard;
|
||||
int LuaCallWithTrace(lua_State *, int, int, lua_State *);
|
||||
int LuaEncodeJsonData(lua_State *, char **, char *, int);
|
||||
int LuaEncodeLuaData(lua_State *, char **, char *, int);
|
||||
int LuaEncodeJsonData(lua_State *, char **, int, bool);
|
||||
int LuaEncodeLuaData(lua_State *, char **, int, bool);
|
||||
int LuaEncodeUrl(lua_State *);
|
||||
int LuaParseUrl(lua_State *);
|
||||
int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int);
|
||||
int LuaPushHeaders(lua_State *, struct HttpMessage *, const char *);
|
||||
int EscapeLuaString(char *, size_t, char **);
|
||||
void LuaPrintStack(lua_State *);
|
||||
void LuaPushLatin1(lua_State *, const char *, size_t);
|
||||
void LuaPushUrlParams(lua_State *, struct UrlParams *);
|
||||
|
|
48
third_party/lua/escapeluastring.c
vendored
48
third_party/lua/escapeluastring.c
vendored
|
@ -1,48 +0,0 @@
|
|||
/*-*- 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/bits/bits.h"
|
||||
#include "libc/log/rop.h"
|
||||
#include "libc/stdio/append.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "third_party/lua/cosmo.h"
|
||||
#include "third_party/lua/lua.h"
|
||||
|
||||
int EscapeLuaString(char *s, size_t len, char **buf) {
|
||||
int rc;
|
||||
size_t i;
|
||||
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||
for (i = 0; i < len; i++) {
|
||||
if (s[i] == '\n') {
|
||||
RETURN_ON_ERROR(appendw(buf, '\\' | 'n' << 8));
|
||||
} else if (s[i] == '\\' || s[i] == '\'' || s[i] == '\"') {
|
||||
RETURN_ON_ERROR(appendw(buf, '\\' | s[i] << 8));
|
||||
} else if (' ' <= s[i] && s[i] <= 0x7e) {
|
||||
RETURN_ON_ERROR(appendw(buf, s[i]));
|
||||
} else {
|
||||
RETURN_ON_ERROR(
|
||||
appendw(buf, '\\' | 'x' << 010 |
|
||||
"0123456789abcdef"[(s[i] & 0xF0) >> 4] << 020 |
|
||||
"0123456789abcdef"[(s[i] & 0x0F) >> 0] << 030));
|
||||
}
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
1
third_party/lua/lua.mk
vendored
1
third_party/lua/lua.mk
vendored
|
@ -64,7 +64,6 @@ THIRD_PARTY_LUA_A_HDRS = \
|
|||
third_party/lua/visitor.h
|
||||
|
||||
THIRD_PARTY_LUA_A_SRCS = \
|
||||
third_party/lua/escapeluastring.c \
|
||||
third_party/lua/lapi.c \
|
||||
third_party/lua/lauxlib.c \
|
||||
third_party/lua/lbaselib.c \
|
||||
|
|
328
third_party/lua/luaencodejsondata.c
vendored
328
third_party/lua/luaencodejsondata.c
vendored
|
@ -34,139 +34,233 @@
|
|||
#include "third_party/lua/lua.h"
|
||||
#include "third_party/lua/visitor.h"
|
||||
|
||||
static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
|
||||
char *numformat, int idx,
|
||||
struct LuaVisited *visited,
|
||||
const char **reason) {
|
||||
char *s;
|
||||
int sli, rc;
|
||||
bool isarray;
|
||||
struct Serializer {
|
||||
struct LuaVisited visited;
|
||||
const char *reason;
|
||||
char *strbuf;
|
||||
size_t strbuflen;
|
||||
bool sorted;
|
||||
};
|
||||
|
||||
static int Serialize(lua_State *, char **, int, struct Serializer *, int);
|
||||
|
||||
static int SerializeNull(lua_State *L, char **buf) {
|
||||
RETURN_ON_ERROR(appendw(buf, READ32LE("null")));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeBoolean(lua_State *L, char **buf, int idx) {
|
||||
RETURN_ON_ERROR(appendw(
|
||||
buf, lua_toboolean(L, idx) ? READ32LE("true") : READ64LE("false\0\0")));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeNumber(lua_State *L, char **buf, int idx) {
|
||||
char ibuf[128];
|
||||
size_t tbllen, i, z;
|
||||
struct StrList sl = {0};
|
||||
if (level > 0) {
|
||||
switch (lua_type(L, idx)) {
|
||||
|
||||
case LUA_TNIL:
|
||||
RETURN_ON_ERROR(appendw(buf, READ32LE("null")));
|
||||
return 0;
|
||||
|
||||
case LUA_TBOOLEAN:
|
||||
RETURN_ON_ERROR(appendw(buf, lua_toboolean(L, idx)
|
||||
? READ32LE("true")
|
||||
: READ64LE("false\0\0")));
|
||||
return 0;
|
||||
|
||||
case LUA_TSTRING:
|
||||
s = lua_tolstring(L, idx, &z);
|
||||
if (!(s = EscapeJsStringLiteral(s, z, &z))) {
|
||||
goto OnError;
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||
RETURN_ON_ERROR(appendd(buf, s, z));
|
||||
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||
free(s);
|
||||
return 0;
|
||||
|
||||
case LUA_TNUMBER:
|
||||
if (lua_isinteger(L, idx)) {
|
||||
RETURN_ON_ERROR(appendd(
|
||||
buf, ibuf, FormatInt64(ibuf, luaL_checkinteger(L, idx)) - ibuf));
|
||||
} else {
|
||||
RETURN_ON_ERROR(
|
||||
appends(buf, DoubleToJson(ibuf, lua_tonumber(L, idx))));
|
||||
}
|
||||
return 0;
|
||||
|
||||
case LUA_TTABLE:
|
||||
RETURN_ON_ERROR(rc = LuaPushVisit(visited, lua_topointer(L, idx)));
|
||||
if (!rc) {
|
||||
// create nearby reference to table at idx
|
||||
lua_pushvalue(L, idx);
|
||||
|
||||
// fast way to tell if table is an array or object
|
||||
if ((tbllen = lua_rawlen(L, -1)) > 0) {
|
||||
isarray = true;
|
||||
} else {
|
||||
// the json parser inserts `[0]=false` in empty arrays
|
||||
// so we can tell them apart from empty objects, which
|
||||
// is needed in order to have `[]` roundtrip the parse
|
||||
isarray = (lua_rawgeti(L, -1, 0) == LUA_TBOOLEAN &&
|
||||
!lua_toboolean(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// now serialize the table
|
||||
if (isarray) {
|
||||
for (i = 1; i <= tbllen; i++) {
|
||||
RETURN_ON_ERROR(sli = AppendStrList(&sl));
|
||||
lua_rawgeti(L, -1, i); // table/-2, value/-1
|
||||
RETURN_ON_ERROR(LuaEncodeJsonDataImpl(
|
||||
L, &sl.p[sli], level - 1, numformat, -1, visited, reason));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
} else {
|
||||
i = 1;
|
||||
lua_pushnil(L); // push the first key
|
||||
while (lua_next(L, -2)) {
|
||||
if (lua_type(L, -2) != LUA_TSTRING) {
|
||||
*reason = "json objects must only use string keys";
|
||||
goto OnError;
|
||||
}
|
||||
RETURN_ON_ERROR(sli = AppendStrList(&sl));
|
||||
RETURN_ON_ERROR(LuaEncodeJsonDataImpl(
|
||||
L, &sl.p[sli], level - 1, numformat, -2, visited, reason));
|
||||
RETURN_ON_ERROR(appendw(&sl.p[sli], ':'));
|
||||
RETURN_ON_ERROR(LuaEncodeJsonDataImpl(
|
||||
L, &sl.p[sli], level - 1, numformat, -1, visited, reason));
|
||||
lua_pop(L, 1); // table/-2, key/-1
|
||||
}
|
||||
// stack: table/-1, as the key was popped by lua_next
|
||||
SortStrList(&sl);
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, isarray ? '[' : '{'));
|
||||
RETURN_ON_ERROR(JoinStrList(&sl, buf, ','));
|
||||
FreeStrList(&sl);
|
||||
RETURN_ON_ERROR(appendw(buf, isarray ? ']' : '}'));
|
||||
LuaPopVisit(visited);
|
||||
lua_pop(L, 1); // table ref
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
*reason = "won't serialize cyclic lua table";
|
||||
goto OnError;
|
||||
}
|
||||
default:
|
||||
*reason = "unsupported lua type";
|
||||
goto OnError;
|
||||
}
|
||||
if (lua_isinteger(L, idx)) {
|
||||
RETURN_ON_ERROR(appendd(
|
||||
buf, ibuf, FormatInt64(ibuf, luaL_checkinteger(L, idx)) - ibuf));
|
||||
} else {
|
||||
*reason = "table has great depth";
|
||||
RETURN_ON_ERROR(appends(buf, DoubleToJson(ibuf, lua_tonumber(L, idx))));
|
||||
}
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeString(lua_State *L, char **buf, int idx,
|
||||
struct Serializer *z) {
|
||||
char *s;
|
||||
size_t m;
|
||||
s = lua_tolstring(L, idx, &m);
|
||||
if (!(s = EscapeJsStringLiteral(&z->strbuf, &z->strbuflen, s, m, &m))) {
|
||||
goto OnError;
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||
RETURN_ON_ERROR(appendd(buf, s, m));
|
||||
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeArray(lua_State *L, char **buf, struct Serializer *z,
|
||||
int level, size_t tbllen) {
|
||||
size_t i;
|
||||
RETURN_ON_ERROR(appendw(buf, '['));
|
||||
for (i = 1; i <= tbllen; i++) {
|
||||
lua_rawgeti(L, -1, i);
|
||||
if (i > 1) RETURN_ON_ERROR(appendw(buf, ','));
|
||||
RETURN_ON_ERROR(Serialize(L, buf, -1, z, level - 1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, ']'));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeObject(lua_State *L, char **buf, struct Serializer *z,
|
||||
int level) {
|
||||
bool comma = false;
|
||||
RETURN_ON_ERROR(appendw(buf, '{'));
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2)) {
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
if (comma) {
|
||||
RETURN_ON_ERROR(appendw(buf, ','));
|
||||
} 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));
|
||||
lua_pop(L, 1);
|
||||
} else {
|
||||
z->reason = "json objects must only use string keys";
|
||||
goto OnError;
|
||||
}
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, '}'));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeSorted(lua_State *L, char **buf, struct Serializer *z,
|
||||
int level) {
|
||||
int i;
|
||||
struct StrList sl = {0};
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2)) {
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
RETURN_ON_ERROR(i = AppendStrList(&sl));
|
||||
RETURN_ON_ERROR(SerializeString(L, sl.p + i, -2, z));
|
||||
RETURN_ON_ERROR(appendw(sl.p + i, ':'));
|
||||
RETURN_ON_ERROR(Serialize(L, sl.p + i, -1, z, level - 1));
|
||||
lua_pop(L, 1);
|
||||
} else {
|
||||
z->reason = "json objects must only use string keys";
|
||||
goto OnError;
|
||||
}
|
||||
}
|
||||
SortStrList(&sl);
|
||||
RETURN_ON_ERROR(appendw(buf, '{'));
|
||||
RETURN_ON_ERROR(JoinStrList(&sl, buf, ','));
|
||||
RETURN_ON_ERROR(appendw(buf, '}'));
|
||||
FreeStrList(&sl);
|
||||
return 0;
|
||||
OnError:
|
||||
FreeStrList(&sl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeTable(lua_State *L, char **buf, int idx,
|
||||
struct Serializer *z, int level) {
|
||||
int rc;
|
||||
bool isarray;
|
||||
lua_Unsigned n;
|
||||
RETURN_ON_ERROR(rc = LuaPushVisit(&z->visited, lua_topointer(L, idx)));
|
||||
if (!rc) {
|
||||
lua_pushvalue(L, idx);
|
||||
if ((n = lua_rawlen(L, -1)) > 0) {
|
||||
isarray = true;
|
||||
} else {
|
||||
// the json parser inserts `[0]=false` in empty arrays
|
||||
// so we can tell them apart from empty objects, which
|
||||
// is needed in order to have `[]` roundtrip the parse
|
||||
isarray =
|
||||
(lua_rawgeti(L, -1, 0) == LUA_TBOOLEAN && !lua_toboolean(L, -1));
|
||||
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));
|
||||
} else {
|
||||
RETURN_ON_ERROR(SerializeObject(L, buf, z, level));
|
||||
}
|
||||
LuaPopVisit(&z->visited);
|
||||
lua_pop(L, 1); // table ref
|
||||
return 0;
|
||||
} else {
|
||||
z->reason = "won't serialize cyclic lua table";
|
||||
goto OnError;
|
||||
}
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int Serialize(lua_State *L, char **buf, int idx, struct Serializer *z,
|
||||
int level) {
|
||||
if (level > 0) {
|
||||
switch (lua_type(L, idx)) {
|
||||
case LUA_TNIL:
|
||||
return SerializeNull(L, buf);
|
||||
case LUA_TBOOLEAN:
|
||||
return SerializeBoolean(L, buf, idx);
|
||||
case LUA_TSTRING:
|
||||
return SerializeString(L, buf, idx, z);
|
||||
case LUA_TNUMBER:
|
||||
return SerializeNumber(L, buf, idx);
|
||||
case LUA_TTABLE:
|
||||
return SerializeTable(L, buf, idx, z, level);
|
||||
default:
|
||||
z->reason = "unsupported lua type";
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
z->reason = "table has great depth";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes Lua data structure as JSON.
|
||||
*
|
||||
* On success, the serialized value is returned in `buf` and the Lua
|
||||
* stack should be in the same state when this function was called. On
|
||||
* error, values *will* be returned on the Lua stack:
|
||||
*
|
||||
* - possible junk...
|
||||
* - nil (index -2)
|
||||
* - error string (index -1)
|
||||
*
|
||||
* The following error return conditions are implemented:
|
||||
*
|
||||
* - Value found that isn't nil/bool/number/string/table
|
||||
* - Object existed with non-string key
|
||||
* - Tables had too much depth
|
||||
* - Tables are cyclic
|
||||
* - Out of C memory
|
||||
*
|
||||
* Your `*buf` must initialized to null. If it's allocated, then it must
|
||||
* have been allocated by cosmo's append*() library. After this function
|
||||
* is called, `*buf` will need to be free()'d.
|
||||
*
|
||||
* @param L is Lua interpreter state
|
||||
* @param buf receives encoded output string
|
||||
* @param numformat controls double formatting
|
||||
* @param idx is index of item on Lua stack
|
||||
* @return 0 on success, or -1 on error
|
||||
*/
|
||||
int LuaEncodeJsonData(lua_State *L, char **buf, char *numformat, int idx) {
|
||||
int rc;
|
||||
struct LuaVisited visited = {0};
|
||||
const char *reason = "out of memory";
|
||||
rc = LuaEncodeJsonDataImpl(L, buf, 64, numformat, idx, &visited, &reason);
|
||||
free(visited.p);
|
||||
if (rc == -1) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, reason);
|
||||
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 * 4)) {
|
||||
rc = Serialize(L, buf, idx, &z, depth);
|
||||
free(z.visited.p);
|
||||
free(z.strbuf);
|
||||
if (rc == -1) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, z.reason);
|
||||
}
|
||||
return rc;
|
||||
} else {
|
||||
luaL_error(L, "can't set stack depth");
|
||||
unreachable;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
|
467
third_party/lua/luaencodeluadata.c
vendored
467
third_party/lua/luaencodeluadata.c
vendored
|
@ -32,6 +32,14 @@
|
|||
#include "third_party/lua/lua.h"
|
||||
#include "third_party/lua/visitor.h"
|
||||
|
||||
struct Serializer {
|
||||
struct LuaVisited visited;
|
||||
const char *reason;
|
||||
bool sorted;
|
||||
};
|
||||
|
||||
static int Serialize(lua_State *, char **, int, struct Serializer *, int);
|
||||
|
||||
static bool IsLuaIdentifier(lua_State *L, int idx) {
|
||||
size_t i, n;
|
||||
const char *p;
|
||||
|
@ -43,12 +51,37 @@ static bool IsLuaIdentifier(lua_State *L, int idx) {
|
|||
return true;
|
||||
}
|
||||
|
||||
// TODO: Can we be smarter with lua_rawlen?
|
||||
// returns true if table at index -1 is an array
|
||||
//
|
||||
// for the purposes of lua serialization, we can only serialize using
|
||||
// array ordering when a table is an array in the strictest sense. we
|
||||
// consider a lua table an array if the following conditions are met:
|
||||
//
|
||||
// 1. for all 𝑘=𝑣 in table, 𝑘 is an integer ≥1
|
||||
// 2. no holes exist between MIN(𝑘) and MAX(𝑘)
|
||||
// 3. if non-empty, MIN(𝑘) is 1
|
||||
//
|
||||
// we need to do this because
|
||||
//
|
||||
// "the order in which the indices are enumerated is not specified,
|
||||
// even for numeric indices" ──quoth lua 5.4 manual § next()
|
||||
//
|
||||
// we're able to implement this check in one pass, since lua_rawlen()
|
||||
// reports the number of integers keys up until the first hole. so we
|
||||
// simply need to check if any non-integers keys exist or any integer
|
||||
// keys greater than the raw length.
|
||||
//
|
||||
// plesae note this is a more expensive check than the one we use for
|
||||
// the json serializer, because lua doesn't require objects have only
|
||||
// string keys. we want to be able to display mixed tables. it's just
|
||||
// they won't be displayed with specified ordering, unless sorted.
|
||||
static bool IsLuaArray(lua_State *L) {
|
||||
int i;
|
||||
lua_Integer i;
|
||||
lua_Unsigned n;
|
||||
n = lua_rawlen(L, -1);
|
||||
lua_pushnil(L);
|
||||
for (i = 1; lua_next(L, -2); ++i) {
|
||||
if (!lua_isinteger(L, -2) || lua_tointeger(L, -2) != i) {
|
||||
while (lua_next(L, -2)) {
|
||||
if (!lua_isinteger(L, -2) || (i = lua_tointeger(L, -2)) < 1 || i > n) {
|
||||
lua_pop(L, 2);
|
||||
return false;
|
||||
}
|
||||
|
@ -57,140 +90,297 @@ static bool IsLuaArray(lua_State *L) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static int LuaEncodeLuaOpaqueData(lua_State *L, char **buf, int idx,
|
||||
const char *kind) {
|
||||
if (appendf(buf, "\"%s@%p\"", kind, lua_topointer(L, idx)) != -1) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
static int SerializeNil(lua_State *L, char **buf) {
|
||||
RETURN_ON_ERROR(appendw(buf, READ32LE("nil")));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int LuaEncodeLuaDataImpl(lua_State *L, char **buf, int level,
|
||||
char *numformat, int idx,
|
||||
struct LuaVisited *visited) {
|
||||
char *s;
|
||||
bool isarray;
|
||||
lua_Integer i;
|
||||
struct StrList sl = {0};
|
||||
int ktype, vtype, sli, rc;
|
||||
size_t tbllen, buflen, slen;
|
||||
char ibuf[24], fmt[] = "%.14g";
|
||||
if (level > 0) {
|
||||
switch (lua_type(L, idx)) {
|
||||
static int SerializeBoolean(lua_State *L, char **buf, int idx) {
|
||||
RETURN_ON_ERROR(appendw(
|
||||
buf, lua_toboolean(L, idx) ? READ32LE("true") : READ64LE("false\0\0")));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
case LUA_TNIL:
|
||||
RETURN_ON_ERROR(appendw(buf, READ32LE("nil")));
|
||||
return 0;
|
||||
static int SerializeOpaque(lua_State *L, char **buf, int idx,
|
||||
const char *kind) {
|
||||
RETURN_ON_ERROR(appendf(buf, "\"%s@%p\"", kind, lua_topointer(L, idx)));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
case LUA_TSTRING:
|
||||
s = lua_tolstring(L, idx, &slen);
|
||||
RETURN_ON_ERROR(EscapeLuaString(s, slen, buf));
|
||||
return 0;
|
||||
|
||||
case LUA_TFUNCTION:
|
||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "func");
|
||||
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "light");
|
||||
|
||||
case LUA_TTHREAD:
|
||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "thread");
|
||||
|
||||
case LUA_TUSERDATA:
|
||||
if (luaL_callmeta(L, idx, "__repr")) {
|
||||
if (lua_type(L, -1) == LUA_TSTRING) {
|
||||
s = lua_tolstring(L, -1, &slen);
|
||||
RETURN_ON_ERROR(appendd(buf, s, slen));
|
||||
} else {
|
||||
RETURN_ON_ERROR(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);
|
||||
RETURN_ON_ERROR(EscapeLuaString(s, slen, buf));
|
||||
} else {
|
||||
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]",
|
||||
"__tostring", luaL_typename(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
return 0;
|
||||
}
|
||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "udata");
|
||||
|
||||
case LUA_TNUMBER:
|
||||
if (lua_isinteger(L, idx)) {
|
||||
RETURN_ON_ERROR(
|
||||
appendd(buf, ibuf,
|
||||
FormatFlex64(ibuf, luaL_checkinteger(L, idx), 2) - ibuf));
|
||||
} else {
|
||||
RETURN_ON_ERROR(
|
||||
appends(buf, DoubleToLua(ibuf, lua_tonumber(L, idx))));
|
||||
}
|
||||
return 0;
|
||||
|
||||
case LUA_TBOOLEAN:
|
||||
RETURN_ON_ERROR(appendw(buf, lua_toboolean(L, idx)
|
||||
? READ32LE("true")
|
||||
: READ64LE("false\0\0")));
|
||||
return 0;
|
||||
|
||||
case LUA_TTABLE:
|
||||
i = 0;
|
||||
RETURN_ON_ERROR(rc = LuaPushVisit(visited, lua_topointer(L, idx)));
|
||||
if (!rc) {
|
||||
lua_pushvalue(L, idx); // idx becomes invalid once we change stack
|
||||
isarray = IsLuaArray(L);
|
||||
lua_pushnil(L); // push the first key
|
||||
while (lua_next(L, -2)) {
|
||||
ktype = lua_type(L, -2);
|
||||
vtype = lua_type(L, -1);
|
||||
RETURN_ON_ERROR(sli = AppendStrList(&sl));
|
||||
if (isarray) {
|
||||
// use {v₁′,v₂′,...} for lua-normal integer keys
|
||||
} else if (ktype == LUA_TSTRING && IsLuaIdentifier(L, -2)) {
|
||||
// use {𝑘=𝑣′} syntax when 𝑘 is legal as a lua identifier
|
||||
s = lua_tolstring(L, -2, &slen);
|
||||
RETURN_ON_ERROR(appendd(&sl.p[sli], s, slen));
|
||||
RETURN_ON_ERROR(appendw(&sl.p[sli], '='));
|
||||
} else {
|
||||
// use {[𝑘′]=𝑣′} otherwise
|
||||
RETURN_ON_ERROR(appendw(&sl.p[sli], '['));
|
||||
RETURN_ON_ERROR(LuaEncodeLuaDataImpl(L, &sl.p[sli], level - 1,
|
||||
numformat, -2, visited));
|
||||
RETURN_ON_ERROR(appendw(&sl.p[sli], ']' | '=' << 010));
|
||||
}
|
||||
RETURN_ON_ERROR(LuaEncodeLuaDataImpl(L, &sl.p[sli], level - 1,
|
||||
numformat, -1, visited));
|
||||
lua_pop(L, 1); // table/-2, key/-1
|
||||
}
|
||||
lua_pop(L, 1); // table ref
|
||||
if (!isarray) SortStrList(&sl);
|
||||
RETURN_ON_ERROR(appendw(buf, '{'));
|
||||
RETURN_ON_ERROR(JoinStrList(&sl, buf, READ16LE(", ")));
|
||||
RETURN_ON_ERROR(appendw(buf, '}'));
|
||||
FreeStrList(&sl);
|
||||
LuaPopVisit(visited);
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "cyclic");
|
||||
}
|
||||
default:
|
||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "unsupported");
|
||||
static int SerializeNumber(lua_State *L, char **buf, int idx) {
|
||||
int64_t x;
|
||||
char ibuf[24];
|
||||
if (lua_isinteger(L, idx)) {
|
||||
x = luaL_checkinteger(L, idx);
|
||||
if (x == -9223372036854775807 - 1) {
|
||||
RETURN_ON_ERROR(appends(buf, "-9223372036854775807 - 1"));
|
||||
} else {
|
||||
RETURN_ON_ERROR(appendd(buf, ibuf, FormatFlex64(ibuf, x, 2) - ibuf));
|
||||
}
|
||||
} else {
|
||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "greatdepth");
|
||||
RETURN_ON_ERROR(appends(buf, DoubleToLua(ibuf, lua_tonumber(L, idx))));
|
||||
}
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
int main(int argc, char *argv[]) {
|
||||
int i, j;
|
||||
signed char tab[256] = {0};
|
||||
for (i = 0; i < 256; ++i) {
|
||||
if (i < 0x20) tab[i] = 1; // hex
|
||||
if (i >= 0x7f) tab[i] = 1; // hex
|
||||
}
|
||||
tab['\e'] = 'e';
|
||||
tab['\a'] = 'a';
|
||||
tab['\b'] = 'b';
|
||||
tab['\f'] = 'f';
|
||||
tab['\n'] = 'n';
|
||||
tab['\r'] = 'r';
|
||||
tab['\t'] = 't';
|
||||
tab['\v'] = 'v';
|
||||
tab['\\'] = '\\';
|
||||
tab['\"'] = '"';
|
||||
tab['\v'] = 'v';
|
||||
printf("const char kBase64[256] = {\n");
|
||||
for (i = 0; i < 16; ++i) {
|
||||
printf(" ");
|
||||
for (j = 0; j < 16; ++j) {
|
||||
if (isprint(tab[i * 16 + j])) {
|
||||
printf("'%c',", tab[i * 16 + j]);
|
||||
} else {
|
||||
printf("%d,", tab[i * 16 + j]);
|
||||
}
|
||||
}
|
||||
printf(" // 0x%02x\n", i * 16);
|
||||
}
|
||||
printf("};\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
static const char kLuaStrXlat[256] = {
|
||||
1,1,1,1,1,1,1,'a','b','t','n','v','f','r',1,1, // 0x00
|
||||
1,1,1,1,1,1,1,1,1,1,1,'e',1,1,1,1, // 0x10
|
||||
0,0,'"',0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x20
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x30
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x40
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,'\\',0,0,0, // 0x50
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x60
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, // 0x70
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x80
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x90
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0xa0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0xb0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0xc0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0xd0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0xe0
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0xf0
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static int SerializeString(lua_State *L, char **buf, int idx) {
|
||||
int x;
|
||||
size_t i, n;
|
||||
const char *s;
|
||||
s = lua_tolstring(L, idx, &n);
|
||||
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||
for (i = 0; i < n; i++) {
|
||||
switch ((x = kLuaStrXlat[s[i] & 255])) {
|
||||
case 0:
|
||||
RETURN_ON_ERROR(appendw(buf, s[i]));
|
||||
break;
|
||||
default:
|
||||
RETURN_ON_ERROR(appendw(buf, READ32LE("\\\x00\x00") | (x << 8)));
|
||||
break;
|
||||
case 1:
|
||||
RETURN_ON_ERROR(
|
||||
appendw(buf, '\\' | 'x' << 010 |
|
||||
"0123456789abcdef"[(s[i] & 0xF0) >> 4] << 020 |
|
||||
"0123456789abcdef"[(s[i] & 0x0F) >> 0] << 030));
|
||||
break;
|
||||
}
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeUserData(lua_State *L, char **buf, int idx) {
|
||||
size_t n;
|
||||
const char *s;
|
||||
if (luaL_callmeta(L, idx, "__repr")) {
|
||||
if (lua_type(L, -1) == LUA_TSTRING) {
|
||||
s = lua_tolstring(L, -1, &n);
|
||||
RETURN_ON_ERROR(appendd(buf, s, n));
|
||||
} else {
|
||||
RETURN_ON_ERROR(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) {
|
||||
RETURN_ON_ERROR(SerializeString(L, buf, -1));
|
||||
} else {
|
||||
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]",
|
||||
"__tostring", luaL_typename(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
return 0;
|
||||
}
|
||||
return SerializeOpaque(L, buf, idx, "udata");
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeArray(lua_State *L, char **buf, struct Serializer *z,
|
||||
int depth) {
|
||||
size_t i, n;
|
||||
const char *s;
|
||||
RETURN_ON_ERROR(appendw(buf, '{'));
|
||||
n = lua_rawlen(L, -1);
|
||||
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));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, '}'));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeObject(lua_State *L, char **buf, struct Serializer *z,
|
||||
int depth) {
|
||||
int rc;
|
||||
size_t n;
|
||||
const char *s;
|
||||
bool comma = false;
|
||||
RETURN_ON_ERROR(appendw(buf, '{'));
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2)) {
|
||||
if (comma) {
|
||||
RETURN_ON_ERROR(appendw(buf, READ16LE(", ")));
|
||||
} else {
|
||||
comma = true;
|
||||
}
|
||||
if (lua_type(L, -2) == LUA_TSTRING && IsLuaIdentifier(L, -2)) {
|
||||
// use {𝑘=𝑣′} syntax when 𝑘 is a legal lua identifier
|
||||
s = lua_tolstring(L, -2, &n);
|
||||
RETURN_ON_ERROR(appendd(buf, s, n));
|
||||
RETURN_ON_ERROR(appendw(buf, '='));
|
||||
} else {
|
||||
// use {[𝑘′]=𝑣′} otherwise
|
||||
RETURN_ON_ERROR(appendw(buf, '['));
|
||||
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));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
RETURN_ON_ERROR(appendw(buf, '}'));
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeSorted(lua_State *L, char **buf, struct Serializer *z,
|
||||
int depth) {
|
||||
size_t n;
|
||||
int i, rc;
|
||||
const char *s;
|
||||
struct StrList sl = {0};
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2)) {
|
||||
RETURN_ON_ERROR(i = AppendStrList(&sl));
|
||||
if (lua_type(L, -2) == LUA_TSTRING && IsLuaIdentifier(L, -2)) {
|
||||
// use {𝑘=𝑣′} syntax when 𝑘 is a legal lua identifier
|
||||
s = lua_tolstring(L, -2, &n);
|
||||
RETURN_ON_ERROR(appendd(sl.p + i, s, n));
|
||||
RETURN_ON_ERROR(appendw(sl.p + i, '='));
|
||||
} else {
|
||||
// use {[𝑘′]=𝑣′} otherwise
|
||||
RETURN_ON_ERROR(appendw(sl.p + i, '['));
|
||||
RETURN_ON_ERROR(Serialize(L, sl.p + i, -2, z, depth - 1));
|
||||
RETURN_ON_ERROR(appendw(sl.p + i, ']' | '=' << 010));
|
||||
}
|
||||
RETURN_ON_ERROR(Serialize(L, sl.p + i, -1, z, depth - 1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
SortStrList(&sl);
|
||||
RETURN_ON_ERROR(appendw(buf, '{'));
|
||||
RETURN_ON_ERROR(JoinStrList(&sl, buf, READ16LE(", ")));
|
||||
RETURN_ON_ERROR(appendw(buf, '}'));
|
||||
FreeStrList(&sl);
|
||||
return 0;
|
||||
OnError:
|
||||
FreeStrList(&sl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int SerializeTable(lua_State *L, char **buf, int idx,
|
||||
struct Serializer *z, int depth) {
|
||||
int rc;
|
||||
RETURN_ON_ERROR(rc = LuaPushVisit(&z->visited, lua_topointer(L, idx)));
|
||||
if (rc) return SerializeOpaque(L, buf, idx, "cyclic");
|
||||
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));
|
||||
}
|
||||
LuaPopVisit(&z->visited);
|
||||
lua_pop(L, 1); // table ref
|
||||
return 0;
|
||||
OnError:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int Serialize(lua_State *L, char **buf, int idx, struct Serializer *z,
|
||||
int depth) {
|
||||
if (depth > 0) {
|
||||
switch (lua_type(L, idx)) {
|
||||
case LUA_TNIL:
|
||||
return SerializeNil(L, buf);
|
||||
case LUA_TBOOLEAN:
|
||||
return SerializeBoolean(L, buf, idx);
|
||||
case LUA_TNUMBER:
|
||||
return SerializeNumber(L, buf, idx);
|
||||
case LUA_TSTRING:
|
||||
return SerializeString(L, buf, idx);
|
||||
case LUA_TTABLE:
|
||||
return SerializeTable(L, buf, idx, z, depth);
|
||||
case LUA_TUSERDATA:
|
||||
return SerializeUserData(L, buf, idx);
|
||||
case LUA_TFUNCTION:
|
||||
return SerializeOpaque(L, buf, idx, "func");
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
return SerializeOpaque(L, buf, idx, "light");
|
||||
case LUA_TTHREAD:
|
||||
return SerializeOpaque(L, buf, idx, "thread");
|
||||
default:
|
||||
return SerializeOpaque(L, buf, idx, "unsupported");
|
||||
}
|
||||
} else {
|
||||
return SerializeOpaque(L, buf, idx, "greatdepth");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes Lua data structure as Lua code string.
|
||||
*
|
||||
|
@ -203,18 +393,23 @@ OnError:
|
|||
*
|
||||
* @param L is Lua interpreter state
|
||||
* @param buf receives encoded output string
|
||||
* @param numformat controls double formatting
|
||||
* @param idx is index of item on Lua stack
|
||||
* @param sorted is ignored (always sorted)
|
||||
* @return 0 on success, or -1 on error
|
||||
*/
|
||||
int LuaEncodeLuaData(lua_State *L, char **buf, char *numformat, int idx) {
|
||||
int rc;
|
||||
struct LuaVisited visited = {0};
|
||||
rc = LuaEncodeLuaDataImpl(L, buf, 64, numformat, idx, &visited);
|
||||
free(visited.p);
|
||||
if (rc == -1) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "out of memory");
|
||||
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 * 4)) {
|
||||
rc = Serialize(L, buf, idx, &z, depth);
|
||||
free(z.visited.p);
|
||||
if (rc == -1) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, z.reason);
|
||||
}
|
||||
return rc;
|
||||
} else {
|
||||
luaL_error(L, "can't set stack depth");
|
||||
unreachable;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
|
2
third_party/lua/luaformatstack.c
vendored
2
third_party/lua/luaformatstack.c
vendored
|
@ -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, "g", i);
|
||||
LuaEncodeLuaData(L, &b, i, true);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue