Improve Lua and JSON serialization

This commit is contained in:
Justine Tunney 2022-07-12 23:31:06 -07:00
parent 3027d67037
commit e3cd476a9b
20 changed files with 1041 additions and 476 deletions

View file

@ -16,48 +16,24 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/fmt/fmt.h"
#include "libc/bits/popcnt.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.
*
* We choose hex vs. decimal based on whichever one has the most zeroes.
* We only bother counting zeroes for numbers outside -256 𝑥 256.
* This formats as int64 signed decimal. However it's a:
*
* 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) {
int zhex, zdec;
if (-256 <= x && x <= 256) goto UseDecimal;
zhex = CountZeroesHex(x) * 16;
zdec = CountZeroesDec(x) * 10;
if (zdec >= zhex) {
UseDecimal:
return FormatInt64(p, x);
} else {
if (x >= 256 && popcnt(x) == 1) {
return FormatHex64(p, x, z);
} else {
return FormatInt64(p, x);
}
}

View file

@ -38,7 +38,7 @@ static inline int PickGoodWidth(unsigned x, char z) {
* Converts unsigned 64-bit integer to hex string.
*
* @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
*/
char *FormatHex64(char p[hasatleast 19], uint64_t x, char z) {

View file

@ -25,7 +25,7 @@ char *EscapePath(const char *, size_t, size_t *);
char *EscapeParam(const char *, size_t, size_t *);
char *EscapeFragment(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);
char *Underlong(const char *, size_t, size_t *);

View file

@ -47,19 +47,30 @@ static const char kEscapeLiteral[128] = {
* EscapeJsStringLiteral(Underlong(𝑥)) since EscapeJsStringLiteral(𝑥)
* 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 n if -1 implies strlen
* @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;
char *q, *r;
size_t i, j, m;
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 ((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;) {
x = p[i++] & 0xff;
if (x >= 0300) {
@ -138,9 +149,8 @@ char *EscapeJsStringLiteral(const char *p, size_t n, size_t *z) {
unreachable;
}
}
if (z) *z = q - r;
if (z) *z = q - *r;
*q++ = '\0';
if ((q = realloc(r, q - r))) r = q;
}
return r;
return *r;
}

View file

@ -130,7 +130,14 @@ void FreeBulk(void) {
}
}
void MallocFree(void) {
char *volatile p;
p = malloc(16);
free(p);
}
BENCH(bulk_free, bench) {
EZBENCH2("free(malloc(16))", donothing, MallocFree());
EZBENCH2("free() bulk", BulkFreeBenchSetup(), FreeBulk());
EZBENCH2("bulk_free()", BulkFreeBenchSetup(),
bulk_free(bulk, ARRAYLEN(bulk)));

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/testlib/ezbench.h"
@ -24,60 +25,69 @@
#include "libc/testlib/testlib.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 *p;
size_t n;
p = EscapeJsStringLiteral(s, strlen(s), &n);
p = EscapeJsStringLiteral(&o, &y, s, strlen(s), &n);
ASSERT_EQ(strlen(p), n);
return p;
}
TEST(EscapeJsStringLiteral, test) {
EXPECT_STREQ("", gc(escapejs("")));
EXPECT_STREQ("\\u00ff", gc(escapejs("\377")));
EXPECT_STREQ("", escapejs(""));
EXPECT_STREQ("\\u00ff", escapejs("\377"));
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\\/",
gc(escapejs("\1\2\3 &=<>/")));
escapejs("\1\2\3 &=<>/"));
}
TEST(EscapeJsStringLiteral, testUcs2) {
EXPECT_STREQ("\\u00d0\\u263b", gc(escapejs("Ð☻")));
EXPECT_STREQ("\\u00d0\\u263b", escapejs("Ð☻"));
}
TEST(EscapeJsStringLiteral, testAstralPlanes) {
EXPECT_STREQ("\\ud800\\udf30\\ud800\\udf30", gc(escapejs("𐌰𐌰")));
EXPECT_STREQ("\\ud800\\udf30\\ud800\\udf30", escapejs("𐌰𐌰"));
}
TEST(EscapeJsStringLiteral, testBrokenUnicode_sparesInnocentCharacters) {
EXPECT_STREQ("\\u00e1YO", gc(escapejs("\xE1YO")));
EXPECT_STREQ("\\u00e1YO", escapejs("\xE1YO"));
}
void makefile1(void) {
FILE *f;
char *p;
size_t n;
p = EscapeJsStringLiteral(kHyperion, kHyperionSize, &n);
p = EscapeJsStringLiteral(&o, &y, kHyperion, kHyperionSize, &n);
f = fopen("/tmp/a", "wb");
fwrite(p, n, 1, f);
fclose(f);
free(p);
}
void makefile2(void) {
int fd;
char *p;
size_t n;
p = EscapeJsStringLiteral(kHyperion, kHyperionSize, &n);
p = EscapeJsStringLiteral(&o, &y, kHyperion, kHyperionSize, &n);
fd = creat("/tmp/a", 0644);
write(fd, p, n);
close(fd);
free(p);
}
BENCH(EscapeJsStringLiteral, bench) {
EZBENCH2("escapejs", donothing,
free(EscapeJsStringLiteral(kHyperion, kHyperionSize, 0)));
EZBENCH2("escapejs tiny", donothing,
EscapeJsStringLiteral(&o, &y, "hello", 5, 0));
EZBENCH2("escapejs book", donothing,
EscapeJsStringLiteral(&o, &y, kHyperion, kHyperionSize, 0));
EZBENCH2("makefile1", donothing, makefile1());
EZBENCH2("makefile2", donothing, makefile2());
}

View 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

View 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

View file

@ -51,50 +51,6 @@ assert(EscapeParam("?hello&there<>") == "%3Fhello%26there%3C%3E")
assert(DecodeLatin1("hello\xff\xc0") == "helloÿÀ")
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")
assert(url.scheme == "https")
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", 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")
----------------------------------------------------------------------------------------------------
-- 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))

View file

@ -115,8 +115,27 @@ res, err = DecodeJson('"\\ucjcc"')
assert(res == nil)
assert(err == 'invalid unicode escape')
res, err = DecodeJson('[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[')
assert(not res)
-- 63 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":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")
--------------------------------------------------------------------------------
@ -126,9 +145,11 @@ assert(err == "maximum depth exceeded")
-- JsonParseInteger 66 206
-- JsonParseDouble 123 383
-- JsonParseString 116 361
-- JsonParseArray 256 793
-- JsonParseInts 256 793
-- JsonParseFloats 361 1118
-- JsonParseObject 890 2756
-- JsonEncodeArray 639 1979
-- JsonEncodeInts 498 1543
-- JsonEncodeFloats 498 1543
-- JsonEncodeObject 1333 4129
function JsonParseEmpty()
@ -147,27 +168,39 @@ function JsonParseString()
DecodeJson[[ "\ud800\udf30 he𐌰𐌰o \ud800\udf30" ]]
end
function JsonParseArray()
function JsonParseInts()
DecodeJson[[ [123,456,789] ]]
end
function JsonParseFloats()
DecodeJson[[ [3.14,1.23,7.89] ]]
end
function JsonParseObject()
DecodeJson[[ {"3": "1", "4": "1", "5": {"3":"1", "4":"1", "5":"9"}} ]]
end
function JsonEncodeArray()
function JsonEncodeInts()
EncodeJson({2, 0, {5, 7, 3}})
end
function JsonEncodeFloats()
EncodeJson({3.14,1.23,7.89})
end
function JsonEncodeObject()
EncodeJson({["3"]="1", ["4"]="1", ["5"]={["3"]="1", ["4"]="1", ["5"]="9"}})
end
-- print('JsonParseEmpty', Benchmark(JsonParseEmpty))
-- print('JsonParseInteg', Benchmark(JsonParseInteger))
-- print('JsonParseDouble', Benchmark(JsonParseDouble))
-- print('JsonParseString', Benchmark(JsonParseString))
-- print('JsonParseArray', Benchmark(JsonParseArray))
-- print('JsonParseObject', Benchmark(JsonParseObject))
-- print('JsonEncodeArr', Benchmark(JsonEncodeArray))
-- print('JsonEncodeObj', Benchmark(JsonEncodeObject))
if nil then
print('JsonParseEmpty', Benchmark(JsonParseEmpty))
print('JsonParseInteg', Benchmark(JsonParseInteger))
print('JsonParseDouble', Benchmark(JsonParseDouble))
print('JsonParseString', Benchmark(JsonParseString))
print('JsonParseInts', Benchmark(JsonParseInts))
print('JsonParseFloats', Benchmark(JsonParseFloats))
print('JsonParseObject', Benchmark(JsonParseObject))
print('JsonEncodeInts', Benchmark(JsonEncodeInts))
print('JsonEncodeFlts', Benchmark(JsonEncodeFloats))
print('JsonEncodeObj', Benchmark(JsonEncodeObject))
end

View file

@ -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 *);

View file

@ -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;
}

View file

@ -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 \

View file

@ -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;
char ibuf[128];
size_t tbllen, i, z;
struct StrList sl = {0};
if (level > 0) {
switch (lua_type(L, idx)) {
struct Serializer {
struct LuaVisited visited;
const char *reason;
char *strbuf;
size_t strbuflen;
bool sorted;
};
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 0;
OnError:
return -1;
}
case LUA_TBOOLEAN:
RETURN_ON_ERROR(appendw(buf, lua_toboolean(L, idx)
? READ32LE("true")
: READ64LE("false\0\0")));
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_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:
static int SerializeNumber(lua_State *L, char **buf, int idx) {
char ibuf[128];
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_ON_ERROR(appends(buf, DoubleToJson(ibuf, lua_tonumber(L, idx))));
}
return 0;
OnError:
return -1;
}
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);
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;
}
// now serialize the table
if (isarray) {
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++) {
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_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);
}
} 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_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 {
*reason = "won't serialize cyclic lua table";
goto OnError;
}
default:
*reason = "unsupported lua type";
goto OnError;
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 {
*reason = "table has great depth";
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);
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, reason);
lua_pushstring(L, z.reason);
}
return rc;
} else {
luaL_error(L, "can't set stack depth");
unreachable;
}
}

View file

@ -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,62 +90,149 @@ 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 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:
static int SerializeNil(lua_State *L, char **buf) {
RETURN_ON_ERROR(appendw(buf, READ32LE("nil")));
return 0;
OnError:
return -1;
}
case LUA_TSTRING:
s = lua_tolstring(L, idx, &slen);
RETURN_ON_ERROR(EscapeLuaString(s, slen, buf));
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_TFUNCTION:
return LuaEncodeLuaOpaqueData(L, buf, idx, "func");
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_TLIGHTUSERDATA:
return LuaEncodeLuaOpaqueData(L, buf, idx, "light");
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_ON_ERROR(appends(buf, DoubleToLua(ibuf, lua_tonumber(L, idx))));
}
return 0;
OnError:
return -1;
}
case LUA_TTHREAD:
return LuaEncodeLuaOpaqueData(L, buf, idx, "thread");
#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
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 (lua_type(L, -1) == LUA_TSTRING) {
s = lua_tolstring(L, -1, &slen);
RETURN_ON_ERROR(appendd(buf, s, slen));
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)));
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));
RETURN_ON_ERROR(SerializeString(L, buf, -1));
} else {
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]",
"__tostring", luaL_typename(L, -1)));
@ -120,77 +240,147 @@ static int LuaEncodeLuaDataImpl(lua_State *L, char **buf, int level,
lua_pop(L, 1);
return 0;
}
return LuaEncodeLuaOpaqueData(L, buf, idx, "udata");
return SerializeOpaque(L, buf, idx, "udata");
OnError:
return -1;
}
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))));
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;
}
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
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)) {
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], '='));
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(&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(appendw(buf, '['));
RETURN_ON_ERROR(Serialize(L, buf, -2, z, depth - 1));
RETURN_ON_ERROR(appendw(buf, READ16LE("]=")));
}
RETURN_ON_ERROR(LuaEncodeLuaDataImpl(L, &sl.p[sli], level - 1,
numformat, -1, visited));
lua_pop(L, 1); // table/-2, key/-1
RETURN_ON_ERROR(Serialize(L, buf, -1, z, depth - 1));
lua_pop(L, 1);
}
lua_pop(L, 1); // table ref
if (!isarray) SortStrList(&sl);
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);
LuaPopVisit(visited);
return 0;
} else {
return LuaEncodeLuaOpaqueData(L, buf, idx, "cyclic");
}
default:
return LuaEncodeLuaOpaqueData(L, buf, idx, "unsupported");
}
} else {
return LuaEncodeLuaOpaqueData(L, buf, idx, "greatdepth");
}
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);
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, "out of memory");
lua_pushstring(L, z.reason);
}
return rc;
} else {
luaL_error(L, "can't set stack depth");
unreachable;
}
}

View file

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

View file

@ -735,20 +735,52 @@ FUNCTIONS
the original ordering of fields. As such, they'll be sorted
by EncodeJson() and may not round-trip with original intent
EncodeJson(value[,options:table])
EncodeJson(value[, options:table])
├─→ json:str
├─→ true [if useoutput]
└─→ 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
as arrays and any non-array elements are ignored. Empty tables
are encoded as `{}` with the exception of the special empty
table `{[0]=false}` shall be encoded as `[]`. Arrays elements
are serialized in specified order. Object entries are sorted
ASCIIbetically using strcmp() on their string keys to ensure
deterministic order.
Since Lua uses tables for both hashmaps and arrays, we use a
simple fast algorithm for telling the two apart. Tables with
non-zero length (as reported by `#`) are encoded as arrays,
and any non-array elements are ignored. For example:
>: EncodeJson({2})
"[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:
@ -756,38 +788,72 @@ FUNCTIONS
output buffer and returns `nil` value. This option is
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` has depth greater than 64
- `value` contains functions, user data, or threads
- `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
in a deterministic order.
We assume strings in `value` contain UTF-8. This serializer
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
├─→ true [if useoutput]
└─→ nil, error:str
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:
- useoutput: (bool=false) encodes the result directly to the
output buffer and returns `nil` value. This option is
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
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
example, it's used by the REPL where we need to be able to
ignore errors when displaying data structures, since showing
@ -802,10 +868,32 @@ FUNCTIONS
tables; however instead of failing, it embeds a string of
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
when C runs out of heap memory.
EncodeLatin1(utf-8:str[,flags:int]) → iso-8859-1:str
Turns UTF-8 into ISO-8859-1 string.

