mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-03 15:38:22 +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
|
@ -16,48 +16,24 @@
|
||||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/fmt/fmt.h"
|
#include "libc/bits/popcnt.h"
|
||||||
#include "libc/fmt/itoa.h"
|
#include "libc/fmt/itoa.h"
|
||||||
#include "libc/math.h"
|
|
||||||
|
|
||||||
static inline int CountZeroesHex(uint64_t x) {
|
|
||||||
int n;
|
|
||||||
for (n = 0; x;) {
|
|
||||||
if (!(x & 15)) {
|
|
||||||
++n;
|
|
||||||
}
|
|
||||||
x >>= 4;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int CountZeroesDec(int64_t s) {
|
|
||||||
int n, r;
|
|
||||||
uint64_t x;
|
|
||||||
x = s >= 0 ? s : -(uint64_t)s;
|
|
||||||
for (n = 0; x;) {
|
|
||||||
r = x % 10;
|
|
||||||
x = x / 10;
|
|
||||||
if (!r) ++n;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats integer using decimal or hexadecimal.
|
* Formats integer using decimal or hexadecimal.
|
||||||
*
|
*
|
||||||
* We choose hex vs. decimal based on whichever one has the most zeroes.
|
* This formats as int64 signed decimal. However it's a:
|
||||||
* We only bother counting zeroes for numbers outside -256 ≤ 𝑥 ≤ 256.
|
*
|
||||||
|
* 1. positive number
|
||||||
|
* 2. with population count of 1
|
||||||
|
* 3. and a magnitude of at least 256
|
||||||
|
*
|
||||||
|
* Then we switch to hex notation to make the number more readable.
|
||||||
*/
|
*/
|
||||||
char *FormatFlex64(char p[hasatleast 24], int64_t x, char z) {
|
char *FormatFlex64(char p[hasatleast 24], int64_t x, char z) {
|
||||||
int zhex, zdec;
|
if (x >= 256 && popcnt(x) == 1) {
|
||||||
if (-256 <= x && x <= 256) goto UseDecimal;
|
|
||||||
zhex = CountZeroesHex(x) * 16;
|
|
||||||
zdec = CountZeroesDec(x) * 10;
|
|
||||||
if (zdec >= zhex) {
|
|
||||||
UseDecimal:
|
|
||||||
return FormatInt64(p, x);
|
|
||||||
} else {
|
|
||||||
return FormatHex64(p, x, z);
|
return FormatHex64(p, x, z);
|
||||||
|
} else {
|
||||||
|
return FormatInt64(p, x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ static inline int PickGoodWidth(unsigned x, char z) {
|
||||||
* Converts unsigned 64-bit integer to hex string.
|
* Converts unsigned 64-bit integer to hex string.
|
||||||
*
|
*
|
||||||
* @param p needs at least 19 bytes
|
* @param p needs at least 19 bytes
|
||||||
* @param z is 0 for DIGITS, 1 for 0bDIGITS, 2 for 0bDIGITS if ≠0
|
* @param z is 0 for DIGITS, 1 for 0xDIGITS, 2 for 0xDIGITS if ≠0
|
||||||
* @return pointer to nul byte
|
* @return pointer to nul byte
|
||||||
*/
|
*/
|
||||||
char *FormatHex64(char p[hasatleast 19], uint64_t x, char z) {
|
char *FormatHex64(char p[hasatleast 19], uint64_t x, char z) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ char *EscapePath(const char *, size_t, size_t *);
|
||||||
char *EscapeParam(const char *, size_t, size_t *);
|
char *EscapeParam(const char *, size_t, size_t *);
|
||||||
char *EscapeFragment(const char *, size_t, size_t *);
|
char *EscapeFragment(const char *, size_t, size_t *);
|
||||||
char *EscapeSegment(const char *, size_t, size_t *);
|
char *EscapeSegment(const char *, size_t, size_t *);
|
||||||
char *EscapeJsStringLiteral(const char *, size_t, size_t *);
|
char *EscapeJsStringLiteral(char **, size_t *, const char *, size_t, size_t *);
|
||||||
|
|
||||||
ssize_t HasControlCodes(const char *, size_t, int);
|
ssize_t HasControlCodes(const char *, size_t, int);
|
||||||
char *Underlong(const char *, size_t, size_t *);
|
char *Underlong(const char *, size_t, size_t *);
|
||||||
|
|
|
@ -47,19 +47,30 @@ static const char kEscapeLiteral[128] = {
|
||||||
* EscapeJsStringLiteral(Underlong(𝑥)) since EscapeJsStringLiteral(𝑥)
|
* EscapeJsStringLiteral(Underlong(𝑥)) since EscapeJsStringLiteral(𝑥)
|
||||||
* will do the same thing.
|
* will do the same thing.
|
||||||
*
|
*
|
||||||
|
* @param r is realloc'able output buffer reused between calls
|
||||||
|
* @param y is used to track byte length of `*r`
|
||||||
* @param p is input value
|
* @param p is input value
|
||||||
* @param n if -1 implies strlen
|
* @param n if -1 implies strlen
|
||||||
* @param out_size if non-NULL receives output length
|
* @param out_size if non-NULL receives output length
|
||||||
* @return allocated NUL-terminated buffer, or NULL w/ errno
|
* @return *r on success, or null w/ errno
|
||||||
*/
|
*/
|
||||||
char *EscapeJsStringLiteral(const char *p, size_t n, size_t *z) {
|
char *EscapeJsStringLiteral(char **r, size_t *y, const char *p, size_t n,
|
||||||
|
size_t *z) {
|
||||||
|
char *q;
|
||||||
uint64_t w;
|
uint64_t w;
|
||||||
char *q, *r;
|
|
||||||
size_t i, j, m;
|
size_t i, j, m;
|
||||||
wint_t x, a, b;
|
wint_t x, a, b;
|
||||||
if (z) *z = 0;
|
if (z) *z = 0; // TODO(jart): why is this here?
|
||||||
if (n == -1) n = p ? strlen(p) : 0;
|
if (n == -1) n = p ? strlen(p) : 0;
|
||||||
if ((q = r = malloc(n * 6 + 6 + 1))) {
|
q = *r;
|
||||||
|
i = n * 8 + 6 + 1; // only need *6 but *8 is faster
|
||||||
|
if (i > *y) {
|
||||||
|
if ((q = realloc(q, i))) {
|
||||||
|
*r = q;
|
||||||
|
*y = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (q) {
|
||||||
for (i = 0; i < n;) {
|
for (i = 0; i < n;) {
|
||||||
x = p[i++] & 0xff;
|
x = p[i++] & 0xff;
|
||||||
if (x >= 0300) {
|
if (x >= 0300) {
|
||||||
|
@ -138,9 +149,8 @@ char *EscapeJsStringLiteral(const char *p, size_t n, size_t *z) {
|
||||||
unreachable;
|
unreachable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (z) *z = q - r;
|
if (z) *z = q - *r;
|
||||||
*q++ = '\0';
|
*q++ = '\0';
|
||||||
if ((q = realloc(r, q - r))) r = q;
|
|
||||||
}
|
}
|
||||||
return r;
|
return *r;
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,7 +130,14 @@ void FreeBulk(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MallocFree(void) {
|
||||||
|
char *volatile p;
|
||||||
|
p = malloc(16);
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
BENCH(bulk_free, bench) {
|
BENCH(bulk_free, bench) {
|
||||||
|
EZBENCH2("free(malloc(16))", donothing, MallocFree());
|
||||||
EZBENCH2("free() bulk", BulkFreeBenchSetup(), FreeBulk());
|
EZBENCH2("free() bulk", BulkFreeBenchSetup(), FreeBulk());
|
||||||
EZBENCH2("bulk_free()", BulkFreeBenchSetup(),
|
EZBENCH2("bulk_free()", BulkFreeBenchSetup(),
|
||||||
bulk_free(bulk, ARRAYLEN(bulk)));
|
bulk_free(bulk, ARRAYLEN(bulk)));
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/calls/calls.h"
|
#include "libc/calls/calls.h"
|
||||||
|
#include "libc/mem/mem.h"
|
||||||
#include "libc/runtime/gc.internal.h"
|
#include "libc/runtime/gc.internal.h"
|
||||||
#include "libc/stdio/stdio.h"
|
#include "libc/stdio/stdio.h"
|
||||||
#include "libc/testlib/ezbench.h"
|
#include "libc/testlib/ezbench.h"
|
||||||
|
@ -24,60 +25,69 @@
|
||||||
#include "libc/testlib/testlib.h"
|
#include "libc/testlib/testlib.h"
|
||||||
#include "net/http/escape.h"
|
#include "net/http/escape.h"
|
||||||
|
|
||||||
|
char *o;
|
||||||
|
size_t y;
|
||||||
|
|
||||||
|
void TearDownOnce(void) {
|
||||||
|
free(o);
|
||||||
|
o = 0;
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
char *escapejs(const char *s) {
|
char *escapejs(const char *s) {
|
||||||
char *p;
|
char *p;
|
||||||
size_t n;
|
size_t n;
|
||||||
p = EscapeJsStringLiteral(s, strlen(s), &n);
|
p = EscapeJsStringLiteral(&o, &y, s, strlen(s), &n);
|
||||||
ASSERT_EQ(strlen(p), n);
|
ASSERT_EQ(strlen(p), n);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EscapeJsStringLiteral, test) {
|
TEST(EscapeJsStringLiteral, test) {
|
||||||
EXPECT_STREQ("", gc(escapejs("")));
|
EXPECT_STREQ("", escapejs(""));
|
||||||
EXPECT_STREQ("\\u00ff", gc(escapejs("\377")));
|
EXPECT_STREQ("\\u00ff", escapejs("\377"));
|
||||||
EXPECT_STREQ("\\u00ff\\u0080\\u0080\\u0080\\u0080",
|
EXPECT_STREQ("\\u00ff\\u0080\\u0080\\u0080\\u0080",
|
||||||
gc(escapejs("\377\200\200\200\200")));
|
escapejs("\377\200\200\200\200"));
|
||||||
EXPECT_STREQ("\\u0001\\u0002\\u0003 \\u0026\\u003d\\u003c\\u003e\\/",
|
EXPECT_STREQ("\\u0001\\u0002\\u0003 \\u0026\\u003d\\u003c\\u003e\\/",
|
||||||
gc(escapejs("\1\2\3 &=<>/")));
|
escapejs("\1\2\3 &=<>/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EscapeJsStringLiteral, testUcs2) {
|
TEST(EscapeJsStringLiteral, testUcs2) {
|
||||||
EXPECT_STREQ("\\u00d0\\u263b", gc(escapejs("Ð☻")));
|
EXPECT_STREQ("\\u00d0\\u263b", escapejs("Ð☻"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EscapeJsStringLiteral, testAstralPlanes) {
|
TEST(EscapeJsStringLiteral, testAstralPlanes) {
|
||||||
EXPECT_STREQ("\\ud800\\udf30\\ud800\\udf30", gc(escapejs("𐌰𐌰")));
|
EXPECT_STREQ("\\ud800\\udf30\\ud800\\udf30", escapejs("𐌰𐌰"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(EscapeJsStringLiteral, testBrokenUnicode_sparesInnocentCharacters) {
|
TEST(EscapeJsStringLiteral, testBrokenUnicode_sparesInnocentCharacters) {
|
||||||
EXPECT_STREQ("\\u00e1YO", gc(escapejs("\xE1YO")));
|
EXPECT_STREQ("\\u00e1YO", escapejs("\xE1YO"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void makefile1(void) {
|
void makefile1(void) {
|
||||||
FILE *f;
|
FILE *f;
|
||||||
char *p;
|
char *p;
|
||||||
size_t n;
|
size_t n;
|
||||||
p = EscapeJsStringLiteral(kHyperion, kHyperionSize, &n);
|
p = EscapeJsStringLiteral(&o, &y, kHyperion, kHyperionSize, &n);
|
||||||
f = fopen("/tmp/a", "wb");
|
f = fopen("/tmp/a", "wb");
|
||||||
fwrite(p, n, 1, f);
|
fwrite(p, n, 1, f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
free(p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void makefile2(void) {
|
void makefile2(void) {
|
||||||
int fd;
|
int fd;
|
||||||
char *p;
|
char *p;
|
||||||
size_t n;
|
size_t n;
|
||||||
p = EscapeJsStringLiteral(kHyperion, kHyperionSize, &n);
|
p = EscapeJsStringLiteral(&o, &y, kHyperion, kHyperionSize, &n);
|
||||||
fd = creat("/tmp/a", 0644);
|
fd = creat("/tmp/a", 0644);
|
||||||
write(fd, p, n);
|
write(fd, p, n);
|
||||||
close(fd);
|
close(fd);
|
||||||
free(p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BENCH(EscapeJsStringLiteral, bench) {
|
BENCH(EscapeJsStringLiteral, bench) {
|
||||||
EZBENCH2("escapejs", donothing,
|
EZBENCH2("escapejs tiny", donothing,
|
||||||
free(EscapeJsStringLiteral(kHyperion, kHyperionSize, 0)));
|
EscapeJsStringLiteral(&o, &y, "hello", 5, 0));
|
||||||
|
EZBENCH2("escapejs book", donothing,
|
||||||
|
EscapeJsStringLiteral(&o, &y, kHyperion, kHyperionSize, 0));
|
||||||
EZBENCH2("makefile1", donothing, makefile1());
|
EZBENCH2("makefile1", donothing, makefile1());
|
||||||
EZBENCH2("makefile2", donothing, makefile2());
|
EZBENCH2("makefile2", donothing, makefile2());
|
||||||
}
|
}
|
||||||
|
|
127
test/tool/net/encodejson_test.lua
Normal file
127
test/tool/net/encodejson_test.lua
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
-- 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.
|
||||||
|
|
||||||
|
assert(EncodeJson(nil) == "null")
|
||||||
|
assert(EncodeJson(true) == "true")
|
||||||
|
assert(EncodeJson(false) == "false")
|
||||||
|
assert(EncodeJson(0) == "0")
|
||||||
|
assert(EncodeJson(0.0) == "0")
|
||||||
|
assert(EncodeJson(3.14) == "3.14")
|
||||||
|
assert(EncodeJson(0/0) == "null")
|
||||||
|
assert(EncodeJson(math.huge) == "null")
|
||||||
|
assert(EncodeJson(123.456e-789) == '0')
|
||||||
|
assert(EncodeJson(9223372036854775807) == '9223372036854775807')
|
||||||
|
assert(EncodeJson(-9223372036854775807 - 1) == '-9223372036854775808')
|
||||||
|
assert(EncodeJson({2, 1}) == "[2,1]")
|
||||||
|
assert(EncodeJson({3, 2}) == "[3,2]")
|
||||||
|
assert(EncodeJson({[0]=false}) == "[]")
|
||||||
|
assert(EncodeJson({[1]=123, [2]=456}) == "[123,456]")
|
||||||
|
assert(EncodeJson({[1]=123, [3]=456}) == "[123]")
|
||||||
|
assert(EncodeJson({["hi"]=1, [1]=2}) == "[2]")
|
||||||
|
assert(EncodeJson({[1]=2, ["hi"]=1}) == "[2]")
|
||||||
|
assert(EncodeJson({[3]=3, [1]=3}) == "[3]")
|
||||||
|
assert(EncodeJson("hello") == "\"hello\"")
|
||||||
|
assert(EncodeJson("\x00") == "\"\\u0000\"")
|
||||||
|
assert(EncodeJson("\t") == "\"\\t\"")
|
||||||
|
assert(EncodeJson("→") == "\"\\u2192\"")
|
||||||
|
assert(EncodeJson("𐌰") == "\"\\ud800\\udf30\"")
|
||||||
|
assert(EncodeJson("\t") == [["\t"]])
|
||||||
|
assert(EncodeJson("\r") == [["\r"]])
|
||||||
|
assert(EncodeJson("\n") == [["\n"]])
|
||||||
|
assert(EncodeJson("\f") == [["\f"]])
|
||||||
|
assert(EncodeJson("\"") == [["\""]])
|
||||||
|
assert(EncodeJson("\'") == [["\'"]])
|
||||||
|
assert(EncodeJson("\\") == [["\\"]])
|
||||||
|
|
||||||
|
val, err = EncodeJson({[3]=3, [2]=3})
|
||||||
|
assert(val == nil)
|
||||||
|
assert(err == 'json objects must only use string keys')
|
||||||
|
|
||||||
|
val, err = EncodeJson({[{[{[3]=2}]=2}]=2})
|
||||||
|
assert(val == nil)
|
||||||
|
assert(err == 'json objects must only use string keys')
|
||||||
|
|
||||||
|
val, err = EncodeJson(EncodeJson)
|
||||||
|
assert(val == nil)
|
||||||
|
assert(err == "unsupported lua type")
|
||||||
|
|
||||||
|
x = {2, 1}
|
||||||
|
x[3] = x
|
||||||
|
val, err = EncodeJson(x)
|
||||||
|
assert(val == nil)
|
||||||
|
assert(err == "won't serialize cyclic lua table")
|
||||||
|
|
||||||
|
x = {}
|
||||||
|
x.c = 'c'
|
||||||
|
x.a = 'a'
|
||||||
|
x.b = 'b'
|
||||||
|
assert(EncodeJson(x) == '{"a":"a","b":"b","c":"c"}')
|
||||||
|
|
||||||
|
assert(EncodeJson({{{{{{{{{{{},{{{{},{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) ==
|
||||||
|
'[[[[[[[[[[{},[[[{},[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{}]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]')
|
||||||
|
val, err = EncodeJson({{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}})
|
||||||
|
assert(val == nil)
|
||||||
|
assert(err == 'table has great depth')
|
||||||
|
|
||||||
|
-- 63 objects
|
||||||
|
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={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}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||||
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) ==
|
||||||
|
"{\"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\":"..
|
||||||
|
"{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":{\"k\":0}}"..
|
||||||
|
"}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"..
|
||||||
|
"}}}}}}}}}}}}}")
|
||||||
|
|
||||||
|
-- 64 objects
|
||||||
|
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={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}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||||
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}})
|
||||||
|
assert(res == nil)
|
||||||
|
assert(err == "table has great depth")
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- benchmark nanos ticks
|
||||||
|
-- JsonEncArray 366 1134
|
||||||
|
-- JsonEncUnsort 754 2336
|
||||||
|
-- JsonEncObject 1208 3742
|
||||||
|
|
||||||
|
function JsonEncArray()
|
||||||
|
EncodeJson({2, 1, 10, 3, "hello"})
|
||||||
|
end
|
||||||
|
|
||||||
|
function JsonEncObject()
|
||||||
|
EncodeJson({yo=2, bye=1, there=10, sup=3, hi="hello"})
|
||||||
|
end
|
||||||
|
|
||||||
|
UNSORT = {sorted=false}
|
||||||
|
function JsonEncUnsort()
|
||||||
|
EncodeJson({yo=2, bye=1, there=10, sup=3, hi="hello"}, UNSORT)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bench()
|
||||||
|
print("JsonEncArray", Benchmark(JsonEncArray))
|
||||||
|
print("JsonEncUnsort", Benchmark(JsonEncUnsort))
|
||||||
|
print("JsonEncObject", Benchmark(JsonEncObject))
|
||||||
|
end
|
120
test/tool/net/encodelua_test.lua
Normal file
120
test/tool/net/encodelua_test.lua
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
-- 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.
|
||||||
|
|
||||||
|
assert(EncodeLua(nil) == "nil")
|
||||||
|
assert(EncodeLua(true) == "true")
|
||||||
|
assert(EncodeLua(false) == "false")
|
||||||
|
assert(EncodeLua(0) == "0")
|
||||||
|
assert(EncodeLua(0.0) == "0.")
|
||||||
|
assert(EncodeLua(3.14) == "3.14")
|
||||||
|
assert(EncodeLua(0/0) == "0/0")
|
||||||
|
assert(EncodeLua(123.456e-789) == '0.')
|
||||||
|
assert(EncodeLua(9223372036854775807) == '9223372036854775807')
|
||||||
|
assert(EncodeLua(-9223372036854775807 - 1) == '-9223372036854775807 - 1')
|
||||||
|
assert(EncodeLua(7000) == '7000')
|
||||||
|
assert(EncodeLua(0x100) == '0x0100')
|
||||||
|
assert(EncodeLua(0x10000) == '0x00010000')
|
||||||
|
assert(EncodeLua(0x100000000) == '0x0000000100000000')
|
||||||
|
assert(EncodeLua(math.huge) == "math.huge")
|
||||||
|
assert(EncodeLua({2, 1}) == "{2, 1}")
|
||||||
|
assert(EncodeLua({3, 2}) == "{3, 2}")
|
||||||
|
assert(EncodeLua({[0]=false}) == "{[0]=false}")
|
||||||
|
assert(EncodeLua({[1]=123, [2]=456}) == "{123, 456}")
|
||||||
|
assert(EncodeLua({[1]=123, [3]=456}) == "{[1]=123, [3]=456}")
|
||||||
|
assert(EncodeLua({["hi"]=1, [1]=2}) == "{[1]=2, hi=1}")
|
||||||
|
assert(EncodeLua({[1]=2, ["hi"]=1}) == "{[1]=2, hi=1}")
|
||||||
|
assert(EncodeLua({[3]=3, [1]=3}) == "{[1]=3, [3]=3}")
|
||||||
|
assert(EncodeLua({[{[{[3]=2}]=2}]=2}) == "{[{[{[3]=2}]=2}]=2}")
|
||||||
|
assert(EncodeLua(" [\"new\nline\"] ") == "\" [\\\"new\\nline\\\"] \"")
|
||||||
|
assert(EncodeLua("hello") == [["hello"]])
|
||||||
|
assert(EncodeLua("\x00") == [["\x00"]])
|
||||||
|
assert(EncodeLua("→") == [["\xe2\x86\x92"]])
|
||||||
|
assert(EncodeLua("𐌰") == [["\xf0\x90\x8c\xb0"]])
|
||||||
|
assert(EncodeLua("\a") == [["\a"]])
|
||||||
|
assert(EncodeLua("\b") == [["\b"]])
|
||||||
|
assert(EncodeLua("\r") == [["\r"]])
|
||||||
|
assert(EncodeLua("\n") == [["\n"]])
|
||||||
|
assert(EncodeLua("\v") == [["\v"]])
|
||||||
|
assert(EncodeLua("\t") == [["\t"]])
|
||||||
|
assert(EncodeLua("\f") == [["\f"]])
|
||||||
|
assert(EncodeLua("\e") == [["\e"]])
|
||||||
|
assert(EncodeLua("\"") == [["\""]])
|
||||||
|
assert(EncodeLua("\\") == [["\\"]])
|
||||||
|
|
||||||
|
x = {}
|
||||||
|
x.c = 'c'
|
||||||
|
x.a = 'a'
|
||||||
|
x.b = 'b'
|
||||||
|
assert(EncodeLua(x) == '{a="a", b="b", c="c"}')
|
||||||
|
|
||||||
|
x = {2, 1}
|
||||||
|
x[3] = x
|
||||||
|
assert(string.match(EncodeLua(x), "{2, 1, \"cyclic@0x%x+\"}"))
|
||||||
|
|
||||||
|
-- 63 objects
|
||||||
|
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={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}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||||
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}) ==
|
||||||
|
"{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="..
|
||||||
|
"{k={k={k={k={k={k={k={k={k={k={k={k={k={k={k=0}}"..
|
||||||
|
"}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"..
|
||||||
|
"}}}}}}}}}}}}}")
|
||||||
|
|
||||||
|
-- 64 objects
|
||||||
|
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={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}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||||
|
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||||
|
) ==
|
||||||
|
"{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="..
|
||||||
|
"{k={k={k={k={k={k={k={k={k={k={k={k={k={k={k={k=\"greatdepth@0\"}}}"..
|
||||||
|
"}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"..
|
||||||
|
"}}}}}}}}}}}}}")
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- benchmark nanos ticks
|
||||||
|
-- LuaEncArray 455 1410
|
||||||
|
-- LuaEncUnsort 699 2165
|
||||||
|
-- LuaEncObject 1134 3513
|
||||||
|
|
||||||
|
function LuaEncArray()
|
||||||
|
EncodeLua({2, 1, 10, 3, "hello"})
|
||||||
|
end
|
||||||
|
|
||||||
|
function LuaEncObject()
|
||||||
|
EncodeLua({hi=2, 1, 10, 3, "hello"})
|
||||||
|
end
|
||||||
|
|
||||||
|
UNSORT = {sorted=false}
|
||||||
|
function LuaEncUnsort()
|
||||||
|
EncodeLua({hi=2, 1, 10, 3, "hello"}, UNSORT)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bench()
|
||||||
|
print("LuaEncArray", Benchmark(LuaEncArray))
|
||||||
|
print("LuaEncUnsort", Benchmark(LuaEncUnsort))
|
||||||
|
print("LuaEncObject", Benchmark(LuaEncObject))
|
||||||
|
end
|
|
@ -51,50 +51,6 @@ assert(EscapeParam("?hello&there<>") == "%3Fhello%26there%3C%3E")
|
||||||
assert(DecodeLatin1("hello\xff\xc0") == "helloÿÀ")
|
assert(DecodeLatin1("hello\xff\xc0") == "helloÿÀ")
|
||||||
assert(EncodeLatin1("helloÿÀ") == "hello\xff\xc0")
|
assert(EncodeLatin1("helloÿÀ") == "hello\xff\xc0")
|
||||||
|
|
||||||
assert(EncodeLua(nil) == "nil")
|
|
||||||
assert(EncodeLua(0) == "0")
|
|
||||||
assert(EncodeLua(3.14) == "3.14")
|
|
||||||
assert(EncodeLua({2, 1}) == "{2, 1}")
|
|
||||||
|
|
||||||
assert(EncodeJson(nil) == "null")
|
|
||||||
assert(EncodeJson(0) == "0")
|
|
||||||
assert(EncodeJson(3.14) == "3.14")
|
|
||||||
assert(EncodeJson({2, 1}) == "[2,1]")
|
|
||||||
|
|
||||||
assert(EncodeLua(" [\"new\nline\"] ") == "\" [\\\"new\\nline\\\"] \"")
|
|
||||||
|
|
||||||
-- EncodeLua() permits serialization of cyclic data structures
|
|
||||||
x = {2, 1}
|
|
||||||
x[3] = x
|
|
||||||
assert(string.match(EncodeLua(x), "{2, 1, \"cyclic@0x%x+\"}"))
|
|
||||||
|
|
||||||
-- EncodeLua() sorts table entries
|
|
||||||
x = {}
|
|
||||||
x.c = 'c'
|
|
||||||
x.a = 'a'
|
|
||||||
x.b = 'b'
|
|
||||||
assert(EncodeLua(x) == '{a="a", b="b", c="c"}')
|
|
||||||
|
|
||||||
-- EncodeJson() doesn't permit serialization of cyclic data structures
|
|
||||||
x = {2, 1}
|
|
||||||
x[3] = x
|
|
||||||
json, err = EncodeJson(x)
|
|
||||||
assert(not json)
|
|
||||||
assert(err == "won't serialize cyclic lua table")
|
|
||||||
|
|
||||||
-- pass the parser to itself lool
|
|
||||||
json, err = EncodeJson(EncodeJson)
|
|
||||||
assert(not json)
|
|
||||||
assert(err == "unsupported lua type")
|
|
||||||
|
|
||||||
-- EncodeJson() sorts table entries
|
|
||||||
-- JSON always requires quotes around key names
|
|
||||||
x = {}
|
|
||||||
x.c = 'c'
|
|
||||||
x.a = 'a'
|
|
||||||
x.b = 'b'
|
|
||||||
assert(EncodeJson(x) == '{"a":"a","b":"b","c":"c"}')
|
|
||||||
|
|
||||||
url = ParseUrl("https://jart:pass@redbean.dev/2.0.html?x&y=z#frag")
|
url = ParseUrl("https://jart:pass@redbean.dev/2.0.html?x&y=z#frag")
|
||||||
assert(url.scheme == "https")
|
assert(url.scheme == "https")
|
||||||
assert(url.user == "jart")
|
assert(url.user == "jart")
|
||||||
|
@ -168,17 +124,3 @@ assert(Uncompress(Compress("hello")) == "hello")
|
||||||
assert(Compress("hello") == "\x05\x86\xa6\x106x\x9c\xcbH\xcd\xc9\xc9\x07\x00\x06,\x02\x15")
|
assert(Compress("hello") == "\x05\x86\xa6\x106x\x9c\xcbH\xcd\xc9\xc9\x07\x00\x06,\x02\x15")
|
||||||
assert(Compress("hello", 0) == "\x05\x86\xa6\x106x\x01\x01\x05\x00\xfa\xffhello\x06,\x02\x15")
|
assert(Compress("hello", 0) == "\x05\x86\xa6\x106x\x01\x01\x05\x00\xfa\xffhello\x06,\x02\x15")
|
||||||
assert(Compress("hello", 0, true) == "x\x01\x01\x05\x00\xfa\xffhello\x06,\x02\x15")
|
assert(Compress("hello", 0, true) == "x\x01\x01\x05\x00\xfa\xffhello\x06,\x02\x15")
|
||||||
|
|
||||||
----------------------------------------------------------------------------------------------------
|
|
||||||
-- benchmarks
|
|
||||||
|
|
||||||
function LuaSerialization()
|
|
||||||
EncodeLua({2, 1, 10, 3, "hello"})
|
|
||||||
end
|
|
||||||
|
|
||||||
function JsonSerialization()
|
|
||||||
EncodeJson({2, 1, 10, 3, "hello"})
|
|
||||||
end
|
|
||||||
|
|
||||||
print("LuaSerialization", Benchmark(LuaSerialization))
|
|
||||||
print("JsonSerialization", Benchmark(JsonSerialization))
|
|
||||||
|
|
|
@ -115,8 +115,27 @@ res, err = DecodeJson('"\\ucjcc"')
|
||||||
assert(res == nil)
|
assert(res == nil)
|
||||||
assert(err == 'invalid unicode escape')
|
assert(err == 'invalid unicode escape')
|
||||||
|
|
||||||
res, err = DecodeJson('[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[')
|
-- 63 objects
|
||||||
assert(not res)
|
res, err = DecodeJson([[
|
||||||
|
{"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":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":
|
||||||
|
{"k":{"k":{"k":{"k":0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||||
|
}}}}}}}}}
|
||||||
|
]])
|
||||||
|
assert(res)
|
||||||
|
|
||||||
|
-- 64 objects
|
||||||
|
res, err = DecodeJson([[
|
||||||
|
{"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":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":{"k":
|
||||||
|
{"k":{"k":{"k":{"k":0}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
||||||
|
}}}}}}}}}}
|
||||||
|
]])
|
||||||
|
assert(res == nil)
|
||||||
assert(err == "maximum depth exceeded")
|
assert(err == "maximum depth exceeded")
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -126,9 +145,11 @@ assert(err == "maximum depth exceeded")
|
||||||
-- JsonParseInteger 66 206
|
-- JsonParseInteger 66 206
|
||||||
-- JsonParseDouble 123 383
|
-- JsonParseDouble 123 383
|
||||||
-- JsonParseString 116 361
|
-- JsonParseString 116 361
|
||||||
-- JsonParseArray 256 793
|
-- JsonParseInts 256 793
|
||||||
|
-- JsonParseFloats 361 1118
|
||||||
-- JsonParseObject 890 2756
|
-- JsonParseObject 890 2756
|
||||||
-- JsonEncodeArray 639 1979
|
-- JsonEncodeInts 498 1543
|
||||||
|
-- JsonEncodeFloats 498 1543
|
||||||
-- JsonEncodeObject 1333 4129
|
-- JsonEncodeObject 1333 4129
|
||||||
|
|
||||||
function JsonParseEmpty()
|
function JsonParseEmpty()
|
||||||
|
@ -147,27 +168,39 @@ function JsonParseString()
|
||||||
DecodeJson[[ "\ud800\udf30 he𐌰𐌰o \ud800\udf30" ]]
|
DecodeJson[[ "\ud800\udf30 he𐌰𐌰o \ud800\udf30" ]]
|
||||||
end
|
end
|
||||||
|
|
||||||
function JsonParseArray()
|
function JsonParseInts()
|
||||||
DecodeJson[[ [123,456,789] ]]
|
DecodeJson[[ [123,456,789] ]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function JsonParseFloats()
|
||||||
|
DecodeJson[[ [3.14,1.23,7.89] ]]
|
||||||
|
end
|
||||||
|
|
||||||
function JsonParseObject()
|
function JsonParseObject()
|
||||||
DecodeJson[[ {"3": "1", "4": "1", "5": {"3":"1", "4":"1", "5":"9"}} ]]
|
DecodeJson[[ {"3": "1", "4": "1", "5": {"3":"1", "4":"1", "5":"9"}} ]]
|
||||||
end
|
end
|
||||||
|
|
||||||
function JsonEncodeArray()
|
function JsonEncodeInts()
|
||||||
EncodeJson({2, 0, {5, 7, 3}})
|
EncodeJson({2, 0, {5, 7, 3}})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function JsonEncodeFloats()
|
||||||
|
EncodeJson({3.14,1.23,7.89})
|
||||||
|
end
|
||||||
|
|
||||||
function JsonEncodeObject()
|
function JsonEncodeObject()
|
||||||
EncodeJson({["3"]="1", ["4"]="1", ["5"]={["3"]="1", ["4"]="1", ["5"]="9"}})
|
EncodeJson({["3"]="1", ["4"]="1", ["5"]={["3"]="1", ["4"]="1", ["5"]="9"}})
|
||||||
end
|
end
|
||||||
|
|
||||||
-- print('JsonParseEmpty', Benchmark(JsonParseEmpty))
|
if nil then
|
||||||
-- print('JsonParseInteg', Benchmark(JsonParseInteger))
|
print('JsonParseEmpty', Benchmark(JsonParseEmpty))
|
||||||
-- print('JsonParseDouble', Benchmark(JsonParseDouble))
|
print('JsonParseInteg', Benchmark(JsonParseInteger))
|
||||||
-- print('JsonParseString', Benchmark(JsonParseString))
|
print('JsonParseDouble', Benchmark(JsonParseDouble))
|
||||||
-- print('JsonParseArray', Benchmark(JsonParseArray))
|
print('JsonParseString', Benchmark(JsonParseString))
|
||||||
-- print('JsonParseObject', Benchmark(JsonParseObject))
|
print('JsonParseInts', Benchmark(JsonParseInts))
|
||||||
-- print('JsonEncodeArr', Benchmark(JsonEncodeArray))
|
print('JsonParseFloats', Benchmark(JsonParseFloats))
|
||||||
-- print('JsonEncodeObj', Benchmark(JsonEncodeObject))
|
print('JsonParseObject', Benchmark(JsonParseObject))
|
||||||
|
print('JsonEncodeInts', Benchmark(JsonEncodeInts))
|
||||||
|
print('JsonEncodeFlts', Benchmark(JsonEncodeFloats))
|
||||||
|
print('JsonEncodeObj', Benchmark(JsonEncodeObject))
|
||||||
|
end
|
||||||
|
|
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;
|
char *LuaFormatStack(lua_State *) dontdiscard;
|
||||||
int LuaCallWithTrace(lua_State *, int, int, lua_State *);
|
int LuaCallWithTrace(lua_State *, int, int, lua_State *);
|
||||||
int LuaEncodeJsonData(lua_State *, char **, char *, int);
|
int LuaEncodeJsonData(lua_State *, char **, int, bool);
|
||||||
int LuaEncodeLuaData(lua_State *, char **, char *, int);
|
int LuaEncodeLuaData(lua_State *, char **, int, bool);
|
||||||
int LuaEncodeUrl(lua_State *);
|
int LuaEncodeUrl(lua_State *);
|
||||||
int LuaParseUrl(lua_State *);
|
int LuaParseUrl(lua_State *);
|
||||||
int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int);
|
int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int);
|
||||||
int LuaPushHeaders(lua_State *, struct HttpMessage *, const char *);
|
int LuaPushHeaders(lua_State *, struct HttpMessage *, const char *);
|
||||||
int EscapeLuaString(char *, size_t, char **);
|
|
||||||
void LuaPrintStack(lua_State *);
|
void LuaPrintStack(lua_State *);
|
||||||
void LuaPushLatin1(lua_State *, const char *, size_t);
|
void LuaPushLatin1(lua_State *, const char *, size_t);
|
||||||
void LuaPushUrlParams(lua_State *, struct UrlParams *);
|
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/visitor.h
|
||||||
|
|
||||||
THIRD_PARTY_LUA_A_SRCS = \
|
THIRD_PARTY_LUA_A_SRCS = \
|
||||||
third_party/lua/escapeluastring.c \
|
|
||||||
third_party/lua/lapi.c \
|
third_party/lua/lapi.c \
|
||||||
third_party/lua/lauxlib.c \
|
third_party/lua/lauxlib.c \
|
||||||
third_party/lua/lbaselib.c \
|
third_party/lua/lbaselib.c \
|
||||||
|
|
282
third_party/lua/luaencodejsondata.c
vendored
282
third_party/lua/luaencodejsondata.c
vendored
|
@ -34,139 +34,233 @@
|
||||||
#include "third_party/lua/lua.h"
|
#include "third_party/lua/lua.h"
|
||||||
#include "third_party/lua/visitor.h"
|
#include "third_party/lua/visitor.h"
|
||||||
|
|
||||||
static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
|
struct Serializer {
|
||||||
char *numformat, int idx,
|
struct LuaVisited visited;
|
||||||
struct LuaVisited *visited,
|
const char *reason;
|
||||||
const char **reason) {
|
char *strbuf;
|
||||||
char *s;
|
size_t strbuflen;
|
||||||
int sli, rc;
|
bool sorted;
|
||||||
bool isarray;
|
};
|
||||||
char ibuf[128];
|
|
||||||
size_t tbllen, i, z;
|
|
||||||
struct StrList sl = {0};
|
|
||||||
if (level > 0) {
|
|
||||||
switch (lua_type(L, idx)) {
|
|
||||||
|
|
||||||
case LUA_TNIL:
|
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_ON_ERROR(appendw(buf, READ32LE("null")));
|
||||||
return 0;
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TBOOLEAN:
|
static int SerializeBoolean(lua_State *L, char **buf, int idx) {
|
||||||
RETURN_ON_ERROR(appendw(buf, lua_toboolean(L, idx)
|
RETURN_ON_ERROR(appendw(
|
||||||
? READ32LE("true")
|
buf, lua_toboolean(L, idx) ? READ32LE("true") : READ64LE("false\0\0")));
|
||||||
: READ64LE("false\0\0")));
|
|
||||||
return 0;
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TSTRING:
|
static int SerializeNumber(lua_State *L, char **buf, int idx) {
|
||||||
s = lua_tolstring(L, idx, &z);
|
char ibuf[128];
|
||||||
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)) {
|
if (lua_isinteger(L, idx)) {
|
||||||
RETURN_ON_ERROR(appendd(
|
RETURN_ON_ERROR(appendd(
|
||||||
buf, ibuf, FormatInt64(ibuf, luaL_checkinteger(L, idx)) - ibuf));
|
buf, ibuf, FormatInt64(ibuf, luaL_checkinteger(L, idx)) - ibuf));
|
||||||
} else {
|
} else {
|
||||||
RETURN_ON_ERROR(
|
RETURN_ON_ERROR(appends(buf, DoubleToJson(ibuf, lua_tonumber(L, idx))));
|
||||||
appends(buf, DoubleToJson(ibuf, lua_tonumber(L, idx))));
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TTABLE:
|
static int SerializeString(lua_State *L, char **buf, int idx,
|
||||||
RETURN_ON_ERROR(rc = LuaPushVisit(visited, lua_topointer(L, idx)));
|
struct Serializer *z) {
|
||||||
if (!rc) {
|
char *s;
|
||||||
// create nearby reference to table at idx
|
size_t m;
|
||||||
lua_pushvalue(L, idx);
|
s = lua_tolstring(L, idx, &m);
|
||||||
|
if (!(s = EscapeJsStringLiteral(&z->strbuf, &z->strbuflen, s, m, &m))) {
|
||||||
// fast way to tell if table is an array or object
|
goto OnError;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||||
|
RETURN_ON_ERROR(appendd(buf, s, m));
|
||||||
|
RETURN_ON_ERROR(appendw(buf, '"'));
|
||||||
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// now serialize the table
|
static int SerializeArray(lua_State *L, char **buf, struct Serializer *z,
|
||||||
if (isarray) {
|
int level, size_t tbllen) {
|
||||||
|
size_t i;
|
||||||
|
RETURN_ON_ERROR(appendw(buf, '['));
|
||||||
for (i = 1; i <= tbllen; i++) {
|
for (i = 1; i <= tbllen; i++) {
|
||||||
RETURN_ON_ERROR(sli = AppendStrList(&sl));
|
lua_rawgeti(L, -1, i);
|
||||||
lua_rawgeti(L, -1, i); // table/-2, value/-1
|
if (i > 1) RETURN_ON_ERROR(appendw(buf, ','));
|
||||||
RETURN_ON_ERROR(LuaEncodeJsonDataImpl(
|
RETURN_ON_ERROR(Serialize(L, buf, -1, z, level - 1));
|
||||||
L, &sl.p[sli], level - 1, numformat, -1, visited, reason));
|
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
}
|
}
|
||||||
} else {
|
RETURN_ON_ERROR(appendw(buf, ']'));
|
||||||
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;
|
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 {
|
} else {
|
||||||
*reason = "won't serialize cyclic lua table";
|
comma = true;
|
||||||
goto OnError;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
*reason = "unsupported lua type";
|
|
||||||
goto OnError;
|
|
||||||
}
|
}
|
||||||
|
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 {
|
} else {
|
||||||
*reason = "table has great depth";
|
z->reason = "json objects must only use string keys";
|
||||||
goto OnError;
|
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:
|
OnError:
|
||||||
FreeStrList(&sl);
|
FreeStrList(&sl);
|
||||||
return -1;
|
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.
|
* 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 L is Lua interpreter state
|
||||||
* @param buf receives encoded output string
|
* @param buf receives encoded output string
|
||||||
* @param numformat controls double formatting
|
|
||||||
* @param idx is index of item on Lua stack
|
* @param idx is index of item on Lua stack
|
||||||
* @return 0 on success, or -1 on error
|
* @return 0 on success, or -1 on error
|
||||||
*/
|
*/
|
||||||
int LuaEncodeJsonData(lua_State *L, char **buf, char *numformat, int idx) {
|
int LuaEncodeJsonData(lua_State *L, char **buf, int idx, bool sorted) {
|
||||||
int rc;
|
int rc, depth = 64;
|
||||||
struct LuaVisited visited = {0};
|
struct Serializer z = {.reason = "out of memory", .sorted = sorted};
|
||||||
const char *reason = "out of memory";
|
if (lua_checkstack(L, depth * 4)) {
|
||||||
rc = LuaEncodeJsonDataImpl(L, buf, 64, numformat, idx, &visited, &reason);
|
rc = Serialize(L, buf, idx, &z, depth);
|
||||||
free(visited.p);
|
free(z.visited.p);
|
||||||
|
free(z.strbuf);
|
||||||
if (rc == -1) {
|
if (rc == -1) {
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
lua_pushstring(L, reason);
|
lua_pushstring(L, z.reason);
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
|
} else {
|
||||||
|
luaL_error(L, "can't set stack depth");
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
399
third_party/lua/luaencodeluadata.c
vendored
399
third_party/lua/luaencodeluadata.c
vendored
|
@ -32,6 +32,14 @@
|
||||||
#include "third_party/lua/lua.h"
|
#include "third_party/lua/lua.h"
|
||||||
#include "third_party/lua/visitor.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) {
|
static bool IsLuaIdentifier(lua_State *L, int idx) {
|
||||||
size_t i, n;
|
size_t i, n;
|
||||||
const char *p;
|
const char *p;
|
||||||
|
@ -43,12 +51,37 @@ static bool IsLuaIdentifier(lua_State *L, int idx) {
|
||||||
return true;
|
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) {
|
static bool IsLuaArray(lua_State *L) {
|
||||||
int i;
|
lua_Integer i;
|
||||||
|
lua_Unsigned n;
|
||||||
|
n = lua_rawlen(L, -1);
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
for (i = 1; lua_next(L, -2); ++i) {
|
while (lua_next(L, -2)) {
|
||||||
if (!lua_isinteger(L, -2) || lua_tointeger(L, -2) != i) {
|
if (!lua_isinteger(L, -2) || (i = lua_tointeger(L, -2)) < 1 || i > n) {
|
||||||
lua_pop(L, 2);
|
lua_pop(L, 2);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -57,62 +90,149 @@ static bool IsLuaArray(lua_State *L) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int LuaEncodeLuaOpaqueData(lua_State *L, char **buf, int idx,
|
static int SerializeNil(lua_State *L, char **buf) {
|
||||||
const char *kind) {
|
|
||||||
if (appendf(buf, "\"%s@%p\"", kind, lua_topointer(L, idx)) != -1) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
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)) {
|
|
||||||
|
|
||||||
case LUA_TNIL:
|
|
||||||
RETURN_ON_ERROR(appendw(buf, READ32LE("nil")));
|
RETURN_ON_ERROR(appendw(buf, READ32LE("nil")));
|
||||||
return 0;
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TSTRING:
|
static int SerializeBoolean(lua_State *L, char **buf, int idx) {
|
||||||
s = lua_tolstring(L, idx, &slen);
|
RETURN_ON_ERROR(appendw(
|
||||||
RETURN_ON_ERROR(EscapeLuaString(s, slen, buf));
|
buf, lua_toboolean(L, idx) ? READ32LE("true") : READ64LE("false\0\0")));
|
||||||
return 0;
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TFUNCTION:
|
static int SerializeOpaque(lua_State *L, char **buf, int idx,
|
||||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "func");
|
const char *kind) {
|
||||||
|
RETURN_ON_ERROR(appendf(buf, "\"%s@%p\"", kind, lua_topointer(L, idx)));
|
||||||
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TLIGHTUSERDATA:
|
static int SerializeNumber(lua_State *L, char **buf, int idx) {
|
||||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "light");
|
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_ON_ERROR(appends(buf, DoubleToLua(ibuf, lua_tonumber(L, idx))));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TTHREAD:
|
#if 0
|
||||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "thread");
|
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
|
||||||
|
|
||||||
case LUA_TUSERDATA:
|
// 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 (luaL_callmeta(L, idx, "__repr")) {
|
||||||
if (lua_type(L, -1) == LUA_TSTRING) {
|
if (lua_type(L, -1) == LUA_TSTRING) {
|
||||||
s = lua_tolstring(L, -1, &slen);
|
s = lua_tolstring(L, -1, &n);
|
||||||
RETURN_ON_ERROR(appendd(buf, s, slen));
|
RETURN_ON_ERROR(appendd(buf, s, n));
|
||||||
} else {
|
} else {
|
||||||
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]",
|
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]", "__repr",
|
||||||
"__repr", luaL_typename(L, -1)));
|
luaL_typename(L, -1)));
|
||||||
}
|
}
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (luaL_callmeta(L, idx, "__tostring")) {
|
if (luaL_callmeta(L, idx, "__tostring")) {
|
||||||
if (lua_type(L, -1) == LUA_TSTRING) {
|
if (lua_type(L, -1) == LUA_TSTRING) {
|
||||||
s = lua_tolstring(L, -1, &slen);
|
RETURN_ON_ERROR(SerializeString(L, buf, -1));
|
||||||
RETURN_ON_ERROR(EscapeLuaString(s, slen, buf));
|
|
||||||
} else {
|
} else {
|
||||||
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]",
|
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]",
|
||||||
"__tostring", luaL_typename(L, -1)));
|
"__tostring", luaL_typename(L, -1)));
|
||||||
|
@ -120,77 +240,147 @@ static int LuaEncodeLuaDataImpl(lua_State *L, char **buf, int level,
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "udata");
|
return SerializeOpaque(L, buf, idx, "udata");
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TNUMBER:
|
static int SerializeArray(lua_State *L, char **buf, struct Serializer *z,
|
||||||
if (lua_isinteger(L, idx)) {
|
int depth) {
|
||||||
RETURN_ON_ERROR(
|
size_t i, n;
|
||||||
appendd(buf, ibuf,
|
const char *s;
|
||||||
FormatFlex64(ibuf, luaL_checkinteger(L, idx), 2) - ibuf));
|
RETURN_ON_ERROR(appendw(buf, '{'));
|
||||||
} else {
|
n = lua_rawlen(L, -1);
|
||||||
RETURN_ON_ERROR(
|
for (i = 1; i <= n; i++) {
|
||||||
appends(buf, DoubleToLua(ibuf, lua_tonumber(L, idx))));
|
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;
|
return 0;
|
||||||
|
OnError:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
case LUA_TBOOLEAN:
|
static int SerializeObject(lua_State *L, char **buf, struct Serializer *z,
|
||||||
RETURN_ON_ERROR(appendw(buf, lua_toboolean(L, idx)
|
int depth) {
|
||||||
? READ32LE("true")
|
int rc;
|
||||||
: READ64LE("false\0\0")));
|
size_t n;
|
||||||
return 0;
|
const char *s;
|
||||||
|
bool comma = false;
|
||||||
case LUA_TTABLE:
|
RETURN_ON_ERROR(appendw(buf, '{'));
|
||||||
i = 0;
|
lua_pushnil(L);
|
||||||
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)) {
|
while (lua_next(L, -2)) {
|
||||||
ktype = lua_type(L, -2);
|
if (comma) {
|
||||||
vtype = lua_type(L, -1);
|
RETURN_ON_ERROR(appendw(buf, READ16LE(", ")));
|
||||||
RETURN_ON_ERROR(sli = AppendStrList(&sl));
|
} else {
|
||||||
if (isarray) {
|
comma = true;
|
||||||
// use {v₁′,v₂′,...} for lua-normal integer keys
|
}
|
||||||
} else if (ktype == LUA_TSTRING && IsLuaIdentifier(L, -2)) {
|
if (lua_type(L, -2) == LUA_TSTRING && IsLuaIdentifier(L, -2)) {
|
||||||
// use {𝑘=𝑣′} syntax when 𝑘 is legal as a lua identifier
|
// use {𝑘=𝑣′} syntax when 𝑘 is a legal lua identifier
|
||||||
s = lua_tolstring(L, -2, &slen);
|
s = lua_tolstring(L, -2, &n);
|
||||||
RETURN_ON_ERROR(appendd(&sl.p[sli], s, slen));
|
RETURN_ON_ERROR(appendd(buf, s, n));
|
||||||
RETURN_ON_ERROR(appendw(&sl.p[sli], '='));
|
RETURN_ON_ERROR(appendw(buf, '='));
|
||||||
} else {
|
} else {
|
||||||
// use {[𝑘′]=𝑣′} otherwise
|
// use {[𝑘′]=𝑣′} otherwise
|
||||||
RETURN_ON_ERROR(appendw(&sl.p[sli], '['));
|
RETURN_ON_ERROR(appendw(buf, '['));
|
||||||
RETURN_ON_ERROR(LuaEncodeLuaDataImpl(L, &sl.p[sli], level - 1,
|
RETURN_ON_ERROR(Serialize(L, buf, -2, z, depth - 1));
|
||||||
numformat, -2, visited));
|
RETURN_ON_ERROR(appendw(buf, READ16LE("]=")));
|
||||||
RETURN_ON_ERROR(appendw(&sl.p[sli], ']' | '=' << 010));
|
|
||||||
}
|
}
|
||||||
RETURN_ON_ERROR(LuaEncodeLuaDataImpl(L, &sl.p[sli], level - 1,
|
RETURN_ON_ERROR(Serialize(L, buf, -1, z, depth - 1));
|
||||||
numformat, -1, visited));
|
lua_pop(L, 1);
|
||||||
lua_pop(L, 1); // table/-2, key/-1
|
|
||||||
}
|
}
|
||||||
lua_pop(L, 1); // table ref
|
RETURN_ON_ERROR(appendw(buf, '}'));
|
||||||
if (!isarray) SortStrList(&sl);
|
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(appendw(buf, '{'));
|
||||||
RETURN_ON_ERROR(JoinStrList(&sl, buf, READ16LE(", ")));
|
RETURN_ON_ERROR(JoinStrList(&sl, buf, READ16LE(", ")));
|
||||||
RETURN_ON_ERROR(appendw(buf, '}'));
|
RETURN_ON_ERROR(appendw(buf, '}'));
|
||||||
FreeStrList(&sl);
|
FreeStrList(&sl);
|
||||||
LuaPopVisit(visited);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
|
||||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "cyclic");
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "unsupported");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return LuaEncodeLuaOpaqueData(L, buf, idx, "greatdepth");
|
|
||||||
}
|
|
||||||
OnError:
|
OnError:
|
||||||
FreeStrList(&sl);
|
FreeStrList(&sl);
|
||||||
return -1;
|
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.
|
* Encodes Lua data structure as Lua code string.
|
||||||
*
|
*
|
||||||
|
@ -203,18 +393,23 @@ OnError:
|
||||||
*
|
*
|
||||||
* @param L is Lua interpreter state
|
* @param L is Lua interpreter state
|
||||||
* @param buf receives encoded output string
|
* @param buf receives encoded output string
|
||||||
* @param numformat controls double formatting
|
|
||||||
* @param idx is index of item on Lua stack
|
* @param idx is index of item on Lua stack
|
||||||
|
* @param sorted is ignored (always sorted)
|
||||||
* @return 0 on success, or -1 on error
|
* @return 0 on success, or -1 on error
|
||||||
*/
|
*/
|
||||||
int LuaEncodeLuaData(lua_State *L, char **buf, char *numformat, int idx) {
|
int LuaEncodeLuaData(lua_State *L, char **buf, int idx, bool sorted) {
|
||||||
int rc;
|
int rc, depth = 64;
|
||||||
struct LuaVisited visited = {0};
|
struct Serializer z = {.reason = "out of memory", .sorted = sorted};
|
||||||
rc = LuaEncodeLuaDataImpl(L, buf, 64, numformat, idx, &visited);
|
if (lua_checkstack(L, depth * 4)) {
|
||||||
free(visited.p);
|
rc = Serialize(L, buf, idx, &z, depth);
|
||||||
|
free(z.visited.p);
|
||||||
if (rc == -1) {
|
if (rc == -1) {
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
lua_pushstring(L, "out of memory");
|
lua_pushstring(L, z.reason);
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
|
} else {
|
||||||
|
luaL_error(L, "can't set stack depth");
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
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++) {
|
for (i = 1; i <= top; i++) {
|
||||||
if (i > 1) appendw(&b, '\n');
|
if (i > 1) appendw(&b, '\n');
|
||||||
appendf(&b, "\t%d\t%s\t", i, luaL_typename(L, i));
|
appendf(&b, "\t%d\t%s\t", i, luaL_typename(L, i));
|
||||||
LuaEncodeLuaData(L, &b, "g", i);
|
LuaEncodeLuaData(L, &b, i, true);
|
||||||
}
|
}
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
|
@ -735,20 +735,52 @@ FUNCTIONS
|
||||||
the original ordering of fields. As such, they'll be sorted
|
the original ordering of fields. As such, they'll be sorted
|
||||||
by EncodeJson() and may not round-trip with original intent
|
by EncodeJson() and may not round-trip with original intent
|
||||||
|
|
||||||
EncodeJson(value[,options:table])
|
EncodeJson(value[, options:table])
|
||||||
├─→ json:str
|
├─→ json:str
|
||||||
├─→ true [if useoutput]
|
├─→ true [if useoutput]
|
||||||
└─→ nil, error:str
|
└─→ nil, error:str
|
||||||
|
|
||||||
Turns Lua data structure into a JSON string.
|
Turns Lua data structure into JSON string.
|
||||||
|
|
||||||
Tables with non-zero length (as reported by `#`) are encoded
|
Since Lua uses tables for both hashmaps and arrays, we use a
|
||||||
as arrays and any non-array elements are ignored. Empty tables
|
simple fast algorithm for telling the two apart. Tables with
|
||||||
are encoded as `{}` with the exception of the special empty
|
non-zero length (as reported by `#`) are encoded as arrays,
|
||||||
table `{[0]=false}` shall be encoded as `[]`. Arrays elements
|
and any non-array elements are ignored. For example:
|
||||||
are serialized in specified order. Object entries are sorted
|
|
||||||
ASCIIbetically using strcmp() on their string keys to ensure
|
>: EncodeJson({2})
|
||||||
deterministic order.
|
"[2]"
|
||||||
|
>: EncodeJson({[1]=2, ["hi"]=1})
|
||||||
|
"[2]"
|
||||||
|
|
||||||
|
If there are holes in your array, then the serialized array
|
||||||
|
will exclude everything after the first hole. If the beginning
|
||||||
|
of your array is a hole, then an error is returned.
|
||||||
|
|
||||||
|
>: EncodeJson({[1]=1, [3]=3})
|
||||||
|
"[1]"
|
||||||
|
>: EncodeJson({[2]=1, [3]=3})
|
||||||
|
"[]"
|
||||||
|
>: EncodeJson({[2]=1, [3]=3})
|
||||||
|
nil "json objects must only use string keys"
|
||||||
|
|
||||||
|
If the raw length of a table is reported as zero, then we
|
||||||
|
check for the magic element `[0]=false`. If it's present, then
|
||||||
|
your table will be serialized as empty array `[]`. That entry
|
||||||
|
inserted by DecodeJson() automatically, only when encountering
|
||||||
|
empty arrays, and it's necessary in order to make empty arrays
|
||||||
|
round-trip. If raw length is zero and `[0]=false` is absent,
|
||||||
|
then your table will be serialized as an iterated object.
|
||||||
|
|
||||||
|
>: EncodeJson({})
|
||||||
|
"{}"
|
||||||
|
>: EncodeJson({[0]=false})
|
||||||
|
"[]"
|
||||||
|
>: EncodeJson({["hi"]=1})
|
||||||
|
"{\"hi\":1}"
|
||||||
|
>: EncodeJson({["hi"]=1, [0]=false})
|
||||||
|
"[]"
|
||||||
|
>: EncodeJson({["hi"]=1, [7]=false})
|
||||||
|
nil "json objects must only use string keys"
|
||||||
|
|
||||||
The following options may be used:
|
The following options may be used:
|
||||||
|
|
||||||
|
@ -756,38 +788,72 @@ FUNCTIONS
|
||||||
output buffer and returns `nil` value. This option is
|
output buffer and returns `nil` value. This option is
|
||||||
ignored if used outside of request handling code.
|
ignored if used outside of request handling code.
|
||||||
|
|
||||||
This function will fail if:
|
- sorted: (bool=true) Lua uses hash tables so the order of
|
||||||
|
object keys is lost in a Lua table. So, by default, we use
|
||||||
|
`qsort(strcmp)` to impose a deterministic output order. If
|
||||||
|
you don't care about ordering then setting `sorted=false`
|
||||||
|
should yield a 1.6x performance boost in serialization.
|
||||||
|
|
||||||
|
This function will return an error if:
|
||||||
|
|
||||||
- `value` is cyclic
|
- `value` is cyclic
|
||||||
- `value` has depth greater than 64
|
- `value` has depth greater than 64
|
||||||
- `value` contains functions, user data, or threads
|
- `value` contains functions, user data, or threads
|
||||||
- `value` is table that blends string / non-string keys
|
- `value` is table that blends string / non-string keys
|
||||||
|
- Your serializer runs out of C heap memory (setrlimit)
|
||||||
|
|
||||||
When arrays and objects are serialized, entries will be sorted
|
We assume strings in `value` contain UTF-8. This serializer
|
||||||
in a deterministic order.
|
currently does not produce UTF-8 output. The output format is
|
||||||
|
right now ASCII. Your UTF-8 data will be safely transcoded to
|
||||||
|
\uXXXX sequences which are UTF-16. Overlong encodings in your
|
||||||
|
input strings will be canonicalized rather than validated.
|
||||||
|
|
||||||
This parser does not support UTF-8
|
NaNs are serialized as `null` and Infinities are `null` which
|
||||||
|
is consistent with the v8 behavior.
|
||||||
|
|
||||||
EncodeLua(value[,options:table])
|
EncodeLua(value[, options:table])
|
||||||
├─→ luacode:str
|
├─→ luacode:str
|
||||||
├─→ true [if useoutput]
|
├─→ true [if useoutput]
|
||||||
└─→ nil, error:str
|
└─→ nil, error:str
|
||||||
|
|
||||||
Turns Lua data structure into Lua code string.
|
Turns Lua data structure into Lua code string.
|
||||||
|
|
||||||
|
Since Lua uses tables as both hashmaps and arrays, tables will
|
||||||
|
only be serialized as an array with determinate order, if it's
|
||||||
|
an array in the strictest possible sense.
|
||||||
|
|
||||||
|
1. for all 𝑘=𝑣 in table, 𝑘 is an integer ≥1
|
||||||
|
2. no holes exist between MIN(𝑘) and MAX(𝑘)
|
||||||
|
3. if non-empty, MIN(𝑘) is 1
|
||||||
|
|
||||||
|
In all other cases, your table will be serialized as an object
|
||||||
|
which is iterated and displayed as a list of (possibly) sorted
|
||||||
|
entries that have equal signs.
|
||||||
|
|
||||||
|
>: EncodeLua({3, 2})
|
||||||
|
"{3, 2}"
|
||||||
|
>: EncodeLua({[1]=3, [2]=3})
|
||||||
|
"{3, 2}"
|
||||||
|
>: EncodeLua({[1]=3, [3]=3})
|
||||||
|
"{[1]=3, [3]=3}"
|
||||||
|
>: EncodeLua({["hi"]=1, [1]=2})
|
||||||
|
"{[1]=2, hi=1}"
|
||||||
|
|
||||||
The following options may be used:
|
The following options may be used:
|
||||||
|
|
||||||
- useoutput: (bool=false) encodes the result directly to the
|
- useoutput: (bool=false) encodes the result directly to the
|
||||||
output buffer and returns `nil` value. This option is
|
output buffer and returns `nil` value. This option is
|
||||||
ignored if used outside of request handling code.
|
ignored if used outside of request handling code.
|
||||||
|
|
||||||
|
- sorted: (bool=true) Lua uses hash tables so the order of
|
||||||
|
object keys is lost in a Lua table. So, by default, we use
|
||||||
|
`qsort(strcmp)` to impose a deterministic output order. If
|
||||||
|
you don't care about ordering then setting `sorted=false`
|
||||||
|
should yield a 2x performance boost in serialization.
|
||||||
|
|
||||||
If a user data object has a `__repr` or `__tostring` meta
|
If a user data object has a `__repr` or `__tostring` meta
|
||||||
method, then that'll be used to encode the Lua code.
|
method, then that'll be used to encode the Lua code.
|
||||||
|
|
||||||
When tables are serialized, entries will be sorted in a
|
|
||||||
deterministic order. This makes `EncodeLua` a great fit for
|
|
||||||
writing unit tests, when tables contain regular normal data.
|
|
||||||
|
|
||||||
This serializer is designed primarily to describe data. For
|
This serializer is designed primarily to describe data. For
|
||||||
example, it's used by the REPL where we need to be able to
|
example, it's used by the REPL where we need to be able to
|
||||||
ignore errors when displaying data structures, since showing
|
ignore errors when displaying data structures, since showing
|
||||||
|
@ -802,10 +868,32 @@ FUNCTIONS
|
||||||
tables; however instead of failing, it embeds a string of
|
tables; however instead of failing, it embeds a string of
|
||||||
unspecified layout describing the cycle.
|
unspecified layout describing the cycle.
|
||||||
|
|
||||||
|
Integer literals are encoded as decimal. However if the int64
|
||||||
|
number is ≥256 and has a population count of 1 then we switch
|
||||||
|
to representating the number in hexadecimal, for readability.
|
||||||
|
Hex numbers have leading zeroes added in order to visualize
|
||||||
|
whether the number fits in a uint16, uint32, or int64. Also
|
||||||
|
some numbers can only be encoded expressionally. For example,
|
||||||
|
NaNs are serialized as `0/0`, and Infinity is `math.huge`.
|
||||||
|
|
||||||
|
>: 7000
|
||||||
|
7000
|
||||||
|
>: 0x100
|
||||||
|
0x0100
|
||||||
|
>: 0x10000
|
||||||
|
0x00010000
|
||||||
|
>: 0x100000000
|
||||||
|
0x0000000100000000
|
||||||
|
>: 0/0
|
||||||
|
0/0
|
||||||
|
>: 1.5e+9999
|
||||||
|
math.huge
|
||||||
|
>: -9223372036854775807 - 1
|
||||||
|
-9223372036854775807 - 1
|
||||||
|
|
||||||
The only failure return condition currently implemented is
|
The only failure return condition currently implemented is
|
||||||
when C runs out of heap memory.
|
when C runs out of heap memory.
|
||||||
|
|
||||||
|
|
||||||
EncodeLatin1(utf-8:str[,flags:int]) → iso-8859-1:str
|
EncodeLatin1(utf-8:str[,flags:int]) → iso-8859-1:str
|
||||||
Turns UTF-8 into ISO-8859-1 string.
|
Turns UTF-8 into ISO-8859-1 string.
|
||||||
|
|
||||||
|
|
|
@ -511,9 +511,13 @@ static dontinline int LuaCoderImpl(lua_State *L,
|
||||||
void *p;
|
void *p;
|
||||||
size_t n;
|
size_t n;
|
||||||
p = luaL_checklstring(L, 1, &n);
|
p = luaL_checklstring(L, 1, &n);
|
||||||
p = C(p, n, &n);
|
if ((p = C(p, n, &n))) {
|
||||||
lua_pushlstring(L, p, n);
|
lua_pushlstring(L, p, n);
|
||||||
free(p);
|
free(p);
|
||||||
|
} else {
|
||||||
|
luaL_error(L, "out of memory");
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,7 +579,17 @@ int LuaEscapeFragment(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int LuaEscapeLiteral(lua_State *L) {
|
int LuaEscapeLiteral(lua_State *L) {
|
||||||
return LuaCoder(L, EscapeJsStringLiteral);
|
char *p, *q = 0;
|
||||||
|
size_t n, y = 0;
|
||||||
|
p = luaL_checklstring(L, 1, &n);
|
||||||
|
if ((p = EscapeJsStringLiteral(&q, &y, p, n, &n))) {
|
||||||
|
lua_pushlstring(L, p, n);
|
||||||
|
free(q);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
luaL_error(L, "out of memory");
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int LuaVisualizeControlCodes(lua_State *L) {
|
int LuaVisualizeControlCodes(lua_State *L) {
|
||||||
|
|
|
@ -36,8 +36,6 @@
|
||||||
#define OBJECT_KEY 2
|
#define OBJECT_KEY 2
|
||||||
#define OBJECT_VAL 3
|
#define OBJECT_VAL 3
|
||||||
|
|
||||||
#define MAX_JSON_DEPTH 128
|
|
||||||
|
|
||||||
static struct DecodeJson Parse(struct lua_State *L, const char *p,
|
static struct DecodeJson Parse(struct lua_State *L, const char *p,
|
||||||
const char *e, int context, int depth) {
|
const char *e, int context, int depth) {
|
||||||
long x;
|
long x;
|
||||||
|
@ -47,7 +45,7 @@ static struct DecodeJson Parse(struct lua_State *L, const char *p,
|
||||||
const char *reason;
|
const char *reason;
|
||||||
struct DecodeJson r;
|
struct DecodeJson r;
|
||||||
int A, B, C, D, c, d, i, u;
|
int A, B, C, D, c, d, i, u;
|
||||||
if (UNLIKELY(!--depth)) {
|
if (UNLIKELY(!depth)) {
|
||||||
return (struct DecodeJson){-1, "maximum depth exceeded"};
|
return (struct DecodeJson){-1, "maximum depth exceeded"};
|
||||||
}
|
}
|
||||||
for (a = p, d = +1; p < e;) {
|
for (a = p, d = +1; p < e;) {
|
||||||
|
@ -154,7 +152,7 @@ static struct DecodeJson Parse(struct lua_State *L, const char *p,
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
i = 0;
|
i = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
r = Parse(L, p, e, ARRAY_VAL, depth);
|
r = Parse(L, p, e, ARRAY_VAL, depth - 1);
|
||||||
if (UNLIKELY(r.rc == -1)) {
|
if (UNLIKELY(r.rc == -1)) {
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
return r;
|
return r;
|
||||||
|
@ -190,7 +188,7 @@ static struct DecodeJson Parse(struct lua_State *L, const char *p,
|
||||||
if (UNLIKELY(context == OBJECT_KEY)) goto BadObjectKey;
|
if (UNLIKELY(context == OBJECT_KEY)) goto BadObjectKey;
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
r = Parse(L, p, e, OBJECT_KEY, depth);
|
r = Parse(L, p, e, OBJECT_KEY, depth - 1);
|
||||||
if (r.rc == -1) {
|
if (r.rc == -1) {
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
return r;
|
return r;
|
||||||
|
@ -199,7 +197,7 @@ static struct DecodeJson Parse(struct lua_State *L, const char *p,
|
||||||
if (!r.rc) {
|
if (!r.rc) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
r = Parse(L, p, e, OBJECT_VAL, depth);
|
r = Parse(L, p, e, OBJECT_VAL, depth - 1);
|
||||||
if (r.rc == -1) {
|
if (r.rc == -1) {
|
||||||
lua_pop(L, 2);
|
lua_pop(L, 2);
|
||||||
return r;
|
return r;
|
||||||
|
@ -388,9 +386,10 @@ static struct DecodeJson Parse(struct lua_State *L, const char *p,
|
||||||
* @return r.p is string describing error if `rc < 0`
|
* @return r.p is string describing error if `rc < 0`
|
||||||
*/
|
*/
|
||||||
struct DecodeJson DecodeJson(struct lua_State *L, const char *p, size_t n) {
|
struct DecodeJson DecodeJson(struct lua_State *L, const char *p, size_t n) {
|
||||||
|
int depth = 64;
|
||||||
if (n == -1) n = p ? strlen(p) : 0;
|
if (n == -1) n = p ? strlen(p) : 0;
|
||||||
if (lua_checkstack(L, MAX_JSON_DEPTH + MAX_JSON_DEPTH / 2)) {
|
if (lua_checkstack(L, depth * 4)) {
|
||||||
return Parse(L, p, p + n, TOP_LEVEL, MAX_JSON_DEPTH);
|
return Parse(L, p, p + n, TOP_LEVEL, depth);
|
||||||
} else {
|
} else {
|
||||||
return (struct DecodeJson){-1, "can't set stack depth"};
|
return (struct DecodeJson){-1, "can't set stack depth"};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4221,11 +4221,11 @@ static int LuaLog(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int LuaEncodeSmth(lua_State *L,
|
static int LuaEncodeSmth(lua_State *L,
|
||||||
int Encoder(lua_State *, char **, char *, int)) {
|
int Encoder(lua_State *, char **, int, bool)) {
|
||||||
int useoutput = false;
|
|
||||||
int maxdepth = 64;
|
|
||||||
char *numformat = "%.14g";
|
|
||||||
char *p = 0;
|
char *p = 0;
|
||||||
|
int maxdepth = 64;
|
||||||
|
int sorted = true;
|
||||||
|
int useoutput = false;
|
||||||
if (lua_istable(L, 2)) {
|
if (lua_istable(L, 2)) {
|
||||||
lua_settop(L, 2); // discard any extra arguments
|
lua_settop(L, 2); // discard any extra arguments
|
||||||
lua_getfield(L, 2, "useoutput");
|
lua_getfield(L, 2, "useoutput");
|
||||||
|
@ -4233,11 +4233,11 @@ static int LuaEncodeSmth(lua_State *L,
|
||||||
if (ishandlingrequest && lua_isboolean(L, -1)) {
|
if (ishandlingrequest && lua_isboolean(L, -1)) {
|
||||||
useoutput = lua_toboolean(L, -1);
|
useoutput = lua_toboolean(L, -1);
|
||||||
}
|
}
|
||||||
lua_getfield(L, 2, "numformat");
|
lua_getfield(L, 2, "sorted");
|
||||||
numformat = luaL_optstring(L, -1, numformat);
|
sorted = lua_toboolean(L, -1);
|
||||||
}
|
}
|
||||||
lua_settop(L, 1); // keep the passed argument on top
|
lua_settop(L, 1); // keep the passed argument on top
|
||||||
if (Encoder(L, useoutput ? &outbuf : &p, numformat, -1) == -1) {
|
if (Encoder(L, useoutput ? &outbuf : &p, -1, sorted) == -1) {
|
||||||
free(p);
|
free(p);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
@ -5352,7 +5352,7 @@ static void LuaPrint(lua_State *L) {
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
for (i = 1; i <= n; i++) {
|
for (i = 1; i <= n; i++) {
|
||||||
if (i > 1) appendw(&b, '\t');
|
if (i > 1) appendw(&b, '\t');
|
||||||
LuaEncodeLuaData(L, &b, "g", i);
|
LuaEncodeLuaData(L, &b, i, true);
|
||||||
}
|
}
|
||||||
appendw(&b, '\n');
|
appendw(&b, '\n');
|
||||||
WRITE(1, b, appendz(b).i);
|
WRITE(1, b, appendz(b).i);
|
||||||
|
|
Loading…
Add table
Reference in a new issue