From c92ecda53337490633c95e6ae00e322dc9ad1fb8 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Mon, 10 Jun 2024 22:39:55 -0300 Subject: feature: Lua 5.3 + 5.4 integer support, with CI and conflicts fixed. Co-Authored-By: Hisham Muhammad Co-authored-by: Mark Pulford Co-authored-by: ichenq Co-authored-by: Cloud Wu Co-authored-by: caijietao Co-authored-by: actboy168 Co-authored-by: wudeng Co-authored-by: caiyiheng --- .github/workflows/test.yml | 4 +- .gitignore | 1 + CMakeLists.txt | 7 + Makefile | 8 +- build-packages.sh | 2 +- dtoa.c | 111 ++++++-- fpconv.c | 2 +- lua-cjson-2.1.0.12-1.rockspec | 60 ----- lua-cjson-2.1.0.14-1.rockspec | 60 +++++ lua-cjson.spec | 2 +- lua_cjson.c | 104 +++++-- manual.adoc | 613 ++++++++++++++++++++++++++++++++++++++++++ manual.txt | 612 ----------------------------------------- performance.adoc | 89 ++++++ performance.txt | 89 ------ strbuf.c | 6 +- tests/agentzh.t | 42 +++ tests/genutf8.pl | 1 + 18 files changed, 988 insertions(+), 825 deletions(-) delete mode 100644 lua-cjson-2.1.0.12-1.rockspec create mode 100644 lua-cjson-2.1.0.14-1.rockspec create mode 100644 manual.adoc delete mode 100644 manual.txt create mode 100644 performance.adoc delete mode 100644 performance.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74fdf1f..5567f63 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: runtestArgs: "LUA_INCLUDE_DIR=.lua/include/luajit-2.1" runtestEnv: "SKIP_CMAKE=1" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@master @@ -47,7 +47,7 @@ jobs: sudo cpanm --notest Test::Base Test::LongString - name: cppcheck - run: cppcheck -i .lua/ -i .install/ -i dtoa.c --force --error-exitcode=1 --enable=warning . + run: cppcheck -i .lua/ -i .install/ -i dtoa.c --force --error-exitcode=1 --enable=warning --inline-suppr . - name: prove run: LUA_BIN=lua prove -Itests tests diff --git a/.gitignore b/.gitignore index b767889..d9b1095 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.html *.o *.so +*.a notes packages tags diff --git a/CMakeLists.txt b/CMakeLists.txt index f7eaf89..8ac6d38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,13 @@ else() set(_lua_module_dir "${_lua_lib_dir}/lua/5.1") endif() +if(MSVC) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(-Dinline=__inline) + add_definitions(-Dsnprintf=_snprintf) + add_definitions(-Dstrncasecmp=_strnicmp) +endif() + add_library(cjson MODULE lua_cjson.c strbuf.c ${FPCONV_SOURCES}) set_target_properties(cjson PROPERTIES PREFIX "") target_link_libraries(cjson ${_MODULE_LINK}) diff --git a/Makefile b/Makefile index 55c2142..c5966c5 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,8 @@ LUA_CMODULE_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) LUA_MODULE_DIR ?= $(PREFIX)/share/lua/$(LUA_VERSION) LUA_BIN_DIR ?= $(PREFIX)/bin +AR= $(CC) -o + ##### Platform overrides ##### ## ## Tweak one of the platform sections below to suit your situation. @@ -84,12 +86,12 @@ OBJS = lua_cjson.o strbuf.o $(FPCONV_OBJS) .PHONY: all clean install install-extra doc -.SUFFIXES: .html .txt +.SUFFIXES: .html .adoc .c.o: $(CC) -c $(CFLAGS) $(CPPFLAGS) $(BUILD_CFLAGS) -o $@ $< -.txt.html: +.adoc.html: $(ASCIIDOC) -n -a toc $< all: $(TARGET) @@ -97,7 +99,7 @@ all: $(TARGET) doc: manual.html performance.html $(TARGET): $(OBJS) - $(CC) $(LDFLAGS) $(CJSON_LDFLAGS) -o $@ $(OBJS) + $(AR) $@ $(LDFLAGS) $(CJSON_LDFLAGS) $(OBJS) install: $(TARGET) mkdir -p $(DESTDIR)$(LUA_CMODULE_DIR) diff --git a/build-packages.sh b/build-packages.sh index 23a6f2e..2c45e66 100755 --- a/build-packages.sh +++ b/build-packages.sh @@ -5,7 +5,7 @@ # Build packages. Use current checked out version, or a specific tag/commit. # Files requiring a version bump -VERSION_FILES="lua-cjson-2.1devel-1.rockspec lua-cjson.spec lua_cjson.c manual.txt runtests.sh tests/test.lua" +VERSION_FILES="lua-cjson-2.1devel-1.rockspec lua-cjson.spec lua_cjson.c manual.adoc runtests.sh tests/test.lua" [ "$1" ] && BRANCH="$1" || BRANCH="`git describe --match '[1-3].[0-9]*'`" VERSION="`git describe --match '[1-3].[0-9]*' $BRANCH`" diff --git a/dtoa.c b/dtoa.c index 8dac7b8..33fd6f0 100644 --- a/dtoa.c +++ b/dtoa.c @@ -1533,12 +1533,18 @@ ThInfo { set_max_dtoa_threads(unsigned int n) { size_t L; + ThInfo *newTI1; if (n > maxthreads) { L = n*sizeof(ThInfo); if (TI1) { - TI1 = (ThInfo*)REALLOC(TI1, L); - memset(TI1 + maxthreads, 0, (n-maxthreads)*sizeof(ThInfo)); + newTI1 = (ThInfo*)REALLOC(TI1, L); + if (newTI1) { + TI1 = newTI1; + memset(TI1 + maxthreads, 0, (n-maxthreads)*sizeof(ThInfo)); + } + else + return; } else { TI1 = (ThInfo*)MALLOC(L); @@ -1871,7 +1877,7 @@ mult(Bigint *a, Bigint *b MTd) #else #ifdef Pack_32 for(; xb < xbe; xb++, xc0++) { - if (y = *xb & 0xffff) { + if ((y = *xb & 0xffff)) { x = xa; xc = xc0; carry = 0; @@ -1885,7 +1891,7 @@ mult(Bigint *a, Bigint *b MTd) while(x < xae); *xc = carry; } - if (y = *xb >> 16) { + if ((y = *xb >> 16)) { x = xa; xc = xc0; carry = 0; @@ -2718,13 +2724,14 @@ enum { /* rounding values: same as FLT_ROUNDS */ }; void -gethex( const char **sp, U *rvp, int rounding, int sign MTd) +gethex(const char **sp, U *rvp, int rounding, int sign MTd) { Bigint *b; + char d; const unsigned char *decpt, *s0, *s, *s1; Long e, e1; ULong L, lostbits, *x; - int big, denorm, esign, havedig, k, n, nbits, up, zret; + int big, denorm, esign, havedig, k, n, nb, nbits, nz, up, zret; #ifdef IBM int j; #endif @@ -2742,6 +2749,9 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd) #endif #endif /*}}*/ }; +#ifdef IEEE_Arith + int check_denorm = 0; +#endif #ifdef USE_LOCALE int i; #ifdef NO_LOCALE_CACHE @@ -2893,7 +2903,7 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd) k++; b = Balloc(k MTa); x = b->x; - n = 0; + havedig = n = nz = 0; L = 0; #ifdef USE_LOCALE for(i = 0; decimalpoint[i+1]; ++i); @@ -2908,22 +2918,28 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd) if (*--s1 == '.') continue; #endif + if ((d = hexdig[*s1])) + havedig = 1; + else if (!havedig) { + e += 4; + continue; + } if (n == ULbits) { *x++ = L; L = 0; n = 0; } - L |= (hexdig[*s1] & 0x0f) << n; + L |= (d & 0x0f) << n; n += 4; } *x++ = L; b->wds = n = x - b->x; - n = ULbits*n - hi0bits(L); + nb = ULbits*n - hi0bits(L); nbits = Nbits; lostbits = 0; x = b->x; - if (n > nbits) { - n -= nbits; + if (nb > nbits) { + n = nb - nbits; if (any_on(b,n)) { lostbits = 1; k = n - 1; @@ -2936,8 +2952,8 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd) rshift(b, n); e += n; } - else if (n < nbits) { - n = nbits - n; + else if (nb < nbits) { + n = nbits - nb; b = lshift(b, n MTa); e -= n; x = b->x; @@ -2992,12 +3008,49 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd) return; } k = n - 1; +#ifdef IEEE_Arith + if (!k) { + switch(rounding) { + case Round_near: + if (((b->x[0] & 3) == 3) || (lostbits && (b->x[0] & 1))) { + multadd(b, 1, 1 MTa); + emin_check: + if (b->x[1] == (1 << (Exp_shift + 1))) { + rshift(b,1); + e = emin; + goto normal; + } + } + break; + case Round_up: + if (!sign && (lostbits || (b->x[0] & 1))) { + incr_denorm: + multadd(b, 1, 2 MTa); + check_denorm = 1; + lostbits = 0; + goto emin_check; + } + break; + case Round_down: + if (sign && (lostbits || (b->x[0] & 1))) + goto incr_denorm; + break; + } + } +#endif if (lostbits) lostbits = 1; else if (k > 0) lostbits = any_on(b,k); +#ifdef IEEE_Arith + else if (check_denorm) + goto no_lostbits; +#endif if (x[k>>kshift] & 1 << (k & kmask)) lostbits |= 2; +#ifdef IEEE_Arith + no_lostbits: +#endif nbits -= n; rshift(b,n); e = emin; @@ -3022,16 +3075,9 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd) k = b->wds; b = increment(b MTa); x = b->x; - if (denorm) { -#if 0 - if (nbits == Nbits - 1 - && x[nbits >> kshift] & 1 << (nbits & kmask)) - denorm = 0; /* not currently used */ -#endif - } - else if (b->wds > k + if (!denorm && (b->wds > k || ((n = nbits & kmask) !=0 - && hi0bits(x[k-1]) < 32-n)) { + && hi0bits(x[k-1]) < 32-n))) { rshift(b,1); if (++e > Emax) goto ovfl; @@ -3041,8 +3087,10 @@ gethex( const char **sp, U *rvp, int rounding, int sign MTd) #ifdef IEEE_Arith if (denorm) word0(rvp) = b->wds > 1 ? b->x[1] & ~0x100000 : 0; - else + else { + normal: word0(rvp) = (b->x[1] & ~0x100000) | ((e + 0x3ff + 52) << 20); + } word1(rvp) = b->x[0]; #endif #ifdef IBM @@ -3409,6 +3457,7 @@ retlow1: if ((j = ((word0(rv) & Exp_mask) >> Exp_shift) - bc->scale) <= 0) { i = 1 - j; if (i <= 31) { + /* cppcheck-suppress integerOverflowCond */ if (word1(rv) & (0x1 << i)) goto odd; } @@ -3619,10 +3668,11 @@ fpconv_strtod(const char *s00, char **se) c = *++s; if (c > '0' && c <= '9') { L = c - '0'; - s1 = s; - while((c = *++s) >= '0' && c <= '9') - L = 10*L + c - '0'; - if (s - s1 > 8 || L > 19999) + while((c = *++s) >= '0' && c <= '9') { + if (L <= 19999) + L = 10*L + c - '0'; + } + if (L > 19999) /* Avoid confusion from exponents * so large that e might overflow. */ @@ -4884,6 +4934,7 @@ nrv_alloc(const char *s, char *s0, size_t s0len, char **rve, int n MTd) s0 = rv_alloc(n MTa); else if (s0len <= n) { rv = 0; + /* cppcheck-suppress nullPointerArithmetic */ t = rv + n; goto rve_chk; } @@ -5237,9 +5288,11 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char #ifndef SET_INEXACT #ifdef Check_FLT_ROUNDS try_quick = Rounding == 1; +#else + try_quick = 1; #endif #endif /*SET_INEXACT*/ -#endif +#endif /*USE_BF96*/ if (mode > 5) { mode -= 4; @@ -5281,6 +5334,7 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char else if (blen <= i) { buf = 0; if (rve) + /* cppcheck-suppress nullPointerArithmetic */ *rve = buf + i; return buf; } @@ -5469,6 +5523,7 @@ dtoa_r(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve, char res3 = p10->b1 * dbhi + (tv3 & 0xffffffffull); res = p10->b0 * dbhi + (tv3>>32) + (res3>>32); be += p10->e - 0x3fe; + /* cppcheck-suppress integerOverflowCond */ eulp = j1 = be - 54 + ulpadj; if (!(res & 0x8000000000000000ull)) { --be; diff --git a/fpconv.c b/fpconv.c index 0ef7f18..64208f8 100644 --- a/fpconv.c +++ b/fpconv.c @@ -130,7 +130,7 @@ double fpconv_strtod(const char *nptr, char **endptr) /* Duplicate number into buffer */ if (buflen >= FPCONV_G_FMT_BUFSIZE) { /* Handle unusually large numbers */ - buf = malloc(buflen + 1); + buf = (char *)malloc(buflen + 1); if (!buf) { fprintf(stderr, "Out of memory"); abort(); diff --git a/lua-cjson-2.1.0.12-1.rockspec b/lua-cjson-2.1.0.12-1.rockspec deleted file mode 100644 index 9ed4272..0000000 --- a/lua-cjson-2.1.0.12-1.rockspec +++ /dev/null @@ -1,60 +0,0 @@ -package = "lua-cjson" -version = "2.1.0.11-1" - -source = { - url = "git+https://github.com/openresty/lua-cjson", - tag = "2.1.0.11", -} - -description = { - summary = "A fast JSON encoding/parsing module", - detailed = [[ - The Lua CJSON module provides JSON support for Lua. It features: - - Fast, standards compliant encoding/parsing routines - - Full support for JSON with UTF-8, including decoding surrogate pairs - - Optional run-time support for common exceptions to the JSON specification - (infinity, NaN,..) - - No dependencies on other libraries - ]], - homepage = "http://www.kyne.com.au/~mark/software/lua-cjson.php", - license = "MIT" -} - -dependencies = { - "lua >= 5.1" -} - -build = { - type = "builtin", - modules = { - cjson = { - sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" }, - defines = { --- LuaRocks does not support platform specific configuration for Solaris. --- Uncomment the line below on Solaris platforms if required. --- "USE_INTERNAL_ISINF" - } - }, - ["cjson.safe"] = { - sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" } - } - }, - install = { - lua = { - ["cjson.util"] = "lua/cjson/util.lua" - }, - bin = { - json2lua = "lua/json2lua.lua", - lua2json = "lua/lua2json.lua" - } - }, - -- Override default build options (per platform) - platforms = { - win32 = { modules = { cjson = { defines = { - "DISABLE_INVALID_NUMBERS", "USE_INTERNAL_ISINF" - } } } } - }, - copy_directories = { "tests" } -} - --- vi:ai et sw=4 ts=4: diff --git a/lua-cjson-2.1.0.14-1.rockspec b/lua-cjson-2.1.0.14-1.rockspec new file mode 100644 index 0000000..4376a08 --- /dev/null +++ b/lua-cjson-2.1.0.14-1.rockspec @@ -0,0 +1,60 @@ +package = "lua-cjson" +version = "2.1.0.14-1" + +source = { + url = "git+https://github.com/openresty/lua-cjson", + tag = "2.1.0.14", +} + +description = { + summary = "A fast JSON encoding/parsing module", + detailed = [[ + The Lua CJSON module provides JSON support for Lua. It features: + - Fast, standards compliant encoding/parsing routines + - Full support for JSON with UTF-8, including decoding surrogate pairs + - Optional run-time support for common exceptions to the JSON specification + (infinity, NaN,..) + - No dependencies on other libraries + ]], + homepage = "http://www.kyne.com.au/~mark/software/lua-cjson.php", + license = "MIT" +} + +dependencies = { + "lua >= 5.1" +} + +build = { + type = "builtin", + modules = { + cjson = { + sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" }, + defines = { +-- LuaRocks does not support platform specific configuration for Solaris. +-- Uncomment the line below on Solaris platforms if required. +-- "USE_INTERNAL_ISINF" + } + }, + ["cjson.safe"] = { + sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" } + } + }, + install = { + lua = { + ["cjson.util"] = "lua/cjson/util.lua" + }, + bin = { + json2lua = "lua/json2lua.lua", + lua2json = "lua/lua2json.lua" + } + }, + -- Override default build options (per platform) + platforms = { + win32 = { modules = { cjson = { defines = { + "DISABLE_INVALID_NUMBERS", "USE_INTERNAL_ISINF" + } } } } + }, + copy_directories = { "tests" } +} + +-- vi:ai et sw=4 ts=4: diff --git a/lua-cjson.spec b/lua-cjson.spec index 13fc56d..3d3cb16 100644 --- a/lua-cjson.spec +++ b/lua-cjson.spec @@ -50,7 +50,7 @@ rm -rf "$RPM_BUILD_ROOT" %files %defattr(-,root,root,-) -%doc LICENSE NEWS performance.html performance.txt manual.html manual.txt rfc4627.txt THANKS +%doc LICENSE NEWS performance.html performance.adoc manual.html manual.adoc rfc4627.txt THANKS %{lualibdir}/* %{luadatadir}/* %{_bindir}/* diff --git a/lua_cjson.c b/lua_cjson.c index ae178eb..bbd8eff 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -66,6 +66,13 @@ #endif +#ifdef _MSC_VER +#define CJSON_EXPORT __declspec(dllexport) +#define strncasecmp(x,y,z) _strnicmp(x,y,z) +#else +#define CJSON_EXPORT extern +#endif + /* Workaround for Solaris platforms missing isinf() */ #if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF)) #define isinf(x) (!isnan(x) && isnan((x) - (x))) @@ -104,8 +111,8 @@ #define json_lightudata_mask(ludata) (ludata) #endif -#if LUA_VERSION_NUM > 501 -#define lua_objlen(L,i) lua_rawlen(L, (i)) +#if LUA_VERSION_NUM >= 502 +#define lua_objlen(L,i) luaL_len(L, (i)) #endif static const char * const *json_empty_array; @@ -118,6 +125,7 @@ typedef enum { T_ARR_END, T_STRING, T_NUMBER, + T_INTEGER, T_BOOLEAN, T_NULL, T_COLON, @@ -135,6 +143,7 @@ static const char *json_token_type_name[] = { "T_ARR_END", "T_STRING", "T_NUMBER", + "T_INTEGER", "T_BOOLEAN", "T_NULL", "T_COLON", @@ -184,6 +193,7 @@ typedef struct { union { const char *string; double number; + lua_Integer integer; int boolean; } value; size_t string_len; @@ -234,7 +244,7 @@ static json_config_t *json_fetch_config(lua_State *l) { json_config_t *cfg; - cfg = lua_touserdata(l, lua_upvalueindex(1)); + cfg = (json_config_t *)lua_touserdata(l, lua_upvalueindex(1)); if (!cfg) luaL_error(l, "BUG: Unable to fetch CJSON configuration"); @@ -443,7 +453,7 @@ static int json_destroy_config(lua_State *l) { json_config_t *cfg; - cfg = lua_touserdata(l, 1); + cfg = (json_config_t *)lua_touserdata(l, 1); if (cfg) strbuf_free(&cfg->encode_buf); cfg = NULL; @@ -456,7 +466,7 @@ static void json_create_config(lua_State *l) json_config_t *cfg; int i; - cfg = lua_newuserdata(l, sizeof(*cfg)); + cfg = (json_config_t *)lua_newuserdata(l, sizeof(*cfg)); if (!cfg) abort(); @@ -552,9 +562,9 @@ static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *js static void json_append_string(lua_State *l, strbuf_t *json, int lindex) { const char *escstr; - unsigned i; const char *str; size_t len; + size_t i; str = lua_tolstring(l, lindex, &len); @@ -653,9 +663,9 @@ static int json_append_data(lua_State *l, json_config_t *cfg, /* json_append_array args: * - lua_State * - JSON strbuf - * - Size of passwd Lua array (top of stack) */ + * - Size of passed Lua array (top of stack) */ static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth, - strbuf_t *json, int array_length) + strbuf_t *json, int array_length, int raw) { int comma, i, json_pos, err; @@ -667,7 +677,17 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept if (comma++ > 0) strbuf_append_char(json, ','); - lua_rawgeti(l, -1, i); + if (raw) { + lua_rawgeti(l, -1, i); + } else { +#if LUA_VERSION_NUM >= 503 + lua_geti(l, -1, i); +#else + lua_pushinteger(l, i); + lua_gettable(l, -2); +#endif + } + err = json_append_data(l, cfg, current_depth, json); if (err) { strbuf_set_length(json, json_pos); @@ -684,8 +704,17 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept static void json_append_number(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex) { - double num = lua_tonumber(l, lindex); int len; +#if LUA_VERSION_NUM >= 503 + if (lua_isinteger(l, lindex)) { + lua_Integer num = lua_tointeger(l, lindex); + strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); /* max length of int64 is 19 */ + len = sprintf(strbuf_empty_ptr(json), LUA_INTEGER_FMT, num); + strbuf_extend_length(json, len); + return; + } +#endif + double num = lua_tonumber(l, lindex); if (cfg->encode_invalid_numbers == 0) { /* Prevent encoding invalid numbers */ @@ -773,6 +802,7 @@ static int json_append_data(lua_State *l, json_config_t *cfg, int len; int as_array = 0; int has_metatable; + int raw = 1; switch (lua_type(l, -1)) { case LUA_TSTRING: @@ -797,17 +827,30 @@ static int json_append_data(lua_State *l, json_config_t *cfg, lua_pushlightuserdata(l, json_lightudata_mask(&json_array)); lua_rawget(l, LUA_REGISTRYINDEX); as_array = lua_rawequal(l, -1, -2); - lua_pop(l, 2); + if (as_array) { + raw = 1; + lua_pop(l, 2); + len = lua_objlen(l, -1); + } else { + raw = 0; + lua_pop(l, 2); + if (luaL_getmetafield(l, -1, "__len")) { + lua_pushvalue(l, -2); + lua_call(l, 1, 1); + len = lua_tonumber(l, -1); + lua_pop(l, 1); + as_array = 1; + } + } } if (as_array) { - len = lua_objlen(l, -1); - json_append_array(l, cfg, current_depth, json, len); + json_append_array(l, cfg, current_depth, json, len, raw); } else { len = lua_array_length(l, cfg, json); if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object)) { - json_append_array(l, cfg, current_depth, json, len); + json_append_array(l, cfg, current_depth, json, len, raw); } else { if (has_metatable) { lua_getmetatable(l, -1); @@ -817,7 +860,9 @@ static int json_append_data(lua_State *l, json_config_t *cfg, as_array = lua_rawequal(l, -1, -2); lua_pop(l, 2); /* pop pointer + metatable */ if (as_array) { - json_append_array(l, cfg, current_depth, json, 0); + len = lua_objlen(l, -1); + raw = 1; + json_append_array(l, cfg, current_depth, json, len, raw); break; } } @@ -832,7 +877,7 @@ static int json_append_data(lua_State *l, json_config_t *cfg, if (lua_touserdata(l, -1) == NULL) { strbuf_append_mem(json, "null", 4); } else if (lua_touserdata(l, -1) == json_lightudata_mask(&json_array)) { - json_append_array(l, cfg, current_depth, json, 0); + json_append_array(l, cfg, current_depth, json, 0, 1); } break; default: @@ -1145,13 +1190,19 @@ static int json_is_invalid_number(json_parse_t *json) static void json_next_number_token(json_parse_t *json, json_token_t *token) { char *endptr; - - token->type = T_NUMBER; - token->value.number = fpconv_strtod(json->ptr, &endptr); - if (json->ptr == endptr) - json_set_token_error(token, json, "invalid number"); - else - json->ptr = endptr; /* Skip the processed number */ + token->value.integer = strtoll(json->ptr, &endptr, 10); + if (json->ptr == endptr || *endptr == '.' || *endptr == 'e' || + *endptr == 'E' || *endptr == 'x') { + token->type = T_NUMBER; + token->value.number = fpconv_strtod(json->ptr, &endptr); + if (json->ptr == endptr) { + json_set_token_error(token, json, "invalid number"); + return; + } + } else { + token->type = T_INTEGER; + } + json->ptr = endptr; /* Skip the processed number */ return; } @@ -1387,6 +1438,9 @@ static void json_process_value(lua_State *l, json_parse_t *json, case T_NUMBER: lua_pushnumber(l, token->value.number); break;; + case T_INTEGER: + lua_pushinteger(l, token->value.integer); + break;; case T_BOOLEAN: lua_pushboolean(l, token->value.boolean); break;; @@ -1602,7 +1656,7 @@ static int lua_cjson_safe_new(lua_State *l) return 1; } -int luaopen_cjson(lua_State *l) +CJSON_EXPORT int luaopen_cjson(lua_State *l) { lua_cjson_new(l); @@ -1616,7 +1670,7 @@ int luaopen_cjson(lua_State *l) return 1; } -int luaopen_cjson_safe(lua_State *l) +CJSON_EXPORT int luaopen_cjson_safe(lua_State *l) { lua_cjson_safe_new(l); diff --git a/manual.adoc b/manual.adoc new file mode 100644 index 0000000..83303a3 --- /dev/null +++ b/manual.adoc @@ -0,0 +1,613 @@ += Lua CJSON 2.1devel Manual = +Mark Pulford +:revdate: August 2016 + +Overview +-------- + +The Lua CJSON module provides JSON support for Lua. + +*Features*:: +- Fast, standards compliant encoding/parsing routines +- Full support for JSON with UTF-8, including decoding surrogate pairs +- Optional run-time support for common exceptions to the JSON + specification (infinity, NaN,..) +- No dependencies on other libraries + +*Caveats*:: +- UTF-16 and UTF-32 are not supported + +Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for +details. + +The current stable version of this software is available from the +http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. + +Feel free to email me if you have any patches, suggestions, or comments. + + +Installation +------------ + +Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, Lua 5.3, +or http://www.luajit.org[LuaJIT] to build. + +The build method can be selected from 4 options: + +Make:: Unix (including Linux, BSD, Mac OSX & Solaris), Windows +CMake:: Unix, Windows +RPM:: Linux +LuaRocks:: Unix, Windows + + +Make +~~~~ + +The included +Makefile+ has generic settings. + +First, review and update the included makefile to suit your platform (if +required). + +Next, build and install the module: + +[source,sh] +make install + +Or install manually into your Lua module directory: + +[source,sh] +make +cp cjson.so $LUA_MODULE_DIRECTORY + + +CMake +~~~~~ + +http://www.cmake.org[CMake] can generate build configuration for many +different platforms (including Unix and Windows). + +First, generate the makefile for your platform using CMake. If CMake is +unable to find Lua, manually set the +LUA_DIR+ environment variable to +the base prefix of your Lua 5.1 installation. + +While +cmake+ is used in the example below, +ccmake+ or +cmake-gui+ may +be used to present an interface for changing the default build options. + +[source,sh] +mkdir build +cd build +# Optional: export LUA_DIR=$LUA51_PREFIX +cmake .. + +Next, build and install the module: + +[source,sh] +make install +# Or: +make +cp cjson.so $LUA_MODULE_DIRECTORY + +Review the +http://www.cmake.org/cmake/help/documentation.html[CMake documentation] +for further details. + + +RPM +~~~ + +Linux distributions using http://rpm.org[RPM] can create a package via +the included RPM spec file. Ensure the +rpm-build+ package (or similar) +has been installed. + +Build and install the module via RPM: + +[source,sh] +rpmbuild -tb lua-cjson-2.1devel.tar.gz +rpm -Uvh $LUA_CJSON_RPM + + +LuaRocks +~~~~~~~~ + +http://luarocks.org[LuaRocks] can be used to install and manage Lua +modules on a wide range of platforms (including Windows). + +First, extract the Lua CJSON source package. + +Next, install the module: + +[source,sh] +cd lua-cjson-2.1devel +luarocks make + +[NOTE] +LuaRocks does not support platform specific configuration for Solaris. +On Solaris, you may need to manually uncomment +USE_INTERNAL_ISINF+ in +the rockspec before building this module. + +Review the http://luarocks.org/en/Documentation[LuaRocks documentation] +for further details. + + +[[build_options]] +Build Options (#define) +~~~~~~~~~~~~~~~~~~~~~~~ + +Lua CJSON offers several +#define+ build options to address portability +issues, and enable non-default features. Some build methods may +automatically set platform specific options if required. Other features +should be enabled manually. + +USE_INTERNAL_ISINF:: Workaround for Solaris platforms missing +isinf+. +DISABLE_INVALID_NUMBERS:: Recommended on platforms where +strtod+ / + +sprintf+ are not POSIX compliant (eg, Windows MinGW). Prevents + +cjson.encode_invalid_numbers+ and +cjson.decode_invalid_numbers+ from + being enabled. However, +cjson.encode_invalid_numbers+ may still be + set to +"null"+. When using the Lua CJSON built-in floating point + conversion this option is unnecessary and is ignored. + + +Built-in floating point conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Lua CJSON may be built with David Gay's +http://www.netlib.org/fp/[floating point conversion routines]. This can +increase overall performance by up to 50% on some platforms when +converting a large amount of numeric data. However, this option reduces +portability and is disabled by default. + +USE_INTERNAL_FPCONV:: Enable internal number conversion routines. +IEEE_BIG_ENDIAN:: Must be set on big endian architectures. +MULTIPLE_THREADS:: Must be set if Lua CJSON may be used in a + multi-threaded application. Requires the _pthreads_ library. + + +API (Functions) +--------------- + +Synopsis +~~~~~~~~ + +[source,lua] +------------ +-- Module instantiation +local cjson = require "cjson" +local cjson2 = cjson.new() +local cjson_safe = require "cjson.safe" + +-- Translate Lua value to/from JSON +text = cjson.encode(value) +value = cjson.decode(text) + +-- Get and/or set Lua CJSON configuration +setting = cjson.decode_invalid_numbers([setting]) +setting = cjson.encode_invalid_numbers([setting]) +keep = cjson.encode_keep_buffer([keep]) +depth = cjson.encode_max_depth([depth]) +depth = cjson.decode_max_depth([depth]) +convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) +------------ + + +Module Instantiation +~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +local cjson = require "cjson" +local cjson2 = cjson.new() +local cjson_safe = require "cjson.safe" +------------ + +Import Lua CJSON via the Lua +require+ function. Lua CJSON does not +register a global module table. + +The +cjson+ module will throw an error during JSON conversion if any +invalid data is encountered. Refer to <> and +<> for details. + +The +cjson.safe+ module behaves identically to the +cjson+ module, +except when errors are encountered during JSON conversion. On error, the ++cjson_safe.encode+ and +cjson_safe.decode+ functions will return ++nil+ followed by the error message. + ++cjson.new+ can be used to instantiate an independent copy of the Lua +CJSON module. The new module has a separate persistent encoding buffer, +and default settings. + +Lua CJSON can support Lua implementations using multiple preemptive +threads within a single Lua state provided the persistent encoding +buffer is not shared. This can be achieved by one of the following +methods: + +- Disabling the persistent encoding buffer with + <> +- Ensuring each thread calls <> separately (ie, + treat +cjson.encode+ as non-reentrant). +- Using a separate +cjson+ module table per preemptive thread + (+cjson.new+) + +[NOTE] +Lua CJSON uses +strtod+ and +snprintf+ to perform numeric conversion as +they are usually well supported, fast and bug free. However, these +functions require a workaround for JSON encoding/parsing under locales +using a comma decimal separator. Lua CJSON detects the current locale +during instantiation to determine and automatically implement the +workaround if required. Lua CJSON should be reinitialised via ++cjson.new+ if the locale of the current process changes. Using a +different locale per thread is not supported. + + +[[decode]] +decode +~~~~~~ + +[source,lua] +------------ +value = cjson.decode(json_text) +------------ + ++cjson.decode+ will deserialise any UTF-8 JSON string into a Lua value +or table. + +UTF-16 and UTF-32 JSON strings are not supported. + ++cjson.decode+ requires that any NULL (ASCII 0) and double quote (ASCII +34) characters are escaped within strings. All escape codes will be +decoded and other bytes will be passed transparently. UTF-8 characters +are not validated during decoding and should be checked elsewhere if +required. + +JSON +null+ will be converted to a NULL +lightuserdata+ value. This can +be compared with +cjson.null+ for convenience. + +By default, numbers incompatible with the JSON specification (infinity, +NaN, hexadecimal) can be decoded. This default can be changed with +<>. + +.Example: Decoding +[source,lua] +json_text = '[ true, { "foo": "bar" } ]' +value = cjson.decode(json_text) +-- Returns: { true, { foo = "bar" } } + +[CAUTION] +Care must be taken after decoding JSON objects with numeric keys. Each +numeric key will be stored as a Lua +string+. Any subsequent code +assuming type +number+ may break. + + +[[decode_invalid_numbers]] +decode_invalid_numbers +~~~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +setting = cjson.decode_invalid_numbers([setting]) +-- "setting" must be a boolean. Default: true. +------------ + +Lua CJSON may generate an error when trying to decode numbers not +supported by the JSON specification. _Invalid numbers_ are defined as: + +- infinity +- NaN +- hexadecimal + +Available settings: + ++true+:: Accept and decode _invalid numbers_. This is the default + setting. ++false+:: Throw an error when _invalid numbers_ are encountered. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[decode_max_depth]] +decode_max_depth +~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +depth = cjson.decode_max_depth([depth]) +-- "depth" must be a positive integer. Default: 1000. +------------ + +Lua CJSON will generate an error when parsing deeply nested JSON once +the maximum array/object depth has been exceeded. This check prevents +unnecessarily complicated JSON from slowing down the application, or +crashing the application due to lack of process stack space. + +An error may be generated before the depth limit is hit if Lua is unable +to allocate more objects on the Lua stack. + +By default, Lua CJSON will reject JSON with arrays and/or objects nested +more than 1000 levels deep. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[encode]] +encode +~~~~~~ + +[source,lua] +------------ +json_text = cjson.encode(value) +------------ + ++cjson.encode+ will serialise a Lua value into a string containing the +JSON representation. + ++cjson.encode+ supports the following types: + +- +boolean+ +- +lightuserdata+ (NULL value only) +- +nil+ +- +number+ +- +string+ +- +table+ + +The remaining Lua types will generate an error: + +- +function+ +- +lightuserdata+ (non-NULL values) +- +thread+ +- +userdata+ + +By default, numbers are encoded with 14 significant digits. Refer to +<> for details. + +Lua CJSON will escape the following characters within each UTF-8 string: + +- Control characters (ASCII 0 - 31) +- Double quote (ASCII 34) +- Forward slash (ASCII 47) +- Blackslash (ASCII 92) +- Delete (ASCII 127) + +All other bytes are passed transparently. + +[CAUTION] +========= +Lua CJSON will successfully encode/decode binary strings, but this is +technically not supported by JSON and may not be compatible with other +JSON libraries. To ensure the output is valid JSON, applications should +ensure all Lua strings passed to +cjson.encode+ are UTF-8. + +Base64 is commonly used to encode binary data as the most efficient +encoding under UTF-8 can only reduce the encoded size by a further +~8%. Lua Base64 routines can be found in the +http://w3.impa.br/%7Ediego/software/luasocket/[LuaSocket] and +http://www.tecgraf.puc-rio.br/%7Elhf/ftp/lua/#lbase64[lbase64] packages. +========= + +Lua CJSON uses a heuristic to determine whether to encode a Lua table as +a JSON array or an object. A Lua table with only positive integer keys +of type +number+ will be encoded as a JSON array. All other tables will +be encoded as a JSON object. + +Lua CJSON does not use metamethods when serialising tables. + +- +rawget+ is used to iterate over Lua arrays +- +next+ is used to iterate over Lua objects + +Lua arrays with missing entries (_sparse arrays_) may optionally be +encoded in several different ways. Refer to +<> for details. + +JSON object keys are always strings. Hence +cjson.encode+ only supports +table keys which are type +number+ or +string+. All other types will +generate an error. + +[NOTE] +Standards compliant JSON must be encapsulated in either an object (+{}+) +or an array (+[]+). If strictly standards compliant JSON is desired, a +table must be passed to +cjson.encode+. + +By default, encoding the following Lua values will generate errors: + +- Numbers incompatible with the JSON specification (infinity, NaN) +- Tables nested more than 1000 levels deep +- Excessively sparse Lua arrays + +These defaults can be changed with: + +- <> +- <> +- <> + +.Example: Encoding +[source,lua] +value = { true, { foo = "bar" } } +json_text = cjson.encode(value) +-- Returns: '[true,{"foo":"bar"}]' + + +[[encode_invalid_numbers]] +encode_invalid_numbers +~~~~~~~~~~~~~~~~~~~~~~ +[source,lua] +------------ +setting = cjson.encode_invalid_numbers([setting]) +-- "setting" must a boolean or "null". Default: false. +------------ + +Lua CJSON may generate an error when encoding floating point numbers not +supported by the JSON specification (_invalid numbers_): + +- infinity +- NaN + +Available settings: + ++true+:: Allow _invalid numbers_ to be encoded using the Javascript + compatible values +NaN+ and +Infinity+. This will generate + non-standard JSON, but these values are supported by some libraries. ++"null"+:: Encode _invalid numbers_ as a JSON +null+ value. This allows + infinity and NaN to be encoded into valid JSON. ++false+:: Throw an error when attempting to encode _invalid numbers_. + This is the default setting. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[encode_keep_buffer]] +encode_keep_buffer +~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +keep = cjson.encode_keep_buffer([keep]) +-- "keep" must be a boolean. Default: true. +------------ + +Lua CJSON can reuse the JSON encoding buffer to improve performance. + +Available settings: + ++true+:: The buffer will grow to the largest size required and is not + freed until the Lua CJSON module is garbage collected. This is the + default setting. ++false+:: Free the encode buffer after each call to +cjson.encode+. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[encode_max_depth]] +encode_max_depth +~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +depth = cjson.encode_max_depth([depth]) +-- "depth" must be a positive integer. Default: 1000. +------------ + +Once the maximum table depth has been exceeded Lua CJSON will generate +an error. This prevents a deeply nested or recursive data structure from +crashing the application. + +By default, Lua CJSON will generate an error when trying to encode data +structures with more than 1000 nested tables. + +The current setting is always returned, and is only updated when an +argument is provided. + +.Example: Recursive Lua table +[source,lua] +a = {}; a[1] = a + + +[[encode_number_precision]] +encode_number_precision +~~~~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +precision = cjson.encode_number_precision([precision]) +-- "precision" must be an integer between 1 and 14. Default: 14. +------------ + +The amount of significant digits returned by Lua CJSON when encoding +numbers can be changed to balance accuracy versus performance. For data +structures containing many numbers, setting ++cjson.encode_number_precision+ to a smaller integer, for example +3+, +can improve encoding performance by up to 50%. + +By default, Lua CJSON will output 14 significant digits when converting +a number to text. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[encode_sparse_array]] +encode_sparse_array +~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) +-- "convert" must be a boolean. Default: false. +-- "ratio" must be a positive integer. Default: 2. +-- "safe" must be a positive integer. Default: 10. +------------ + +Lua CJSON classifies a Lua table into one of three kinds when encoding a +JSON array. This is determined by the number of values missing from the +Lua array as follows: + +Normal:: All values are available. +Sparse:: At least 1 value is missing. +Excessively sparse:: The number of values missing exceeds the configured + ratio. + +Lua CJSON encodes sparse Lua arrays as JSON arrays using JSON +null+ for +the missing entries. + +An array is excessively sparse when all the following conditions are +met: + +- +ratio+ > +0+ +- _maximum_index_ > +safe+ +- _maximum_index_ > _item_count_ * +ratio+ + +Lua CJSON will never consider an array to be _excessively sparse_ when ++ratio+ = +0+. The +safe+ limit ensures that small Lua arrays are always +encoded as sparse arrays. + +By default, attempting to encode an _excessively sparse_ array will +generate an error. If +convert+ is set to +true+, _excessively sparse_ +arrays will be converted to a JSON object. + +The current settings are always returned. A particular setting is only +changed when the argument is provided (non-++nil++). + +.Example: Encoding a sparse array +[source,lua] +cjson.encode({ [3] = "data" }) +-- Returns: '[null,null,"data"]' + +.Example: Enabling conversion to a JSON object +[source,lua] +cjson.encode_sparse_array(true) +cjson.encode({ [1000] = "excessively sparse" }) +-- Returns: '{"1000":"excessively sparse"}' + + +API (Variables) +--------------- + +_NAME +~~~~~ + +The name of the Lua CJSON module (+"cjson"+). + + +_VERSION +~~~~~~~~ + +The version number of the Lua CJSON module (+"2.1devel"+). + + +null +~~~~ + +Lua CJSON decodes JSON +null+ as a Lua +lightuserdata+ NULL pointer. ++cjson.null+ is provided for comparison. + + +[sect1] +References +---------- + +- http://tools.ietf.org/html/rfc4627[RFC 4627] +- http://www.json.org/[JSON website] + + +// vi:ft=asciidoc tw=72: diff --git a/manual.txt b/manual.txt deleted file mode 100644 index a12e378..0000000 --- a/manual.txt +++ /dev/null @@ -1,612 +0,0 @@ -= Lua CJSON 2.1devel Manual = -Mark Pulford -:revdate: 1st March 2012 - -Overview --------- - -The Lua CJSON module provides JSON support for Lua. - -*Features*:: -- Fast, standards compliant encoding/parsing routines -- Full support for JSON with UTF-8, including decoding surrogate pairs -- Optional run-time support for common exceptions to the JSON - specification (infinity, NaN,..) -- No dependencies on other libraries - -*Caveats*:: -- UTF-16 and UTF-32 are not supported - -Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for -details. - -The latest version of this software is available from the -http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. - -Feel free to email me if you have any patches, suggestions, or comments. - - -Installation ------------- - -Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, or -http://www.luajit.org[LuaJIT] to build. - -The build method can be selected from 4 options: - -Make:: Unix (including Linux, BSD, Mac OSX & Solaris), Windows -CMake:: Unix, Windows -RPM:: Linux -LuaRocks:: Unix, Windows - - -Make -~~~~ - -The included +Makefile+ has generic settings. - -First, review and update the included makefile to suit your platform (if -required). - -Next, build and install the module: - -[source,sh] -make install - -Or install manually into your Lua module directory: - -[source,sh] -make -cp cjson.so $LUA_MODULE_DIRECTORY - - -CMake -~~~~~ - -http://www.cmake.org[CMake] can generate build configuration for many -different platforms (including Unix and Windows). - -First, generate the makefile for your platform using CMake. If CMake is -unable to find Lua, manually set the +LUA_DIR+ environment variable to -the base prefix of your Lua 5.1 installation. - -While +cmake+ is used in the example below, +ccmake+ or +cmake-gui+ may -be used to present an interface for changing the default build options. - -[source,sh] -mkdir build -cd build -# Optional: export LUA_DIR=$LUA51_PREFIX -cmake .. - -Next, build and install the module: - -[source,sh] -make install -# Or: -make -cp cjson.so $LUA_MODULE_DIRECTORY - -Review the -http://www.cmake.org/cmake/help/documentation.html[CMake documentation] -for further details. - - -RPM -~~~ - -Linux distributions using http://rpm.org[RPM] can create a package via -the included RPM spec file. Ensure the +rpm-build+ package (or similar) -has been installed. - -Build and install the module via RPM: - -[source,sh] -rpmbuild -tb lua-cjson-2.1devel.tar.gz -rpm -Uvh $LUA_CJSON_RPM - - -LuaRocks -~~~~~~~~ - -http://luarocks.org[LuaRocks] can be used to install and manage Lua -modules on a wide range of platforms (including Windows). - -First, extract the Lua CJSON source package. - -Next, install the module: - -[source,sh] -cd lua-cjson-2.1devel -luarocks make - -[NOTE] -LuaRocks does not support platform specific configuration for Solaris. -On Solaris, you may need to manually uncomment +USE_INTERNAL_ISINF+ in -the rockspec before building this module. - -Review the http://luarocks.org/en/Documentation[LuaRocks documentation] -for further details. - - -[[build_options]] -Build Options (#define) -~~~~~~~~~~~~~~~~~~~~~~~ - -Lua CJSON offers several +#define+ build options to address portability -issues, and enable non-default features. Some build methods may -automatically set platform specific options if required. Other features -should be enabled manually. - -USE_INTERNAL_ISINF:: Workaround for Solaris platforms missing +isinf+. -DISABLE_INVALID_NUMBERS:: Recommended on platforms where +strtod+ / - +sprintf+ are not POSIX compliant (eg, Windows MinGW). Prevents - +cjson.encode_invalid_numbers+ and +cjson.decode_invalid_numbers+ from - being enabled. However, +cjson.encode_invalid_numbers+ may still be - set to +"null"+. When using the Lua CJSON built-in floating point - conversion this option is unnecessary and is ignored. - - -Built-in floating point conversion -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Lua CJSON may be built with David Gay's -http://www.netlib.org/fp/[floating point conversion routines]. This can -increase overall performance by up to 50% on some platforms when -converting a large amount of numeric data. However, this option reduces -portability and is disabled by default. - -USE_INTERNAL_FPCONV:: Enable internal number conversion routines. -IEEE_BIG_ENDIAN:: Must be set on big endian architectures. -MULTIPLE_THREADS:: Must be set if Lua CJSON may be used in a - multi-threaded application. Requires the _pthreads_ library. - - -API (Functions) ---------------- - -Synopsis -~~~~~~~~ - -[source,lua] ------------- --- Module instantiation -local cjson = require "cjson" -local cjson2 = cjson.new() -local cjson_safe = require "cjson.safe" - --- Translate Lua value to/from JSON -text = cjson.encode(value) -value = cjson.decode(text) - --- Get and/or set Lua CJSON configuration -setting = cjson.decode_invalid_numbers([setting]) -setting = cjson.encode_invalid_numbers([setting]) -keep = cjson.encode_keep_buffer([keep]) -depth = cjson.encode_max_depth([depth]) -depth = cjson.decode_max_depth([depth]) -convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) ------------- - - -Module Instantiation -~~~~~~~~~~~~~~~~~~~~ - -[source,lua] ------------- -local cjson = require "cjson" -local cjson2 = cjson.new() -local cjson_safe = require "cjson.safe" ------------- - -Import Lua CJSON via the Lua +require+ function. Lua CJSON does not -register a global module table. - -The +cjson+ module will throw an error during JSON conversion if any -invalid data is encountered. Refer to <> -and <> for details. - -The +cjson.safe+ module behaves identically to the +cjson+ module, -except when errors are encountered during JSON conversion. On error, the -+cjson_safe.encode+ and +cjson_safe.decode+ functions will return -+nil+ followed by the error message. - -+cjson.new+ can be used to instantiate an independent copy of the Lua -CJSON module. The new module has a separate persistent encoding buffer, -and default settings. - -Lua CJSON can support Lua implementations using multiple preemptive -threads within a single Lua state provided the persistent encoding -buffer is not shared. This can be achieved by one of the following -methods: - -- Disabling the persistent encoding buffer with - <> -- Ensuring each thread calls <> separately (ie, - treat +cjson.encode+ as non-reentrant). -- Using a separate +cjson+ module table per preemptive thread - (+cjson.new+) - -[NOTE] -Lua CJSON uses +strtod+ and +snprintf+ to perform numeric conversion as -they are usually well supported, fast and bug free. However, these -functions require a workaround for JSON encoding/parsing under locales -using a comma decimal separator. Lua CJSON detects the current locale -during instantiation to determine and automatically implement the -workaround if required. Lua CJSON should be reinitialised via -+cjson.new+ if the locale of the current process changes. Using a -different locale per thread is not supported. - - -decode -~~~~~~ - -[source,lua] ------------- -value = cjson.decode(json_text) ------------- - -+cjson.decode+ will deserialise any UTF-8 JSON string into a Lua value -or table. - -UTF-16 and UTF-32 JSON strings are not supported. - -+cjson.decode+ requires that any NULL (ASCII 0) and double quote (ASCII -34) characters are escaped within strings. All escape codes will be -decoded and other bytes will be passed transparently. UTF-8 characters -are not validated during decoding and should be checked elsewhere if -required. - -JSON +null+ will be converted to a NULL +lightuserdata+ value. This can -be compared with +cjson.null+ for convenience. - -By default, numbers incompatible with the JSON specification (infinity, -NaN, hexadecimal) can be decoded. This default can be changed with -<>. - -.Example: Decoding -[source,lua] -json_text = '[ true, { "foo": "bar" } ]' -value = cjson.decode(json_text) --- Returns: { true, { foo = "bar" } } - -[CAUTION] -Care must be taken after decoding JSON objects with numeric keys. Each -numeric key will be stored as a Lua +string+. Any subsequent code -assuming type +number+ may break. - - -[[decode_invalid_numbers]] -decode_invalid_numbers -~~~~~~~~~~~~~~~~~~~~~~ - -[source,lua] ------------- -setting = cjson.decode_invalid_numbers([setting]) --- "setting" must be a boolean. Default: true. ------------- - -Lua CJSON may generate an error when trying to decode numbers not -supported by the JSON specification. _Invalid numbers_ are defined as: - -- infinity -- NaN -- hexadecimal - -Available settings: - -+true+:: Accept and decode _invalid numbers_. This is the default - setting. -+false+:: Throw an error when _invalid numbers_ are encountered. - -The current setting is always returned, and is only updated when an -argument is provided. - - -[[decode_max_depth]] -decode_max_depth -~~~~~~~~~~~~~~~~ - -[source,lua] ------------- -depth = cjson.decode_max_depth([depth]) --- "depth" must be a positive integer. Default: 1000. ------------- - -Lua CJSON will generate an error when parsing deeply nested JSON once -the maximum array/object depth has been exceeded. This check prevents -unnecessarily complicated JSON from slowing down the application, or -crashing the application due to lack of process stack space. - -An error may be generated before the depth limit is hit if Lua is unable -to allocate more objects on the Lua stack. - -By default, Lua CJSON will reject JSON with arrays and/or objects nested -more than 1000 levels deep. - -The current setting is always returned, and is only updated when an -argument is provided. - - -[[encode]] -encode -~~~~~~ - -[source,lua] ------------- -json_text = cjson.encode(value) ------------- - -+cjson.encode+ will serialise a Lua value into a string containing the -JSON representation. - -+cjson.encode+ supports the following types: - -- +boolean+ -- +lightuserdata+ (NULL value only) -- +nil+ -- +number+ -- +string+ -- +table+ - -The remaining Lua types will generate an error: - -- +function+ -- +lightuserdata+ (non-NULL values) -- +thread+ -- +userdata+ - -By default, numbers are encoded with 14 significant digits. Refer to -<> for details. - -Lua CJSON will escape the following characters within each UTF-8 string: - -- Control characters (ASCII 0 - 31) -- Double quote (ASCII 34) -- Forward slash (ASCII 47) -- Blackslash (ASCII 92) -- Delete (ASCII 127) - -All other bytes are passed transparently. - -[CAUTION] -========= -Lua CJSON will successfully encode/decode binary strings, but this is -technically not supported by JSON and may not be compatible with other -JSON libraries. To ensure the output is valid JSON, applications should -ensure all Lua strings passed to +cjson.encode+ are UTF-8. - -Base64 is commonly used to encode binary data as the most efficient -encoding under UTF-8 can only reduce the encoded size by a further -~8%. Lua Base64 routines can be found in the -http://w3.impa.br/%7Ediego/software/luasocket/[LuaSocket] and -http://www.tecgraf.puc-rio.br/%7Elhf/ftp/lua/#lbase64[lbase64] packages. -========= - -Lua CJSON uses a heuristic to determine whether to encode a Lua table as -a JSON array or an object. A Lua table with only positive integer keys -of type +number+ will be encoded as a JSON array. All other tables will -be encoded as a JSON object. - -Lua CJSON does not use metamethods when serialising tables. - -- +rawget+ is used to iterate over Lua arrays -- +next+ is used to iterate over Lua objects - -Lua arrays with missing entries (_sparse arrays_) may optionally be -encoded in several different ways. Refer to -<> for details. - -JSON object keys are always strings. Hence +cjson.encode+ only supports -table keys which are type +number+ or +string+. All other types will -generate an error. - -[NOTE] -Standards compliant JSON must be encapsulated in either an object (+{}+) -or an array (+[]+). If strictly standards compliant JSON is desired, a -table must be passed to +cjson.encode+. - -By default, encoding the following Lua values will generate errors: - -- Numbers incompatible with the JSON specification (infinity, NaN) -- Tables nested more than 1000 levels deep -- Excessively sparse Lua arrays - -These defaults can be changed with: - -- <> -- <> -- <> - -.Example: Encoding -[source,lua] -value = { true, { foo = "bar" } } -json_text = cjson.encode(value) --- Returns: '[true,{"foo":"bar"}]' - - -[[encode_invalid_numbers]] -encode_invalid_numbers -~~~~~~~~~~~~~~~~~~~~~~ -[source,lua] ------------- -setting = cjson.encode_invalid_numbers([setting]) --- "setting" must a boolean or "null". Default: false. ------------- - -Lua CJSON may generate an error when encoding floating point numbers not -supported by the JSON specification (_invalid numbers_): - -- infinity -- NaN - -Available settings: - -+true+:: Allow _invalid numbers_ to be encoded using the Javascript - compatible values +NaN+ and +Infinity+. This will generate - non-standard JSON, but these values are supported by some libraries. -+"null"+:: Encode _invalid numbers_ as a JSON +null+ value. This allows - infinity and NaN to be encoded into valid JSON. -+false+:: Throw an error when attempting to encode _invalid numbers_. - This is the default setting. - -The current setting is always returned, and is only updated when an -argument is provided. - - -[[encode_keep_buffer]] -encode_keep_buffer -~~~~~~~~~~~~~~~~~~ - -[source,lua] ------------- -keep = cjson.encode_keep_buffer([keep]) --- "keep" must be a boolean. Default: true. ------------- - -Lua CJSON can reuse the JSON encoding buffer to improve performance. - -Available settings: - -+true+:: The buffer will grow to the largest size required and is not - freed until the Lua CJSON module is garbage collected. This is the - default setting. -+false+:: Free the encode buffer after each call to +cjson.encode+. - -The current setting is always returned, and is only updated when an -argument is provided. - - -[[encode_max_depth]] -encode_max_depth -~~~~~~~~~~~~~~~~ - -[source,lua] ------------- -depth = cjson.encode_max_depth([depth]) --- "depth" must be a positive integer. Default: 1000. ------------- - -Once the maximum table depth has been exceeded Lua CJSON will generate -an error. This prevents a deeply nested or recursive data structure from -crashing the application. - -By default, Lua CJSON will generate an error when trying to encode data -structures with more than 1000 nested tables. - -The current setting is always returned, and is only updated when an -argument is provided. - -.Example: Recursive Lua table -[source,lua] -a = {}; a[1] = a - - -[[encode_number_precision]] -encode_number_precision -~~~~~~~~~~~~~~~~~~~~~~~ - -[source,lua] ------------- -precision = cjson.encode_number_precision([precision]) --- "precision" must be an integer between 1 and 14. Default: 14. ------------- - -The amount of significant digits returned by Lua CJSON when encoding -numbers can be changed to balance accuracy versus performance. For data -structures containing many numbers, setting -+cjson.encode_number_precision+ to a smaller integer, for example +3+, -can improve encoding performance by up to 50%. - -By default, Lua CJSON will output 14 significant digits when converting -a number to text. - -The current setting is always returned, and is only updated when an -argument is provided. - - -[[encode_sparse_array]] -encode_sparse_array -~~~~~~~~~~~~~~~~~~~ - -[source,lua] ------------- -convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) --- "convert" must be a boolean. Default: false. --- "ratio" must be a positive integer. Default: 2. --- "safe" must be a positive integer. Default: 10. ------------- - -Lua CJSON classifies a Lua table into one of three kinds when encoding a -JSON array. This is determined by the number of values missing from the -Lua array as follows: - -Normal:: All values are available. -Sparse:: At least 1 value is missing. -Excessively sparse:: The number of values missing exceeds the configured - ratio. - -Lua CJSON encodes sparse Lua arrays as JSON arrays using JSON +null+ for -the missing entries. - -An array is excessively sparse when all the following conditions are -met: - -- +ratio+ > +0+ -- _maximum_index_ > +safe+ -- _maximum_index_ > _item_count_ * +ratio+ - -Lua CJSON will never consider an array to be _excessively sparse_ when -+ratio+ = +0+. The +safe+ limit ensures that small Lua arrays are always -encoded as sparse arrays. - -By default, attempting to encode an _excessively sparse_ array will -generate an error. If +convert+ is set to +true+, _excessively sparse_ -arrays will be converted to a JSON object. - -The current settings are always returned. A particular setting is only -changed when the argument is provided (non-++nil++). - -.Example: Encoding a sparse array -[source,lua] -cjson.encode({ [3] = "data" }) --- Returns: '[null,null,"data"]' - -.Example: Enabling conversion to a JSON object -[source,lua] -cjson.encode_sparse_array(true) -cjson.encode({ [1000] = "excessively sparse" }) --- Returns: '{"1000":"excessively sparse"}' - - -API (Variables) ---------------- - -_NAME -~~~~~ - -The name of the Lua CJSON module (+"cjson"+). - - -_VERSION -~~~~~~~~ - -The version number of the Lua CJSON module (+"2.1devel"+). - - -null -~~~~ - -Lua CJSON decodes JSON +null+ as a Lua +lightuserdata+ NULL pointer. -+cjson.null+ is provided for comparison. - - -[sect1] -References ----------- - -- http://tools.ietf.org/html/rfc4627[RFC 4627] -- http://www.json.org/[JSON website] - - -// vi:ft=asciidoc tw=72: diff --git a/performance.adoc b/performance.adoc new file mode 100644 index 0000000..a36412d --- /dev/null +++ b/performance.adoc @@ -0,0 +1,89 @@ +JSON module performance comparison under Lua +============================================ +Mark Pulford +:revdate: January 22, 2012 + +This performance comparison aims to provide a guide of relative +performance between several fast and popular JSON modules. + +The examples used in this comparison were mostly sourced from the +http://json.org[JSON website] and +http://tools.ietf.org/html/rfc4627[RFC 4627]. + +Performance will vary widely between platforms and data sets. These +results should only be used as an approximation. + + +Modules +------- + +The following JSON modules for Lua were tested: + +http://chiselapp.com/user/dhkolf/repository/dkjson/[DKJSON 2.1]:: + - Lua implementation with no dependencies on other libraries + - Supports LPeg to improve decode performance + +https://github.com/brimworks/lua-yajl[Lua YAJL 2.0]:: + - C wrapper for the YAJL library + +http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON 2.0.0]:: + - C implementation with no dependencies on other libraries + + +Summary +------- + +All modules were built and tested as follows: + +DKJSON:: Tested with/without LPeg 10.2. +Lua YAJL:: Tested with YAJL 2.0.4. +Lua CJSON:: Tested with Libc and internal floating point conversion + routines. + +The following Lua implementations were used for this comparison: + +- http://www.lua.org[Lua 5.1.4] (_Lua_) +- http://www.luajit.org[LuaJIT 2.0.0-beta9] (_JIT_) + +These results show the number of JSON operations per second sustained by +each module. All results have been normalised against the pure Lua +DKJSON implementation. + +.Decoding performance +............................................................................ + | DKJSON | Lua YAJL | Lua CJSON + | No LPeg With LPeg | | Libc Internal + | Lua JIT Lua JIT | Lua JIT | Lua JIT Lua JIT +example1 | 1x 2x 2.6x 3.4x | 7.1x 10x | 14x 20x 14x 20x +example2 | 1x 2.2x 2.9x 4.4x | 6.7x 9.9x | 14x 22x 14x 22x +example3 | 1x 2.1x 3x 4.3x | 6.9x 9.3x | 14x 21x 15x 22x +example4 | 1x 2x 2.5x 3.7x | 7.3x 10x | 12x 19x 12x 20x +example5 | 1x 2.2x 3x 4.5x | 7.8x 11x | 16x 24x 16x 24x +numbers | 1x 2.2x 2.3x 4x | 4.6x 5.5x | 8.9x 10x 13x 17x +rfc-example1 | 1x 2.1x 2.8x 4.3x | 6.1x 8.1x | 13x 19x 14x 21x +rfc-example2 | 1x 2.1x 3.1x 4.2x | 7.1x 9.2x | 15x 21x 17x 24x +types | 1x 2.2x 2.6x 4.3x | 5.3x 7.4x | 12x 20x 13x 21x +-------------|-------------------------|------------|----------------------- += Average => | 1x 2.1x 2.7x 4.1x | 6.5x 9x | 13x 20x 14x 21x +............................................................................ + +.Encoding performance +............................................................................. + | DKJSON | Lua YAJL | Lua CJSON + | No LPeg With LPeg | | Libc Internal + | Lua JIT Lua JIT | Lua JIT | Lua JIT Lua JIT +example1 | 1x 1.8x 0.97x 1.6x | 3.1x 5.2x | 23x 29x 23x 29x +example2 | 1x 2x 0.97x 1.7x | 2.6x 4.3x | 22x 28x 22x 28x +example3 | 1x 1.9x 0.98x 1.6x | 2.8x 4.3x | 13x 15x 16x 18x +example4 | 1x 1.7x 0.96x 1.3x | 3.9x 6.1x | 15x 19x 17x 21x +example5 | 1x 2x 0.98x 1.7x | 2.7x 4.5x | 20x 23x 20x 23x +numbers | 1x 2.3x 1x 2.2x | 1.3x 1.9x | 3.8x 4.1x 4.2x 4.6x +rfc-example1 | 1x 1.9x 0.97x 1.6x | 2.2x 3.2x | 8.5x 9.3x 11x 12x +rfc-example2 | 1x 1.9x 0.98x 1.6x | 2.6x 3.9x | 10x 11x 17x 19x +types | 1x 2.2x 0.97x 2x | 1.2x 1.9x | 11x 13x 12x 14x +-------------|-------------------------|------------|----------------------- += Average => | 1x 1.9x 0.98x 1.7x | 2.5x 3.9x | 14x 17x 16x 19x +............................................................................. + + +// vi:ft=asciidoc tw=72: diff --git a/performance.txt b/performance.txt deleted file mode 100644 index fc3a5bb..0000000 --- a/performance.txt +++ /dev/null @@ -1,89 +0,0 @@ -JSON module performance comparison under Lua -============================================ -Mark Pulford -:revdate: January 22, 2012 - -This performance comparison aims to provide a guide of relative -performance between several fast and popular JSON modules. - -The examples used in this comparison were mostly sourced from the -http://json.org[JSON website] and -http://tools.ietf.org/html/rfc4627[RFC 4627]. - -Performance will vary widely between platforms and data sets. These -results should only be used as an approximation. - - -Modules -------- - -The following JSON modules for Lua were tested: - -http://chiselapp.com/user/dhkolf/repository/dkjson/[DKJSON 2.1]:: - - Lua implementation with no dependencies on other libraries - - Supports LPeg to improve decode performance - -https://github.com/brimworks/lua-yajl[Lua YAJL 2.0]:: - - C wrapper for the YAJL library - -http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CSJON 2.0.0]:: - - C implementation with no dependencies on other libraries - - -Summary -------- - -All modules were built and tested as follows: - -DKJSON:: Tested with/without LPeg 10.2. -Lua YAJL:: Tested with YAJL 2.0.4. -Lua CJSON:: Tested with Libc and internal floating point conversion - routines. - -The following Lua implementations were used for this comparison: - -- http://www.lua.org[Lua 5.1.4] (_Lua_) -- http://www.luajit.org[LuaJIT 2.0.0-beta9] (_JIT_) - -These results show the number of JSON operations per second sustained by -each module. All results have been normalised against the pure Lua -DKJSON implementation. - -.Decoding performance -............................................................................ - | DKJSON | Lua YAJL | Lua CJSON - | No LPeg With LPeg | | Libc Internal - | Lua JIT Lua JIT | Lua JIT | Lua JIT Lua JIT -example1 | 1x 2x 2.6x 3.4x | 7.1x 10x | 14x 20x 14x 20x -example2 | 1x 2.2x 2.9x 4.4x | 6.7x 9.9x | 14x 22x 14x 22x -example3 | 1x 2.1x 3x 4.3x | 6.9x 9.3x | 14x 21x 15x 22x -example4 | 1x 2x 2.5x 3.7x | 7.3x 10x | 12x 19x 12x 20x -example5 | 1x 2.2x 3x 4.5x | 7.8x 11x | 16x 24x 16x 24x -numbers | 1x 2.2x 2.3x 4x | 4.6x 5.5x | 8.9x 10x 13x 17x -rfc-example1 | 1x 2.1x 2.8x 4.3x | 6.1x 8.1x | 13x 19x 14x 21x -rfc-example2 | 1x 2.1x 3.1x 4.2x | 7.1x 9.2x | 15x 21x 17x 24x -types | 1x 2.2x 2.6x 4.3x | 5.3x 7.4x | 12x 20x 13x 21x --------------|-------------------------|------------|----------------------- -= Average => | 1x 2.1x 2.7x 4.1x | 6.5x 9x | 13x 20x 14x 21x -............................................................................ - -.Encoding performance -............................................................................. - | DKJSON | Lua YAJL | Lua CJSON - | No LPeg With LPeg | | Libc Internal - | Lua JIT Lua JIT | Lua JIT | Lua JIT Lua JIT -example1 | 1x 1.8x 0.97x 1.6x | 3.1x 5.2x | 23x 29x 23x 29x -example2 | 1x 2x 0.97x 1.7x | 2.6x 4.3x | 22x 28x 22x 28x -example3 | 1x 1.9x 0.98x 1.6x | 2.8x 4.3x | 13x 15x 16x 18x -example4 | 1x 1.7x 0.96x 1.3x | 3.9x 6.1x | 15x 19x 17x 21x -example5 | 1x 2x 0.98x 1.7x | 2.7x 4.5x | 20x 23x 20x 23x -numbers | 1x 2.3x 1x 2.2x | 1.3x 1.9x | 3.8x 4.1x 4.2x 4.6x -rfc-example1 | 1x 1.9x 0.97x 1.6x | 2.2x 3.2x | 8.5x 9.3x 11x 12x -rfc-example2 | 1x 1.9x 0.98x 1.6x | 2.6x 3.9x | 10x 11x 17x 19x -types | 1x 2.2x 0.97x 2x | 1.2x 1.9x | 11x 13x 12x 14x --------------|-------------------------|------------|----------------------- -= Average => | 1x 1.9x 0.98x 1.7x | 2.5x 3.9x | 14x 17x 16x 19x -............................................................................. - - -// vi:ft=asciidoc tw=72: diff --git a/strbuf.c b/strbuf.c index 2dc30be..73cd9b8 100644 --- a/strbuf.c +++ b/strbuf.c @@ -59,7 +59,7 @@ void strbuf_init(strbuf_t *s, size_t len) s->reallocs = 0; s->debug = 0; - s->buf = malloc(size); + s->buf = (char *)malloc(size); if (!s->buf) die("Out of memory"); @@ -70,7 +70,7 @@ strbuf_t *strbuf_new(size_t len) { strbuf_t *s; - s = malloc(sizeof(strbuf_t)); + s = (strbuf_t*)malloc(sizeof(strbuf_t)); if (!s) die("Out of memory"); @@ -169,7 +169,7 @@ void strbuf_resize(strbuf_t *s, size_t len) } s->size = newsize; - s->buf = realloc(s->buf, s->size); + s->buf = (char *)realloc(s->buf, s->size); if (!s->buf) die("Out of memory, len: %zu", len); s->reallocs++; diff --git a/tests/agentzh.t b/tests/agentzh.t index 552630a..88a94b8 100644 --- a/tests/agentzh.t +++ b/tests/agentzh.t @@ -332,3 +332,45 @@ Cannot serialise function: type not supported {"valid":"valid"} ["one","two","three"] + + + +=== TEST 23: array-like proxy object with __len and __index +--- lua +local cjson = require "cjson" +local real_array = {"foo", "bar", "baz"} +local proxy_array = {} +setmetatable(proxy_array, { + __len = function() return 3 end, + __index = function(t, k) + return real_array[k] + end, +}) + +print(cjson.encode(proxy_array)) +--- out +["foo","bar","baz"] + + + +=== TEST 24: check that integers are handled correctly on Lua 5.3+ +--- lua +local lv = tonumber((_VERSION):match("Lua 5%.([0-9]+)")) + +if lv >= 3 then + local cjson = require "cjson" + local array = cjson.decode [[ [10, 10.0, 3.5] ]] + for i = 1, 4 do + print(tostring(i) .. ": " .. tostring(math.type(array[i]))) + end +else + print("1: integer") + print("2: float") + print("3: float") + print("4: nil") +end +--- out +1: integer +2: float +3: float +4: nil diff --git a/tests/genutf8.pl b/tests/genutf8.pl index db661a1..c79f238 100755 --- a/tests/genutf8.pl +++ b/tests/genutf8.pl @@ -6,6 +6,7 @@ # cff03b039d850f370a7362f3313e5268 use strict; +no warnings 'nonchar'; # 0xD800 - 0xDFFF are used to encode supplementary codepoints # 0x10000 - 0x10FFFF are supplementary codepoints -- cgit v1.2.3-55-g6feb