Commit 99e6ae99 authored by seny's avatar seny
Browse files

Version 0.1.0

parents
cmake_minimum_required(VERSION 3.0)
project(lua-json)
set(USE_LUA_VERSION "" CACHE STRING "Build for Lua version 'X.Y' (or 'jit' for LuaJIT).")
if(USE_LUA_VERSION MATCHES "^[0-9]\\.[0-9]$")
set(ver ${USE_LUA_VERSION})
string(REGEX REPLACE "\\." "" ver_ ${USE_LUA_VERSION})
set(lua lua-${ver} lua${ver} lua${ver_})
elseif(USE_LUA_VERSION MATCHES "^(jit|)$")
set(lua lua${USE_LUA_VERSION})
set(ver 5.1)
else()
message(FATAL_ERROR "Unrecognized Lua version '${USE_LUA_VERSION}'")
endif()
find_package(PkgConfig)
pkg_search_module(LUA REQUIRED ${lua})
if(NOT LUA_FOUND)
message(FATAL_ERROR "Lua not found - set USE_LUA_VERSION to match your configuration")
elseif(USE_LUA_VERSION STREQUAL "")
string(REGEX MATCH "^[0-9]\\.[0-9]" USE_LUA_VERSION ${LUA_VERSION})
message(STATUS "Using Lua '${USE_LUA_VERSION}', version ${LUA_VERSION} (set USE_LUA_VERSION to override)")
set(ver ${USE_LUA_VERSION})
else()
message(STATUS "Using Lua '${USE_LUA_VERSION}', version ${LUA_VERSION}")
endif()
add_definitions(-Wall -Wextra -Wpedantic -Wundef -Wshadow -Wredundant-decls
-Wstrict-prototypes -Wmissing-prototypes -Wno-variadic-macros)
include_directories(${LUA_INCLUDE_DIRS})
file(GLOB srcs src/*.c)
add_library(json SHARED ${srcs})
set_target_properties(json PROPERTIES PREFIX "")
if(APPLE)
target_link_libraries(json "-undefined dynamic_lookup")
set_target_properties(json PROPERTIES SUFFIX ".so")
endif()
include(GNUInstallDirs)
install(TARGETS json DESTINATION ${CMAKE_INSTALL_LIBDIR}/lua/${ver})
enable_testing()
find_program(LUA_COMMAND NAMES ${lua})
file(GLOB tests test/test-*.lua)
foreach(test ${tests})
string(REGEX REPLACE "^.*(test-[^/\\]+\\.lua)$" "\\1" name ${test})
add_test(${name} ${LUA_COMMAND} ${test})
set_tests_properties(${name} PROPERTIES ENVIRONMENT "LUA_CPATH=${CMAKE_BINARY_DIR}/?.so\;\;")
endforeach()
Copyright (C) 2019 Arseny Vakhrushev <arseny.vakhrushev@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
JSON encoding/decoding library for Lua
======================================
[lua-json] provides the following API:
### json.encode(value, [event])
Returns a text string containing a JSON representation of `value`. Optional `event` may be used
to specify a metamethod name (default is `__toJSON`) that is called for every processed value. The
value returned by the metamethod is used instead of the original value.
A table (root or nested) is encoded into a dense array if it has a field `__array` whose value is
_true_. The length of the resulting array can be adjusted by storing an integer value in that field.
Otherwise, it is assumed to be equal to the raw length of the table.
### json.decode(data, [pos], [handler])
Returns the value encoded in `data` along with the index of the first unread byte. Optional `pos`
marks where to start reading in `data` (default is 1). Optional `handler` is called for each new
table (root or nested), and its return value is used instead of the original table.
When an array is decoded, its length is stored in a field `__array`.
### json.null
A Lua value that represents JSON null.
Building and installing with LuaRocks
-------------------------------------
To build and install, run:
luarocks make
To install the latest release using [luarocks.org], run:
luarocks install lua-json
Building and installing with CMake
----------------------------------
To build and install, run:
cmake .
make
make install
To build for a specific Lua version, set `USE_LUA_VERSION`. For example:
cmake -D USE_LUA_VERSION=5.1 .
or for LuaJIT:
cmake -D USE_LUA_VERSION=jit .
To build in a separate directory, replace `.` with a path to the source.
Getting started
---------------
```Lua
local json = require 'json'
-- Helpers
local function encode_decode(val, ev, h)
return json.decode(json.encode(val, ev), nil, h)
end
-- Primitive types
assert(encode_decode(nil) == json.null)
assert(encode_decode(json.null) == json.null)
assert(encode_decode(false) == false)
assert(encode_decode(true) == true)
assert(encode_decode(123) == 123)
assert(encode_decode(123.456) == 123.456)
assert(encode_decode('abc') == 'abc')
-- Complex types
local data = {
obj = { -- A table with only string keys translates into an object
str = 'abc',
len = 3,
val = -10.2,
null = json.null,
},
arr1 = {__array = true, 1, 2, 3}, -- A table with a field '__array' translates into an array
arr2 = {__array = 5, nil, 2, nil, 4, nil}, -- Array length can be adjusted to form a sparse array
}
local out = encode_decode(data)
assert(out.obj.null == json.null) -- 'null' as a field value
assert(out.arr1.__array == #out.arr1) -- Array length is restored
assert(out.arr2.__array == 5) -- Access to the number of items in a sparse array
-- Serialization metamethods can be used to produce multiple JSON representations of the same object.
-- Deserialization handlers can be used to restore Lua objects from complex JSON types on the way back.
-- This is helpful, for example, when objects are exchanged with both trusted and untrusted parties.
-- Various custom filters/wrappers can also be implemented using this API.
local function construct(t)
return setmetatable(t, {
__tostring = function (t) return (t.a or '') .. (t.b or '') end,
__toA = function (t) return {A = t.a} end, -- [a -> A]
__toB = function (t) return {B = t.b} end, -- [b -> B]
})
end
local function fromA(t) return construct{a = t.A} end -- [A -> a]
local function fromB(t) return construct{b = t.B} end -- [B -> b]
local obj = construct{a = 'a', b = 'b'}
assert(tostring(obj) == 'ab')
assert(tostring(encode_decode(obj, '__toA', fromA)) == 'a')
assert(tostring(encode_decode(obj, '__toB', fromB)) == 'b')
```
Extended JSON
-------------
[lua-json] accepts non-standard numeric values `[-]nan`, `[-]NaN`, `[-]inf`, `[-]Infinity` when encoding
or decoding (if supported by the system). It also recognizes numbers prefixed with `0x` as hexadecimal
when decoding.
If strictly compliant JSON generation is preferred, the following technique may be used to filter out
these values:
```Lua
local json = require 'json'
local function check(t)
for k, v in pairs(t) do
if type(v) == 'number' and (v ~= v or v == 1/0 or v == -1/0) then
error(("invalid value '%f' at index '%s'"):format(v, k))
end
end
return setmetatable(t, nil)
end
local mt = {__toJSON = check}
local t = {
good = 1.234,
nan = 0/0,
inf = 1/0,
ninf = -1/0,
}
local s = [[{
"good": 1.234,
"nan": nan,
"inf": inf,
"ninf": -inf
}]]
-- Strict encoding
print(json.encode(setmetatable(t, mt)))
-- Strict decoding
print(json.decode(s, nil, check))
```
[lua-json]: https://github.com/neoxic/lua-json
[luarocks.org]: https://luarocks.org
Current: 0.1.0
Release: 0.1.0
package = 'lua-json'
version = 'git-1'
source = {
url = 'git://github.com/neoxic/lua-json.git',
}
description = {
summary = 'JSON encoding/decoding library for Lua',
license = 'MIT',
homepage = 'https://github.com/neoxic/lua-json',
maintainer = 'Arseny Vakhrushev <arseny.vakhrushev@gmail.com>',
}
dependencies = {
'lua >= 5.1',
}
build = {
type = 'builtin',
modules = {
json = {
sources = {
'src/json.c',
'src/json-encode.c',
'src/json-decode.c',
},
},
},
}
/*
** Copyright (C) 2019 Arseny Vakhrushev <arseny.vakhrushev@gmail.com>
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <math.h>
#include "json.h"
static size_t decodeWhitespace(const char *buf, size_t pos, size_t size) {
while (pos < size && strchr(" \t\n\r", buf[pos])) ++pos;
return pos;
}
static size_t decodeBoundary(const char *buf, size_t pos, size_t size, char c, int *res) {
return pos + (*res = pos < size && buf[pos] == c);
}
static size_t decodeDelimiter(lua_State *L, const char *buf, size_t pos, size_t size, char c, int *res) {
int res_;
pos = decodeWhitespace(buf, pos, size);
pos = decodeBoundary(buf, pos, size, c, &res_);
if (res) *res = res_;
else if (!res_) luaL_error(L, "delimiter '%c' expected at position %d", c, pos + 1);
return pos;
}
static size_t decodeCharacter(lua_State *L, const char *buf, size_t pos, size_t size, luaL_Buffer *sb, int *val) {
int len = 0, x = 0;
buf += pos;
for (;;) {
unsigned char c;
if (pos + len >= size) luaL_error(L, "character expected at position %d", pos + len + 1);
c = buf[len];
if (c < 0x20) luaL_error(L, "control character at position %d", pos + len + 1);
++len;
if (len == 1) {
if (c == '\\') continue;
luaL_addchar(sb, c);
break;
}
if (len == 2) {
switch (c) {
case '"':
case '\\':
case '/':
break;
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'u':
continue;
default:
goto error;
}
luaL_addchar(sb, c);
break;
}
x <<= 4;
if ((c >= '0') && (c <= '9')) x |= c - '0';
else if ((c >= 'a') && (c <= 'f')) x |= c - 'a' + 10;
else if ((c >= 'A') && (c <= 'F')) x |= c - 'A' + 10;
else goto error;
if (len == 6) {
*val = x;
break;
}
}
return pos + len;
error:
luaL_error(L, "invalid escape sequence at position %d", pos + 1);
return 0;
}
static void addUTF8(luaL_Buffer *sb, int val) {
char buf[4];
int len;
if (val <= 0x7f) {
buf[0] = val;
len = 1;
} else if (val <= 0x7ff) {
buf[0] = ((val >> 6) & 0x1f) | 0xc0;
buf[1] = (val & 0x3f) | 0x80;
len = 2;
} else if (val <= 0xffff) {
buf[0] = ((val >> 12) & 0x0f) | 0xe0;
buf[1] = ((val >> 6) & 0x3f) | 0x80;
buf[2] = (val & 0x3f) | 0x80;
len = 3;
} else {
buf[0] = ((val >> 18) & 0x07) | 0xf0;
buf[1] = ((val >> 12) & 0x3f) | 0x80;
buf[2] = ((val >> 6) & 0x3f) | 0x80;
buf[3] = (val & 0x3f) | 0x80;
len = 4;
}
luaL_addlstring(sb, buf, len);
}
static size_t decodeString(lua_State *L, const char *buf, size_t pos, size_t size) {
luaL_Buffer sb;
pos = decodeDelimiter(L, buf, pos, size, '"', 0);
luaL_buffinit(L, &sb);
for (;;) {
int res, val = -1;
pos = decodeBoundary(buf, pos, size, '"', &res);
if (res) break;
pos = decodeCharacter(L, buf, pos, size, &sb, &val);
if (val == -1) continue;
if (val >= 0xd800 && val <= 0xdbff) { /* Surrogate pair */
int val_ = -1;
size_t pos_ = decodeCharacter(L, buf, pos, size, &sb, &val_);
if (val_ < 0xdc00 || val_ > 0xdfff) luaL_error(L, "invalid UTF-16 surrogate at position %d", pos + 1);
val = ((val - 0xd800) << 10) + (val_ - 0xdc00) + 0x10000;
pos = pos_;
}
addUTF8(&sb, val);
}
luaL_pushresult(&sb);
return pos;
}
static size_t decodeValue(lua_State *L, const char *buf, size_t pos, size_t size, int hidx);
static size_t decodeArray(lua_State *L, const char *buf, size_t pos, size_t size, int hidx) {
int res;
lua_Integer len = 0;
pos = decodeDelimiter(L, buf, pos, size, '[', 0);
pos = decodeDelimiter(L, buf, pos, size, ']', &res);
lua_newtable(L);
if (res) goto done;
checkStack(L);
do {
pos = decodeValue(L, buf, pos, size, hidx);
lua_rawseti(L, -2, ++len);
pos = decodeDelimiter(L, buf, pos, size, ',', &res);
} while (res);
pos = decodeDelimiter(L, buf, pos, size, ']', 0);
done:
lua_pushinteger(L, len);
lua_setfield(L, -2, "__array");
return pos;
}
static size_t decodeObject(lua_State *L, const char *buf, size_t pos, size_t size, int hidx) {
int res;
pos = decodeDelimiter(L, buf, pos, size, '{', 0);
pos = decodeDelimiter(L, buf, pos, size, '}', &res);
lua_newtable(L);
if (res) return pos;
checkStack(L);
do {
pos = decodeString(L, buf, pos, size);
pos = decodeDelimiter(L, buf, pos, size, ':', 0);
pos = decodeValue(L, buf, pos, size, hidx);
lua_rawset(L, -3);
pos = decodeDelimiter(L, buf, pos, size, ',', &res);
} while (res);
pos = decodeDelimiter(L, buf, pos, size, '}', 0);
return pos;
}
static const char *const literals[] = {"null", "false", "true", "nan", "NaN", "-nan", "-NaN", "inf", "Infinity", "-inf", "-Infinity", 0};
#ifndef lua_str2number /* LuaJIT fails to define this macro */
#define lua_str2number(s, p) strtod(s, p)
#endif
static size_t decodeLiteral(lua_State *L, const char *buf, size_t pos, size_t size) {
int len, num, frn;
char str[64];
pos = decodeWhitespace(buf, pos, size);
buf += pos;
for (len = 0, num = 0, frn = 0; pos + len < size; ++len) {
unsigned char c = buf[len];
if ((c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && !strchr(".-+", c)) break;
if (len == sizeof str - 1) goto error;
if (!num && c >= '0' && c <= '9') num = 1;
if (!frn && num && strchr(".eE", c)) {
frn = 1;
if (c == '.') c = localeconv()->decimal_point[0]; /* "Localize" decimal point */
}
str[len] = c;
}
if (!len) luaL_error(L, "literal expected at position %d", pos + 1);
str[len] = 0;
if (num) {
char *end;
#if LUA_VERSION_NUM >= 503
if (!frn) {
lua_Integer i = strtoll(str, &end, 0);
if (*end) goto error;
lua_pushinteger(L, i);
} else
#endif
{
lua_Number n = lua_str2number(str, &end);
if (*end) goto error;
lua_pushnumber(L, n);
}
} else {
int i;
const char *lit;
for (i = 0; (lit = literals[i]) && strcmp(lit, str); ++i);
switch (i) {
case 0: /* null */
lua_pushlightuserdata(L, 0);
break;
case 1: /* false */
lua_pushboolean(L, 0);
break;
case 2: /* true */
lua_pushboolean(L, 1);
break;
#ifdef NAN
case 3: /* nan */
case 4: /* NaN */
lua_pushnumber(L, NAN);
break;
case 5: /* -nan */
case 6: /* -NaN */
lua_pushnumber(L, -NAN);
break;
#endif
#ifdef INFINITY
case 7: /* inf */
case 8: /* Infinity */
lua_pushnumber(L, INFINITY);
break;
case 9: /* -inf */
case 10: /* -Infinity */
lua_pushnumber(L, -INFINITY);
break;
#endif
default:
goto error;
}
}
return pos + len;
error:
luaL_error(L, "invalid literal at position %d", pos + 1);
return 0;
}
static size_t decodeValue(lua_State *L, const char *buf, size_t pos, size_t size, int hidx) {
pos = decodeWhitespace(buf, pos, size);
if (pos >= size) luaL_error(L, "value expected at position %d", pos + 1);
switch (buf[pos]) {
case '"':
pos = decodeString(L, buf, pos, size);
break;
case '[':
pos = decodeArray(L, buf, pos, size, hidx);
break;
case '{':
pos = decodeObject(L, buf, pos, size, hidx);
break;
default:
pos = decodeLiteral(L, buf, pos, size);
break;
}
if (!lua_istable(L, -1) || lua_isnil(L, hidx)) return pos;
lua_pushvalue(L, hidx);
lua_insert(L, -2);
lua_call(L, 1, 1); /* Call handler */
return pos;
}
int json__decode(lua_State *L) {
size_t size;
const char *buf = luaL_checklstring(L, 1, &size);
size_t pos = luaL_optinteger(L, 2, 1) - 1;
luaL_argcheck(L, pos <= size, 2, "value out of range");
lua_settop(L, 3);
lua_pushinteger(L, decodeValue(L, buf, pos, size, 3) + 1);
return 2;
}
/*
** Copyright (C) 2019 Arseny Vakhrushev <arseny.vakhrushev@gmail.com>
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
*/
#include <stdarg.h>
#include <string.h>
#include <locale.h>
#include "json.h"
#define MAXSTACK 1000 /* Arbitrary stack size limit to check for recursion */
typedef struct {
char *buf, data[1024];
size_t pos, size;
} Box;
static void *resizeBox(lua_State *L, Box *box, size_t size) {
void *ud;
lua_Alloc allocf = lua_getallocf(L, &ud);
int dyn = box->buf != box->data; /* Dynamically allocated? */
void *buf = dyn ? allocf(ud, box->buf, box->size, size) : allocf(ud, 0, 0, size);
if (!size) return 0;
if (!buf) luaL_error(L, "cannot allocate buffer");
if (!dyn) memcpy(buf, box->buf, box->pos);
box->buf = buf;
box->size = size;
return buf;
}
static int m__gc(lua_State *L) {
resizeBox(L, lua_touserdata(L, 1), 0);
return 0;
}
static Box *newBox(lua_State *L) {
Box *box = lua_newuserdata(L, sizeof *box);
box->buf = box->data;
box->pos = 0;
box->size = sizeof box->data;
if (luaL_newmetatable(L, MODNAME)) {
lua_pushcfunction(L, m__gc);
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
return box;
}
static void *appendData(lua_State *L, Box *box, size_t size) {
char *buf = box->buf;
size_t pos = box->pos;
size_t old = box->size;
size_t new = pos + size;
if (new > old) { /* Expand buffer */
old <<= 1; /* At least twice the old size */
buf = resizeBox(L, box, new > old ? new : old);
}
box->pos = new;
return buf + pos;
}
static void encodeData(lua_State *L, Box *box, const char *data, size_t size) {
memcpy(appendData(L, box, size), data, size);
}
static void encodeByte(lua_State *L, Box *box, char val) {
encodeData(L, box, &val, 1);
}
static void encodeString(lua_State *L, Box *box, int idx) {
size_t len, i;
const char *str = lua_tolstring(L, idx, &len);
encodeByte(L, box, '"');
for (i = 0; i < len; ++i) {
unsigned char c = str[i];
switch (c) {
case '"':
case '\\':
case '/':
break;
case '\b':
c = 'b';
break;
case '\f':
c = 'f';
break;
case '\n':
c = 'n';
break;
case '\r':
c = 'r';
break;
case '\t':
c = 't';
break;
default:
if (c < 0x20) { /* Control character */
char buf[7];
encodeData(L, box, buf, sprintf(buf, "\\u%04x", c));
continue;
}
goto next;
}
encodeByte(L, box, '\\');
next:
encodeByte(L, box, c);
}
encodeByte(L, box, '"');
}
#define encodeLiteral(L, box, str) encodeData(L, box, str, sizeof(str) - 1)
static int isInteger(lua_State *L, int idx, lua_Integer *val) {
lua_Integer i;
#if LUA_VERSION_NUM < 503
lua_Number n;
if (!lua_isnumber(L, idx)) return 0;
n = lua_tonumber(L, idx);
i = (lua_Integer)n;
if (i != n) return 0;
#else
int res;
i = lua_tointegerx(L, idx, &res);
if (!res) return 0;
#endif
*val = i;
return 1;
}
static int error(lua_State *L, int *nerr, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
lua_pushvfstring(L, fmt, ap);
va_end(ap);
lua_insert(L, -(++(*nerr)));
return 0;
}
static int encodeValue(lua_State *L, Box *box, int idx, const char *ev, int ridx, int *nerr) {
if (luaL_callmeta(L, idx, ev)) lua_replace(L, idx); /* Transform value */
switch (lua_type(L, idx)) {
case LUA_TNIL:
encodeLiteral(L, box, "null");
break;
case LUA_TBOOLEAN:
if (lua_toboolean(L, idx)) encodeLiteral(L, box, "true");
else encodeLiteral(L, box, "false");
break;
case LUA_TNUMBER: {
char buf[64], *s, p = localeconv()->decimal_point[0];
int len;
#if LUA_VERSION_NUM < 503
len = lua_number2str(buf, lua_tonumber(L, idx));
#else
lua_Integer i;
len = isInteger(L, idx, &i) ?
lua_integer2str(buf, sizeof buf, i):
lua_number2str(buf, sizeof buf, lua_tonumber(L, idx));
#endif
if (p != '.' && (s = strchr(buf, p))) *s = '.'; /* "Unlocalize" decimal point */
encodeData(L, box, buf, len);
break;
}
case LUA_TSTRING:
encodeString(L, box, idx);
break;
case LUA_TTABLE: {
lua_Integer i, len = -1;
int top = lua_gettop(L);
if (top >= MAXSTACK) return error(L, nerr, "recursion detected");
if (lua_getmetatable(L, idx)) return error(L, nerr, "table with metatable unexpected");
lua_pushvalue(L, idx);
lua_rawget(L, ridx);
if (lua_toboolean(L, -1)) return error(L, nerr, "circular reference detected");
lua_pushvalue(L, idx);
lua_pushboolean(L, 1);
lua_rawset(L, ridx);
lua_getfield(L, idx, "__array");
if (lua_toboolean(L, -1)) {
if (!isInteger(L, -1, &len)) len = (lua_Integer)lua_rawlen(L, idx);
if (len < 0) len = 0;
}
lua_settop(L, top);
checkStack(L);
if (len != -1) {
encodeByte(L, box, '[');
for (i = 0; i < len; ++i) {
if (i) encodeByte(L, box, ',');
lua_rawgeti(L, idx, i + 1);
if (!encodeValue(L, box, top + 1, ev, ridx, nerr)) return error(L, nerr, "[%d] => ", i + 1);
lua_pop(L, 1);
}
encodeByte(L, box, ']');
} else {
encodeByte(L, box, '{');
for (i = 0, lua_pushnil(L); lua_next(L, idx); lua_pop(L, 1), ++i) {
if (i) encodeByte(L, box, ',');
if (lua_type(L, top + 1) != LUA_TSTRING) return error(L, nerr, "string index expected, got %s", luaL_typename(L, top + 1));
encodeString(L, box, top + 1);
encodeByte(L, box, ':');
if (!encodeValue(L, box, top + 2, ev, ridx, nerr)) return error(L, nerr, "[\"%s\"] => ", lua_tostring(L, top + 1));
}
encodeByte(L, box, '}');
}
lua_pushvalue(L, idx);
lua_pushnil(L);
lua_rawset(L, ridx);
break;
}
case LUA_TLIGHTUSERDATA:
if (!lua_touserdata(L, idx)) {
encodeLiteral(L, box, "null");
break;
} /* Fall through */
default:
return error(L, nerr, "%s unexpected", luaL_typename(L, idx));
}
return 1;
}
int json__encode(lua_State *L) {
Box *box;
const char *ev;
int nerr = 0;
luaL_checkany(L, 1);
ev = luaL_optstring(L, 2, "__toJSON");
lua_settop(L, 2);
lua_newtable(L);
box = newBox(L);
if (!encodeValue(L, box, 1, ev, 3, &nerr)) {
lua_concat(L, nerr);
return luaL_argerror(L, 1, lua_tostring(L, -1));
}
lua_pushlstring(L, box->buf, box->pos);
return 1;
}
/*
** Copyright (C) 2019 Arseny Vakhrushev <arseny.vakhrushev@gmail.com>
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
*/
#include "json.h"
#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __attribute__((visibility("default")))
#endif
EXPORT int luaopen_json(lua_State *L);
static const luaL_Reg funcs[] = {
{"encode", json__encode},
{"decode", json__decode},
{0, 0}
};
int luaopen_json(lua_State *L) {
#if LUA_VERSION_NUM < 502
luaL_register(L, lua_tostring(L, 1), funcs);
#else
luaL_newlib(L, funcs);
#endif
lua_pushliteral(L, MODNAME);
lua_setfield(L, -2, "_NAME");
lua_pushliteral(L, VERSION);
lua_setfield(L, -2, "_VERSION");
lua_pushlightuserdata(L, 0);
lua_setfield(L, -2, "null");
return 1;
}
/*
** Copyright (C) 2019 Arseny Vakhrushev <arseny.vakhrushev@gmail.com>
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
*/
#pragma once
#include <lauxlib.h>
#ifndef _WIN32
#pragma GCC visibility push(hidden)
#endif
#define MODNAME "lua-json"
#define VERSION "0.1.0"
#define checkStack(L) luaL_checkstack(L, LUA_MINSTACK, "too many nested values")
#if LUA_VERSION_NUM < 502
#define lua_rawlen(L, idx) lua_objlen(L, idx)
#endif
int json__encode(lua_State *L);
int json__decode(lua_State *L);
#ifndef _WIN32
#pragma GCC visibility pop
#endif
local json = require 'json'
local function copy(t)
local r = {}
for k, v in pairs(t) do
r[k] = v
end
return r
end
local mt = {__toJSON = function (t) return copy(t) end}
local nums = {
0.110001,
0.12345678910111,
0.412454033640,
2.6651441426902,
2.718281828459,
3.1415926535898,
2.1406926327793,
}
local vals = {
function () return json.null end, -- Null
function () return math.random() < 0.5 end, -- Boolean
function () return math.random(-2147483648, 2147483647) end, -- Integer
function () return nums[math.random(#nums)] end, -- Double
function () -- String
local t = {}
for i = 1, math.random(0, 10) do
t[i] = string.char(math.random(0, 255))
end
return table.concat(t)
end,
}
local refs, any
local objs = {
function () -- Reference
local n = #refs
return n > 0 and refs[math.random(n)] or json.null
end,
function (d) -- Array
local n = math.random(0, 10)
local t = setmetatable({__array = n}, mt)
for i = 1, n do
t[i] = any(d + 1)
end
table.insert(refs, t)
return t
end,
function (d) -- Object
local t = setmetatable({}, mt)
for i = 1, math.random(0, 10) do
local k = vals[5]() -- Random string key
if #k > 0 then
t[k] = any(d + 1)
end
end
table.insert(refs, t)
return t
end,
}
function any(d)
if d < 4 and math.random() < 0.7 then
return objs[math.random(#objs)](d)
end
return vals[math.random(#vals)]()
end
local function spawn()
refs = {}
return any(0)
end
local function compare(v1, v2)
local r = {}
local function compare(v1, v2)
if type(v1) ~= 'table' or type(v2) ~= 'table' then
return v1 == v2
end
if v1 == v2 then
return true
end
if not compare(getmetatable(v1), getmetatable(v2)) then
return false
end
if r[v1] and r[v2] then
return true
end
r[v1] = true
r[v2] = true
local function find(t, xk, xv)
if t[xk] == xv then
return true
end
for k, v in pairs(t) do
if compare(k, xk) and compare(v, xv) then
return true
end
end
end
for k, v in pairs(v1) do
if not find(v2, k, v) then
return false
end
end
for k, v in pairs(v2) do
if not find(v1, k, v) then
return false
end
end
r[v1] = nil
r[v2] = nil
return true
end
return compare(v1, v2)
end
local function handler(t)
return setmetatable(t, mt)
end
math.randomseed(os.time())
-----------------
-- Stress test --
-----------------
for i = 1, 1000 do
local obj = spawn()
local str = json.encode(obj)
local obj_, pos = json.decode(str, nil, handler)
assert(compare(obj, obj_))
assert(pos == #str + 1)
-- Extra robustness test
for pos = 2, pos do
pcall(json.decode, str, pos)
end
end
---------------------
-- Compliance test --
---------------------
-- TODO
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment