730 lines
23 KiB
C++
730 lines
23 KiB
C++
#include "json.hpp"
|
|
#include "luastack.hpp"
|
|
#include "util.hpp"
|
|
#include <string_view>
|
|
#include <ostream>
|
|
#include <cmath>
|
|
#include <iomanip>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
|
|
#define NOINDENT_LEVEL 1000
|
|
|
|
LuaTokenConstant(json_null, "null", "");
|
|
LuaTokenConstant(json_object, "object", "");
|
|
LuaTokenConstant(json_error, "error", "");
|
|
|
|
static void indent(eng::ostringstream &oss, int level) {
|
|
if (level < NOINDENT_LEVEL) {
|
|
oss << std::endl;
|
|
for (int i = 0; i < level; i++) {
|
|
oss << " ";
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool length_exceeded(eng::ostringstream &oss, int maxlen) {
|
|
return oss.tellp() > maxlen;
|
|
}
|
|
|
|
template <class... ARGS>
|
|
inline void store_error(eng::ostringstream &oss, const ARGS & ... args) {
|
|
oss.str("");
|
|
util::send_to_stream(oss, args...);
|
|
}
|
|
|
|
static void store_length_error(eng::ostringstream &oss, int maxlen) {
|
|
store_error(oss, "maximum json length exceeded: ", maxlen);
|
|
}
|
|
|
|
static bool use_array_representation(lua_State *L) {
|
|
int top = lua_gettop(L);
|
|
int nfound = 0;
|
|
while (true) {
|
|
lua_rawgeti(L, top, nfound + 1);
|
|
bool null = lua_isnil(L, -1);
|
|
lua_settop(L, top);
|
|
if (null) break;
|
|
nfound += 1;
|
|
}
|
|
return (nfound == lua_nkeys(L, top));
|
|
}
|
|
|
|
static bool encode_key(lua_State *L, eng::ostringstream &oss);
|
|
static bool encode_value(lua_State *L, eng::ostringstream &oss, int level, int maxlen);
|
|
|
|
|
|
// The goal here is to emit a double in such a way that
|
|
// when we read it back in, we get the *exact* same number.
|
|
//
|
|
// In the worst case, you can accomplish this by using 17
|
|
// digits of precision - that's enough to uniquely identify
|
|
// all double values (see the following URL). However, 17
|
|
// digits tends to produce unnecessary repeating decimals.
|
|
// So we try 16 digits first, which tends to remove those
|
|
// repeating decimals, but sometimes produces losses.
|
|
// If that doesn't work, we fall back to 17 digits.
|
|
//
|
|
// https://randomascii.wordpress.com/2012/03/08/float-precisionfrom-zero-to-100-digits-2/
|
|
//
|
|
static void encode_double_lossless(double value, eng::ostringstream &oss) {
|
|
char buffer[80];
|
|
sprintf(buffer, "%.16g", value);
|
|
if (strtod(buffer, nullptr) != value) {
|
|
sprintf(buffer, "%.17g", value);
|
|
assert(strtod(buffer, nullptr) == value);
|
|
}
|
|
oss << buffer;
|
|
}
|
|
|
|
static bool encode_nil(lua_State *L, eng::ostringstream &oss) {
|
|
oss << "null";
|
|
return true;
|
|
}
|
|
|
|
static bool encode_token(lua_State *L, eng::ostringstream &oss) {
|
|
LuaToken token(lua_touserdata(L, -1));
|
|
if (token == ltoken_json_null) {
|
|
oss << "null";
|
|
return true;
|
|
} else {
|
|
store_error(oss, "cannot encode token: [", token.str(), "]");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool encode_number(lua_State *L, eng::ostringstream &oss) {
|
|
lua_Number value = lua_tonumber(L, -1);
|
|
if (std::isnan(value) || std::isinf(value)) {
|
|
store_error(oss, "cannot encode infinity or NAN");
|
|
return false;
|
|
}
|
|
int64_t ivalue = int64_t(value);
|
|
if (double(ivalue) == value) {
|
|
oss << ivalue;
|
|
} else {
|
|
encode_double_lossless(value, oss);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool encode_number_key(lua_State *L, eng::ostringstream &oss) {
|
|
lua_Number value = lua_tonumber(L, -1);
|
|
int64_t ivalue = int64_t(value);
|
|
if (double(ivalue) != value) {
|
|
store_error(oss, "cannot encode floating point numbers in table keys");
|
|
return false;
|
|
}
|
|
if (ivalue >= 0) {
|
|
oss << "\"\\uE000+" << ivalue << '"';
|
|
} else {
|
|
oss << "\"\\uE000-" << -ivalue << '"';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool encode_boolean(lua_State *L, eng::ostringstream &oss) {
|
|
int flag = lua_toboolean(L, -1);
|
|
oss << (flag ? "true" : "false");
|
|
return true;
|
|
}
|
|
|
|
static bool encode_string(lua_State *L, eng::ostringstream &oss) {
|
|
size_t len;
|
|
const char *s = lua_tolstring(L, -1, &len);
|
|
std::string_view str(s, len);
|
|
oss << '"';
|
|
if (sv::valid_utf8(str) && !sv::has_prefix(str, "")) {
|
|
while (!str.empty()) {
|
|
int32_t cp = sv::read_codepoint_utf8(str);
|
|
assert(cp >= 0);
|
|
switch (cp) {
|
|
case '\\': oss << "\\\\"; break;
|
|
case '"' : oss << "\\\""; break;
|
|
case '\b': oss << "\\b"; break;
|
|
case '\f': oss << "\\f"; break;
|
|
case '\r': oss << "\\r"; break;
|
|
case '\n': oss << "\\n"; break;
|
|
case '\t': oss << "\\t"; break;
|
|
default: {
|
|
if (cp < 32) {
|
|
oss << "\\u" << util::hex16.val(cp);
|
|
} else {
|
|
bool ok = util::write_codepoint_utf8(cp, &oss);
|
|
assert(ok);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Output as a base64-encoded string.
|
|
oss << "\\uE000=";
|
|
util::base64_encode(str, &oss);
|
|
}
|
|
oss << '"';
|
|
return true;
|
|
}
|
|
|
|
static bool encode_array(lua_State *L, eng::ostringstream &oss, int level, int maxlen) {
|
|
lua_checkstack(L, 20);
|
|
int top = lua_gettop(L);
|
|
oss << "[";
|
|
level ++;
|
|
int i = 1;
|
|
while (true) {
|
|
lua_rawgeti(L, top, i);
|
|
if (lua_isnil(L, -1)) break;
|
|
if (i > 1) oss << ",";
|
|
indent(oss, level);
|
|
bool ok = encode_value(L, oss, level, maxlen);
|
|
lua_settop(L, top);
|
|
if (!ok) return false;
|
|
if (length_exceeded(oss, maxlen)) {
|
|
store_length_error(oss, maxlen);
|
|
return false;
|
|
}
|
|
i += 1;
|
|
}
|
|
lua_settop(L, top);
|
|
level --;
|
|
indent(oss, level);
|
|
oss << "]";
|
|
return true;
|
|
}
|
|
|
|
static bool encode_object(lua_State *L, eng::ostringstream &oss, int level, int maxlen) {
|
|
lua_checkstack(L, 20);
|
|
int top = lua_gettop(L);
|
|
oss << "{";
|
|
level ++;
|
|
lua_pushnil(L);
|
|
int i = 1;
|
|
while (lua_next(L, top) != 0) {
|
|
// Check for [json.object]=true, if so skip.
|
|
if (lua_islightuserdata(L, -2) &&
|
|
lua_isboolean(L, -1) &&
|
|
(LuaToken(lua_touserdata(L, -2)) == LuaToken("object")) &&
|
|
(lua_toboolean(L, -1) == 1)) {
|
|
lua_pop(L, 1);
|
|
continue;
|
|
}
|
|
|
|
lua_pushvalue(L, -2);
|
|
// Stack now has key, value, key
|
|
assert(lua_gettop(L) == top + 3);
|
|
if (i > 1) oss << ",";
|
|
indent(oss, level);
|
|
bool ok = encode_key(L, oss);
|
|
if (!ok) {
|
|
lua_settop(L, top);
|
|
return false;
|
|
}
|
|
if (length_exceeded(oss, maxlen)) {
|
|
store_length_error(oss, maxlen);
|
|
lua_settop(L, top);
|
|
return false;
|
|
}
|
|
lua_pop(L, 1);
|
|
// Stack now has key, value
|
|
assert(lua_gettop(L) == top + 2);
|
|
oss << ((level < NOINDENT_LEVEL) ? " : " : ":");
|
|
ok = encode_value(L, oss, level, maxlen);
|
|
assert(lua_gettop(L) == top + 2);
|
|
if (!ok) {
|
|
lua_settop(L, top);
|
|
return false;
|
|
}
|
|
if (length_exceeded(oss, maxlen)) {
|
|
store_length_error(oss, maxlen);
|
|
lua_settop(L, top);
|
|
return false;
|
|
}
|
|
lua_pop(L, 1);
|
|
// Stack now just has key.
|
|
assert(lua_gettop(L) == top + 1);
|
|
i += 1;
|
|
}
|
|
// Stack should be back to where we started.
|
|
assert(lua_gettop(L) == top);
|
|
level --;
|
|
indent(oss, level);
|
|
oss << "}";
|
|
return true;
|
|
}
|
|
|
|
static bool encode_key(lua_State *L, eng::ostringstream &oss) {
|
|
int type = lua_type(L, -1);
|
|
switch (type) {
|
|
case LUA_TSTRING: return encode_string(L, oss);
|
|
case LUA_TNUMBER: return encode_number_key(L, oss);
|
|
case LUA_TBOOLEAN:
|
|
case LUA_TTABLE: {
|
|
store_error(oss, "cannot encode '", lua_typename(L, type), "' in table keys");
|
|
return false;
|
|
}
|
|
default: {
|
|
store_error(oss, "cannot encode '", lua_typename(L, type), "'");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool encode_value(lua_State *L, eng::ostringstream &oss, int level, int maxlen) {
|
|
int type = lua_type(L, -1);
|
|
switch (type) {
|
|
case LUA_TNIL: return encode_nil(L, oss);
|
|
case LUA_TNUMBER: return encode_number(L, oss);
|
|
case LUA_TBOOLEAN: return encode_boolean(L, oss);
|
|
case LUA_TSTRING: return encode_string(L, oss);
|
|
case LUA_TLIGHTUSERDATA: return encode_token(L, oss);
|
|
case LUA_TTABLE: {
|
|
if (use_array_representation(L)) {
|
|
return encode_array(L, oss, level, maxlen);
|
|
} else {
|
|
return encode_object(L, oss, level, maxlen);
|
|
}
|
|
}
|
|
default: {
|
|
store_error(oss, "cannot encode '", lua_typename(L, type), "'");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool decode_value(lua_State *L, std::string_view &v);
|
|
|
|
static bool decode_id(lua_State *L, std::string_view &v) {
|
|
std::string_view id = sv::read_ascii_identifier(v);
|
|
if (id == "null") lua_pushlightuserdata(L, LuaToken("null").voidvalue());
|
|
else if (id == "true") lua_pushboolean(L, 1);
|
|
else if (id == "false") lua_pushboolean(L, 0);
|
|
else return false;
|
|
return true;
|
|
}
|
|
|
|
static bool decode_number(lua_State *L, std::string_view &v) {
|
|
std::string_view n = sv::read_number(v, true, true, true, true);
|
|
if (n.empty()) return false;
|
|
|
|
// If it's an integer, make sure it fits in a lua double
|
|
// losslessly. If it's a double, some loss in precision
|
|
// is OK.
|
|
if (sv::valid_number(n, true, true, false, false)) {
|
|
int64_t i = sv::to_int64(n);
|
|
if (!LuaCoreStack::validinteger(i)) return false;
|
|
lua_pushnumber(L, double(i));
|
|
return true;
|
|
} else {
|
|
double d = sv::to_double(n);
|
|
if (std::isnan(d) || std::isinf(d)) return false;
|
|
lua_pushnumber(L, d);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static bool decode_base64_string(lua_State *L, std::string_view &v) {
|
|
// We've already read the starting quote and the E000
|
|
// escape sequence at this point.
|
|
|
|
// Skip the equal sign.
|
|
if (!sv::read_prefix(v, "=")) return false;
|
|
|
|
// Find the end of the quoted string.
|
|
const char *p = v.data();
|
|
const char *l = p + v.size();
|
|
while (true) {
|
|
if (p == l) return false;
|
|
if (*p < 32) return false;
|
|
if (*p == '"') break;
|
|
p++;
|
|
}
|
|
std::string_view b64 = v.substr(0, p - v.data());
|
|
v.remove_prefix(b64.size() + 1);
|
|
eng::ostringstream oss;
|
|
if (!util::base64_decode(b64, &oss)) return false;
|
|
eng::string str = oss.str();
|
|
lua_pushlstring(L, str.c_str(), str.size());
|
|
return true;
|
|
}
|
|
|
|
static bool decode_int_string(lua_State *L, std::string_view &v) {
|
|
// We've already read the starting quote and the E000
|
|
// escape sequence at this point.
|
|
|
|
// Parse the number and the closing quote.
|
|
std::string_view n = sv::read_number(v, true, true, false, false);
|
|
if (n.empty()) return false;
|
|
if (!sv::read_prefix(v, "\"")) {
|
|
return false;
|
|
}
|
|
|
|
// Make sure the number fits in a lua double,
|
|
// and push it on the stack.
|
|
int64_t i = sv::to_int64(n);
|
|
if (!LuaCoreStack::validinteger(i)) {
|
|
return false;
|
|
}
|
|
lua_pushnumber(L, double(i));
|
|
return true;
|
|
}
|
|
|
|
static bool decode_standard_string(lua_State *L, std::string_view &v) {
|
|
// We've already read the starting quote at this point.
|
|
eng::ostringstream oss;
|
|
while (true) {
|
|
// Get the next codepoint.
|
|
int32_t c = sv::read_codepoint_utf8(v);
|
|
|
|
// If it's a control character or invalid codepoint, reject.
|
|
if (c < 32) return false;
|
|
|
|
// If it is an unescaped quote, that's end of string.
|
|
if (c == '"') break;
|
|
|
|
// If it's a backslash, then deal with the escape sequence.
|
|
if (c == '\\') {
|
|
char next = sv::read_ascii_char(v);
|
|
switch (next) {
|
|
case '"': oss << '"'; break;
|
|
case '\\': oss << '\\'; break;
|
|
case '/': oss << '/'; break;
|
|
case 'r': oss << '\r'; break;
|
|
case 'n': oss <<'\n'; break;
|
|
case 'b': oss << '\b'; break;
|
|
case 'f': oss << '\f'; break;
|
|
case 't': oss << '\t'; break;
|
|
case 'u': {
|
|
std::string_view hexdigits = sv::read_nbytes(v, 4);
|
|
if (hexdigits.size() != 4) return false;
|
|
uint64_t codepoint = sv::to_hex64(hexdigits, 0x10000);
|
|
if (codepoint >= 0x10000) return false;
|
|
if (!util::write_codepoint_utf8(codepoint, &oss)) return false;
|
|
break;
|
|
}
|
|
default: return false;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Any other codepoint should be echoed into stream.
|
|
util::write_codepoint_utf8(c, &oss);
|
|
}
|
|
eng::string result = oss.str();
|
|
lua_pushlstring(L, result.c_str(), result.size());
|
|
return true;
|
|
}
|
|
|
|
static bool decode_string(lua_State *L, std::string_view &v) {
|
|
if (!sv::read_prefix(v, "\"")) return false;
|
|
|
|
// Check for codepoint E000, the escape sequence.
|
|
if (sv::read_prefix(v, "") ||
|
|
sv::read_prefix(v, "\\uE000") ||
|
|
sv::read_prefix(v, "\\ue000")) {
|
|
char c = sv::zfront(v);
|
|
if (c == '=') return decode_base64_string(L, v);
|
|
else if ((c=='-') || (c=='+')) return decode_int_string(L, v);
|
|
else return false;
|
|
} else {
|
|
return decode_standard_string(L, v);
|
|
}
|
|
}
|
|
|
|
static bool decode_array(lua_State *L, std::string_view &v) {
|
|
if (!sv::read_prefix(v, "[")) return false;
|
|
lua_newtable(L);
|
|
int tabpos = lua_gettop(L);
|
|
int next = 1;
|
|
while (true) {
|
|
v = sv::ltrim(v);
|
|
if (sv::zfront(v) == ']') {
|
|
v.remove_prefix(1);
|
|
return true;
|
|
}
|
|
if (!decode_value(L, v)) {
|
|
return false;
|
|
}
|
|
v = sv::ltrim(v);
|
|
if (sv::zfront(v) == ',') {
|
|
v.remove_prefix(1);
|
|
}
|
|
lua_rawseti(L, tabpos, next++);
|
|
}
|
|
}
|
|
|
|
static bool decode_object(lua_State *L, std::string_view &v) {
|
|
if (!sv::read_prefix(v, "{")) return false;
|
|
lua_newtable(L);
|
|
int tabpos = lua_gettop(L);
|
|
while (true) {
|
|
v = sv::ltrim(v);
|
|
if (sv::zfront(v) == '}') {
|
|
v.remove_prefix(1);
|
|
return true;
|
|
}
|
|
if (!decode_string(L, v)) {
|
|
return false;
|
|
}
|
|
v = sv::ltrim(v);
|
|
if (!sv::read_prefix(v, ":")) {
|
|
return false;
|
|
}
|
|
if (!decode_value(L, v)) {
|
|
return false;
|
|
}
|
|
v = sv::ltrim(v);
|
|
if (sv::zfront(v) == ',') {
|
|
v.remove_prefix(1);
|
|
}
|
|
lua_rawset(L, tabpos);
|
|
}
|
|
}
|
|
|
|
// Decode a single value.
|
|
//
|
|
// On success, pushes the value on the stack and returns true.
|
|
// On failure, pushes NIL on the stack and returns false.
|
|
//
|
|
static bool decode_value(lua_State *L, std::string_view &v) {
|
|
lua_checkstack(L, 20);
|
|
int top = lua_gettop(L);
|
|
|
|
// Skip blanks.
|
|
v = sv::ltrim(v);
|
|
|
|
// Try to read something.
|
|
char c = sv::zfront(v);
|
|
bool result;
|
|
if (c == '"') result = decode_string(L, v);
|
|
else if (c == '[') result = decode_array(L, v);
|
|
else if (c == '{') result = decode_object(L, v);
|
|
else if (sv::ascii_isalpha(c)) result = decode_id(L, v);
|
|
else result = decode_number(L, v);
|
|
|
|
// On failure, the decode routines may leave junk
|
|
// on the stack, in which case it's our job to clean up.
|
|
if (result == false) {
|
|
lua_settop(L, top);
|
|
lua_pushnil(L);
|
|
}
|
|
|
|
// Now there should be exactly one new value on the stack.
|
|
assert(lua_gettop(L) == top + 1);
|
|
return result;
|
|
}
|
|
|
|
namespace json {
|
|
|
|
eng::string encode(LuaCoreStack &LS, LuaSlot in, eng::string &out, bool indent, int maxlen) {
|
|
eng::ostringstream oss;
|
|
|
|
// Call the recursive encoder. Clean up any crap on the lua stack afterward.
|
|
int top = lua_gettop(LS.state());
|
|
lua_pushvalue(LS.state(), in.index());
|
|
bool ok = encode_value(LS.state(), oss, indent ? 0 : NOINDENT_LEVEL, maxlen);
|
|
lua_settop(LS.state(), top);
|
|
|
|
// One last check for overruns.
|
|
if (ok && length_exceeded(oss, maxlen)) {
|
|
store_length_error(oss, maxlen);
|
|
ok = false;
|
|
}
|
|
|
|
// Produce the return value.
|
|
if (ok) {
|
|
out = oss.str();
|
|
return "";
|
|
} else {
|
|
out = "";
|
|
return oss.str();
|
|
}
|
|
}
|
|
|
|
bool decode(LuaCoreStack &LS, LuaSlot out, std::string_view v) {
|
|
lua_State *L = LS.state();
|
|
|
|
// Try to read a single value from the view.
|
|
int top = lua_gettop(L);
|
|
bool ok = decode_value(L, v);
|
|
lua_replace(L, out.index());
|
|
lua_settop(L, top);
|
|
if (!ok) {
|
|
LS.set(out, LuaToken("error"));
|
|
return false;
|
|
}
|
|
|
|
// Special case: if the top level value is 'null', change
|
|
// it to 'nil.'
|
|
if (LS.istoken(out)) {
|
|
LS.set(out, LuaNil);
|
|
}
|
|
|
|
// There should be nothing left of the input text.
|
|
if (v.size() > 0) {
|
|
LS.set(out, LuaToken("error"));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace util
|
|
|
|
|
|
LuaDefine(json_encode, "data, indent, maxlen",
|
|
"|Encode a lua data structure returning a json string."
|
|
"|"
|
|
"|Data is the value being encoded. Indent is a flag,"
|
|
"|if it's true, then the json is indented nicely,"
|
|
"|otherwise, it is packed tightly. Maxlen is the maximum"
|
|
"|length in bytes of the encoded json string."
|
|
"|"
|
|
"|Usually, Lua data translates straightforwardly to json."
|
|
"|However, there are a number of special cases to be"
|
|
"|aware of:"
|
|
"|"
|
|
"|- Closures and threads cannot be encoded. These will"
|
|
"| cause the encoder to abort."
|
|
"|"
|
|
"|- The numbers infinity and NAN cannot be encoded."
|
|
"| Both of these will cause the encoder to abort."
|
|
"|"
|
|
"|- You must specify a size-limit to the encoded"
|
|
"| string. Exceeding the size limit causes the"
|
|
"| encoder to abort."
|
|
"|"
|
|
"|- Recursive data structures will cause the encoder to"
|
|
"| loop infinitely until the size-limit is exceeded,"
|
|
"| causing the encoder to abort."
|
|
"|"
|
|
"|- There is no way to represent math.huge or math.nan in"
|
|
"| json. Encoding math.nan will cause the encoder to abort,"
|
|
"| as expected. However, encoding math.huge will emit null,"
|
|
"| which is probably not what you would expect."
|
|
"|"
|
|
"|- Lua tables cannot contain 'nil', but json objects and"
|
|
"| arrays can contain null. If you want the encoder to"
|
|
"| emit a json object or array containing null, you must"
|
|
"| use token json.null to represent null."
|
|
"|"
|
|
"|- Json objects, like lua tables, are key-value stores."
|
|
"| However, json objects can only have string keys. Our"
|
|
"| encoder uses a workaround to transparently"
|
|
"| allow mixing string and integer keys in json tables."
|
|
"| See 'encoding difficult data' below."
|
|
"|"
|
|
"|- Json strings are required to be valid utf-8. Our encoder"
|
|
"| uses a workaround to transparently allow the use of"
|
|
"| arbitrary 8-bit-clean strings. See 'encoding difficult"
|
|
"| data' below."
|
|
"|"
|
|
"|- Lua tables containing contiguous integer keys from 1-n are"
|
|
"| autodetected to be json arrays. Empty tables are also"
|
|
"| emitted as json arrays. All other tables are emitted"
|
|
"| as json objects."
|
|
"|"
|
|
"|- You can force a table to be emitted as a json object"
|
|
"| by putting the key-value pair table[json.object]=true"
|
|
"| into the table. This special key is not emitted, but"
|
|
"| it triggers json object mode. This is the only way"
|
|
"| to emit an empty json object (a truly empty table is"
|
|
"| emitted as a json array.)"
|
|
"|"
|
|
"|Encoding Difficult Data:"
|
|
"|"
|
|
"|Normally, json doesn't allow integer table keys, and it"
|
|
"|doesn't allow strings that aren't valid utf-8. Our"
|
|
"|json encoder and decoder, on the other hand, can"
|
|
"|encode and decode integer table keys and 8-bit-clean"
|
|
"|strings transparently. This is accomplished without"
|
|
"|violating the json specification, by encoding such"
|
|
"|values as utf-8 strings:"
|
|
"|"
|
|
"| '123' (encoded integer 123)"
|
|
"| '=aGVsbG8=' (binary string encoded as base64)"
|
|
"|"
|
|
"|Those encodings start with utf-8 codepoint E000."
|
|
"|This codepoint probably shows up in your text editor"
|
|
"|as a little rectangle. When the decoder sees codepoint"
|
|
"|E000 at the beginning of a string, it automatically"
|
|
"|decodes the string back into its original form."
|
|
"|"
|
|
"|The one price for this behavior is that the encoder"
|
|
"|cannot literally emit strings that start"
|
|
"|with codepoint E000. If the encoder detects such a"
|
|
"|string, it will emit it as a base64-encoded string."
|
|
"|This should be uncommon, since codepoint E000 is"
|
|
"|reserved."
|
|
"|"
|
|
"|Note that integers are only encoded when they are"
|
|
"|used as table keys. Otherwise, numbers are emitted"
|
|
"|straightforwardly."
|
|
"|") {
|
|
LuaArg data, indent, maxlen;
|
|
LuaRet encoded;
|
|
LuaDefStack LS(L, data, indent, maxlen, encoded);
|
|
eng::string out;
|
|
eng::string error = json::encode(LS, data, out, LS.ckboolean(indent), LS.ckint(maxlen));
|
|
if (!error.empty()) {
|
|
luaL_error(L, "%s", error.c_str());
|
|
LS.set(encoded, LuaNil);
|
|
return LS.result();
|
|
} else {
|
|
LS.set(encoded, out);
|
|
return LS.result();
|
|
}
|
|
}
|
|
|
|
LuaDefine(json_decode, "data",
|
|
"|Decode a json expression into a lua data structure."
|
|
"|"
|
|
"|Data that was generated by our own encoder is almost"
|
|
"|8-bit clean. That includes difficult cases, like"
|
|
"|binary strings, floating point numbers, and tables"
|
|
"|with mixed string and integer keys. The exception"
|
|
"|are the kinds of data that can't be encoded at all:"
|
|
"|See doc(json.encode) for details about what"
|
|
"|can and cannot be encoded."
|
|
"|"
|
|
"|Some json may contain 'null' inside objects and"
|
|
"|arrays. Lua tables can't store nil, so instead, we"
|
|
"|store the token json.null. If that's not what you"
|
|
"|want, you can use json.stripnulls to strip out"
|
|
"|the json.null values from a data structure and"
|
|
"|replace them with nil."
|
|
"|"
|
|
"|") {
|
|
LuaArg encoded;
|
|
LuaRet data;
|
|
LuaDefStack LS(L, encoded, data);
|
|
std::string_view v = LS.ckstringview(encoded);
|
|
bool ok = json::decode(LS, data, v);
|
|
if (!ok) {
|
|
luaL_error(L, "invalid json string.");
|
|
}
|
|
return LS.result();
|
|
}
|
|
|
|
// LuaDefine(base64_encode, "data", "") {
|
|
// LuaArg str;
|
|
// LuaRet ret;
|
|
// LuaDefStack LS(L, str, ret);
|
|
// eng::string cstr = LS.ckstring(str);
|
|
// eng::ostringstream oss;
|
|
// util::base64_encode(cstr, &oss);
|
|
// LS.set(ret, oss.str());
|
|
// return LS.result();
|
|
// }
|
|
|
|
// LuaDefine(base64_decode, "data", "") {
|
|
// LuaArg str;
|
|
// LuaRet ret;
|
|
// LuaDefStack LS(L, str, ret);
|
|
// eng::string cstr = LS.ckstring(str);
|
|
// eng::ostringstream oss;
|
|
// util::base64_decode(cstr, &oss);
|
|
// LS.set(ret, oss.str());
|
|
// return LS.result();
|
|
// }
|
|
|