View file

@ -511,9 +511,13 @@ static dontinline int LuaCoderImpl(lua_State *L,
void *p;
size_t n;
p = luaL_checklstring(L, 1, &n);
p = C(p, n, &n);
if ((p = C(p, n, &n))) {
lua_pushlstring(L, p, n);
free(p);
} else {
luaL_error(L, "out of memory");
unreachable;
}
return 1;
}
@ -575,7 +579,17 @@ int LuaEscapeFragment(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) {

View file

@ -36,8 +36,6 @@
#define OBJECT_KEY 2
#define OBJECT_VAL 3
#define MAX_JSON_DEPTH 128
static struct DecodeJson Parse(struct lua_State *L, const char *p,
const char *e, int context, int depth) {
long x;
@ -47,7 +45,7 @@ static struct DecodeJson Parse(struct lua_State *L, const char *p,
const char *reason;
struct DecodeJson r;
int A, B, C, D, c, d, i, u;
if (UNLIKELY(!--depth)) {
if (UNLIKELY(!depth)) {
return (struct DecodeJson){-1, "maximum depth exceeded"};
}
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);
i = 0;
for (;;) {
r = Parse(L, p, e, ARRAY_VAL, depth);
r = Parse(L, p, e, ARRAY_VAL, depth - 1);
if (UNLIKELY(r.rc == -1)) {
lua_pop(L, 1);
return r;
@ -190,7 +188,7 @@ static struct DecodeJson Parse(struct lua_State *L, const char *p,
if (UNLIKELY(context == OBJECT_KEY)) goto BadObjectKey;
lua_newtable(L);
for (;;) {
r = Parse(L, p, e, OBJECT_KEY, depth);
r = Parse(L, p, e, OBJECT_KEY, depth - 1);
if (r.rc == -1) {
lua_pop(L, 1);
return r;
@ -199,7 +197,7 @@ static struct DecodeJson Parse(struct lua_State *L, const char *p,
if (!r.rc) {
break;
}
r = Parse(L, p, e, OBJECT_VAL, depth);
r = Parse(L, p, e, OBJECT_VAL, depth - 1);
if (r.rc == -1) {
lua_pop(L, 2);
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`
*/
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 (lua_checkstack(L, MAX_JSON_DEPTH + MAX_JSON_DEPTH / 2)) {
return Parse(L, p, p + n, TOP_LEVEL, MAX_JSON_DEPTH);
if (lua_checkstack(L, depth * 4)) {
return Parse(L, p, p + n, TOP_LEVEL, depth);
} else {
return (struct DecodeJson){-1, "can't set stack depth"};
}

View file

@ -4221,11 +4221,11 @@ static int LuaLog(lua_State *L) {
}
static int LuaEncodeSmth(lua_State *L,
int Encoder(lua_State *, char **, char *, int)) {
int useoutput = false;
int maxdepth = 64;
char *numformat = "%.14g";
int Encoder(lua_State *, char **, int, bool)) {
char *p = 0;
int maxdepth = 64;
int sorted = true;
int useoutput = false;
if (lua_istable(L, 2)) {
lua_settop(L, 2); // discard any extra arguments
lua_getfield(L, 2, "useoutput");
@ -4233,11 +4233,11 @@ static int LuaEncodeSmth(lua_State *L,
if (ishandlingrequest && lua_isboolean(L, -1)) {
useoutput = lua_toboolean(L, -1);
}
lua_getfield(L, 2, "numformat");
numformat = luaL_optstring(L, -1, numformat);
lua_getfield(L, 2, "sorted");
sorted = lua_toboolean(L, -1);
}
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);
return 2;
}
@ -5352,7 +5352,7 @@ static void LuaPrint(lua_State *L) {
if (n > 0) {
for (i = 1; i <= n; i++) {
if (i > 1) appendw(&b, '\t');
LuaEncodeLuaData(L, &b, "g", i);
LuaEncodeLuaData(L, &b, i, true);
}
appendw(&b, '\n');
WRITE(1, b, appendz(b).i);