diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f349fed --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +.gitattributes export-ignore +.gitignore export-ignore +build-packages.sh export-ignore +TODO export-ignore +devel export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b0fcd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,227 @@ +======= +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Xamarin Studio / monodevelop user-specific +*.userprefs + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# Enable nuget.exe in the .nuget folder (though normally executables are not tracked) +!.nuget/NuGet.exe + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Mac desktop service store files +.DS_Store + +# =================================================== +# Exclude F# project specific directories and files +# =================================================== + +# NuGet Packages Directory +packages/ + +# Generated documentation folder +docs/output/ + +# Temp folder used for publishing docs +temp/ + +# Test results produced by build +TestResults.xml + +# Nuget outputs +nuget/*.nupkg +release.cmd +release.sh +localpackages/ +paket-files +*.orig +.paket/paket.exe +docs/content/license.md +docs/content/release-notes.md + +*.ide +.idea +.vs + +\.vs/ +artifacts diff --git a/CMakeLists.txt b/CMakeLists.txt index 43a42d4..ca26381 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,83 @@ -# Copyright (C) 2007-2011 LuaDist. -# Created by Peter Kapec -# Redistribution and use of this file is allowed according to the terms of the MIT license. -# For details see the COPYRIGHT file distributed with LuaDist. -# Please note that the package source code is licensed under its own license. +# If Lua is installed in a non-standard location, please set the LUA_DIR +# environment variable to point to prefix for the install. Eg: +# Unix: export LUA_DIR=/home/user/pkg +# Windows: set LUA_DIR=c:\lua51 -project ( lua-cjson C ) -cmake_minimum_required ( VERSION 2.6 ) -include ( dist.cmake ) +project(lua-cjson C) +cmake_minimum_required(VERSION 2.6) +option(USE_INTERNAL_FPCONV "Use internal strtod() / g_fmt() code for performance") +option(MULTIPLE_THREADS "Support multi-threaded apps with internal fpconv - recommended" ON) -# lua-cjson modules -add_definitions ( -DVERSION="1.0.3" ) -install_lua_module( cjson lua_cjson.c strbuf.c ) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif() +find_package(Lua51 REQUIRED) +include_directories(${LUA_INCLUDE_DIR}) -# Install Lua-CJSON Documentation -install_data( README NEWS performance.txt rfc4627.txt ) +if(NOT USE_INTERNAL_FPCONV) + # Use libc number conversion routines (strtod(), sprintf()) + set(FPCONV_SOURCES fpconv.c) +else() + # Use internal number conversion routines + add_definitions(-DUSE_INTERNAL_FPCONV) + set(FPCONV_SOURCES g_fmt.c dtoa.c) -# Install Tests -install_test( tests/ ) \ No newline at end of file + include(TestBigEndian) + TEST_BIG_ENDIAN(IEEE_BIG_ENDIAN) + if(IEEE_BIG_ENDIAN) + add_definitions(-DIEEE_BIG_ENDIAN) + endif() + + if(MULTIPLE_THREADS) + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + find_package(Threads REQUIRED) + if(NOT CMAKE_USE_PTHREADS_INIT) + message(FATAL_ERROR + "Pthreads not found - required by MULTIPLE_THREADS option") + endif() + add_definitions(-DMULTIPLE_THREADS) + endif() +endif() + +# Handle platforms missing isinf() macro (Eg, some Solaris systems). +include(CheckSymbolExists) +CHECK_SYMBOL_EXISTS(isinf math.h HAVE_ISINF) +if(NOT HAVE_ISINF) + add_definitions(-DUSE_INTERNAL_ISINF) +endif() + +set(_MODULE_LINK "${CMAKE_THREAD_LIBS_INIT}") +get_filename_component(_lua_lib_dir ${LUA_LIBRARY} PATH) + +if(APPLE) + set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS + "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -undefined dynamic_lookup") +endif() + +if(WIN32) + # Win32 modules need to be linked to the Lua library. + set(_MODULE_LINK ${LUA_LIBRARY} ${_MODULE_LINK}) + set(_lua_module_dir "${_lua_lib_dir}") + # Windows sprintf()/strtod() handle NaN/inf differently. Not supported. + add_definitions(-DDISABLE_INVALID_NUMBERS) +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}) +install(TARGETS cjson DESTINATION "${_lua_module_dir}") + +# vi:ai et sw=4 ts=4: diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..9c50d25 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,27 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [ + { + "name": "LUA_INCLUDE_DIR", + "value": "./LUA/", + "type": "PATH" + }, + { + "name": "LUA_LIBRARY", + "value": "./LUA/", + "type": "FILEPATH" + } + ] + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8b16d47..747a8bf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2011 Mark Pulford +Copyright (c) 2010-2012 Mark Pulford Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/Makefile b/Makefile index d34ff6d..63f84a0 100644 --- a/Makefile +++ b/Makefile @@ -1,45 +1,120 @@ -CJSON_VERSION = 1.0.3 -LUA_VERSION = 5.1 +##### Available defines for CJSON_CFLAGS ##### +## +## USE_INTERNAL_ISINF: Workaround for Solaris platforms missing isinf(). +## DISABLE_INVALID_NUMBERS: Permanently disable invalid JSON numbers: +## NaN, Infinity, hex. +## +## Optional built-in number conversion uses the following defines: +## USE_INTERNAL_FPCONV: Use builtin strtod/dtoa for numeric conversions. +## IEEE_BIG_ENDIAN: Required on big endian architectures. +## MULTIPLE_THREADS: Must be set when Lua CJSON may be used in a +## multi-threaded application. Requries _pthreads_. -# See http://lua-users.org/wiki/BuildingModules for platform specific -# details. +##### Build defaults ##### +LUA_VERSION = 5.3 +TARGET = cjson.so +PREFIX = /usr/local +#CFLAGS = -g -Wall -pedantic -fno-inline +CFLAGS = -O3 -Wall -pedantic -DNDEBUG +CJSON_CFLAGS = -fpic +CJSON_LDFLAGS = -shared +LUA_INCLUDE_DIR = $(PREFIX)/include +LUA_CMODULE_DIR = $(PREFIX)/lib/lua/$(LUA_VERSION) +LUA_MODULE_DIR = $(PREFIX)/share/lua/$(LUA_VERSION) +LUA_BIN_DIR = $(PREFIX)/bin -## Linux/BSD -PREFIX ?= /usr/local -LDFLAGS += -shared +##### Platform overrides ##### +## +## Tweak one of the platform sections below to suit your situation. +## +## See http://lua-users.org/wiki/BuildingModules for further platform +## specific details. -## OSX (Macports) -#PREFIX ?= /opt/local -#LDFLAGS += -bundle -undefined dynamic_lookup +## Linux -LUA_INCLUDE_DIR ?= $(PREFIX)/include -LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) +## FreeBSD +#LUA_INCLUDE_DIR = $(PREFIX)/include/lua51 -# Some versions of Solaris are missing isinf(). Add -DMISSING_ISINF to -# CFLAGS to work around this bug. +## MacOSX (Macports) +#PREFIX = /opt/local +#CJSON_LDFLAGS = -bundle -undefined dynamic_lookup -#CFLAGS ?= -g -Wall -pedantic -fno-inline -CFLAGS ?= -g -O3 -Wall -pedantic -override CFLAGS += -fpic -I$(LUA_INCLUDE_DIR) -DVERSION=\"$(CJSON_VERSION)\" +## Solaris +#PREFIX = /home/user/opt +#CC = gcc +#CJSON_CFLAGS = -fpic -DUSE_INTERNAL_ISINF -INSTALL ?= install +## Windows (MinGW) +#TARGET = cjson.dll +#PREFIX = /home/user/opt +#CJSON_CFLAGS = -DDISABLE_INVALID_NUMBERS +#CJSON_LDFLAGS = -shared -L$(PREFIX)/lib -llua51 +#LUA_BIN_SUFFIX = .lua -.PHONY: all clean install package +##### Number conversion configuration ##### -all: cjson.so +## Use Libc support for number conversion (default) +FPCONV_OBJS = fpconv.o -cjson.so: lua_cjson.o strbuf.o - $(CC) $(LDFLAGS) -o $@ $^ +## Use built in number conversion +#FPCONV_OBJS = g_fmt.o dtoa.o +#CJSON_CFLAGS += -DUSE_INTERNAL_FPCONV -install: - $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR) - $(INSTALL) cjson.so $(DESTDIR)/$(LUA_LIB_DIR) +## Compile built in number conversion for big endian architectures +#CJSON_CFLAGS += -DIEEE_BIG_ENDIAN -clean: - rm -f *.o *.so +## Compile built in number conversion to support multi-threaded +## applications (recommended) +#CJSON_CFLAGS += -pthread -DMULTIPLE_THREADS +#CJSON_LDFLAGS += -pthread + +##### End customisable sections ##### + +TEST_FILES = README bench.lua genutf8.pl test.lua octets-escaped.dat \ + example1.json example2.json example3.json example4.json \ + example5.json numbers.json rfc-example1.json \ + rfc-example2.json types.json +DATAPERM = 644 +EXECPERM = 755 + +ASCIIDOC = asciidoc + +BUILD_CFLAGS = -I$(LUA_INCLUDE_DIR) $(CJSON_CFLAGS) +OBJS = lua_cjson.o strbuf.o $(FPCONV_OBJS) + +.PHONY: all clean install install-extra doc + +.SUFFIXES: .html .adoc + +.c.o: + $(CC) -c $(CFLAGS) $(CPPFLAGS) $(BUILD_CFLAGS) -o $@ $< -package: - git archive --prefix="lua-cjson-$(CJSON_VERSION)/" master | \ - gzip -9 > "lua-cjson-$(CJSON_VERSION).tar.gz" - git archive --prefix="lua-cjson-$(CJSON_VERSION)/" \ - -o "lua-cjson-$(CJSON_VERSION).zip" master +.adoc.html: + $(ASCIIDOC) -n -a toc $< + +all: $(TARGET) + +doc: manual.html performance.html + +$(TARGET): $(OBJS) + $(CC) $(LDFLAGS) $(CJSON_LDFLAGS) -o $@ $(OBJS) + +install: $(TARGET) + mkdir -p $(DESTDIR)/$(LUA_CMODULE_DIR) + cp $(TARGET) $(DESTDIR)/$(LUA_CMODULE_DIR) + chmod $(EXECPERM) $(DESTDIR)/$(LUA_CMODULE_DIR)/$(TARGET) + +install-extra: + mkdir -p $(DESTDIR)/$(LUA_MODULE_DIR)/cjson/tests \ + $(DESTDIR)/$(LUA_BIN_DIR) + cp lua/cjson/util.lua $(DESTDIR)/$(LUA_MODULE_DIR)/cjson + chmod $(DATAPERM) $(DESTDIR)/$(LUA_MODULE_DIR)/cjson/util.lua + cp lua/lua2json.lua $(DESTDIR)/$(LUA_BIN_DIR)/lua2json$(LUA_BIN_SUFFIX) + chmod $(EXECPERM) $(DESTDIR)/$(LUA_BIN_DIR)/lua2json$(LUA_BIN_SUFFIX) + cp lua/json2lua.lua $(DESTDIR)/$(LUA_BIN_DIR)/json2lua$(LUA_BIN_SUFFIX) + chmod $(EXECPERM) $(DESTDIR)/$(LUA_BIN_DIR)/json2lua$(LUA_BIN_SUFFIX) + cd tests; cp $(TEST_FILES) $(DESTDIR)/$(LUA_MODULE_DIR)/cjson/tests + cd tests; chmod $(DATAPERM) $(TEST_FILES); chmod $(EXECPERM) *.lua *.pl + +clean: + rm -f *.o $(TARGET) diff --git a/NEWS b/NEWS index 3527b04..8927d6e 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,27 @@ +Version 2.1.0 (Mar 1 2012) +* Added cjson.safe module interface which returns nil after an error +* Improved Makefile compatibility with Solaris make + +Version 2.0.0 (Jan 22 2012) +* Improved platform compatibility for strtod/sprintf locale workaround +* Added option to build with David Gay's dtoa.c for improved performance +* Added support for Lua 5.2 +* Added option to encode infinity/NaN as JSON null +* Fixed encode bug with a raised default limit and deeply nested tables +* Updated Makefile for compatibility with non-GNU make implementations +* Added CMake build support +* Added HTML manual +* Increased default nesting limit to 1000 +* Added support for re-entrant use of encode and decode +* Added support for installing lua2json and json2lua utilities +* Added encode_invalid_numbers() and decode_invalid_numbers() +* Added decode_max_depth() +* Removed registration of global cjson module table +* Removed refuse_invalid_numbers() + +Version 1.0.4 (Nov 30 2011) +* Fixed numeric conversion under locales with a comma decimal separator + Version 1.0.3 (Sep 15 2011) * Fixed detection of objects with numeric string keys * Provided work around for missing isinf() on Solaris diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..e50534c --- /dev/null +++ b/README.adoc @@ -0,0 +1,27 @@ += Lua CJSON = +Mark Pulford + +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. + +Please read +manual.adoc+ for installation instructions and the API +manual. + +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. + +// vi:ft=asciidoc tw=72: diff --git a/atoluanumber.c b/atoluanumber.c new file mode 100644 index 0000000..ead2398 --- /dev/null +++ b/atoluanumber.c @@ -0,0 +1,223 @@ +/* + * 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 "pch.h" +#include "lua_cjson.h" + +enum special_c { + space = ' ', + zero = '0', + nine = '9', + minus = '-', + plus = '+', + tab = '\t', +}; + +#define white_space(c) ((c) == space || (c) == tab) +#define valid_digit10(c) ((c) >= zero && (c) <= nine) +#define valid_digit16(c) ( ((c) >= zero && (c) <= nine) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) + +static double const e_mul[] = +{ + 1.0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9, 1E10, 1E11, 1E12, 1E13, 1E14, 1E15, 1E16, 1E17, 1E18, 1E19, + 1E20, 1E21, 1E22, 1E23, 1E24, 1E25, 1E26, 1E27, 1E28, 1E29, 1E30, 1E31, 1E32, 1E33, 1E34, 1E35, 1E36, 1E37, 1E38, 1E39, + 1E40, 1E41, 1E42, 1E43, 1E44, 1E45, 1E46, 1E47, 1E48, 1E49, +}; + + +/* JSON numbers should take the following form: + * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)? + * + * json_next_number_token() allows other forms: + * - numbers starting with '+' + * - NaN, -NaN, infinity, -infinity + * - hexadecimal numbers + * - numbers with leading zeros + * + * if json_strict_rule is != 0, JSON nubers will be strict JSON rules. + */ + +// Converts string to token value (int64 or double). returns 1 on success or 0 on fail +int json_next_number_token(json_parse_t* json, json_token_t* token) +{ + int json_strict_rule = !json->cfg->decode_invalid_numbers; + const char* p = json->ptr; + + int is_neg = 0; + int base = 10; + int is_int = 0; + long int_value = 0; + int is_double = 0; + + /* Skip minus sign if it exists */ + if (*p == '-') + { + p++; + is_neg = 1; + } + else + if (*p == '+') + { + if (json_strict_rule) + return 0; // * Reject numbers starting with + */ + p++; + } + + if (*p == '0') + { + p++; + char c = *p | 0x20; + if (c == 'x') + { + if (json_strict_rule) + return 0; // * Reject numbers starting with 0x, or leading zeros */ + base = 16; + p++; + } + else + { + if (json_strict_rule && *p == '0') + return 0; // * Reject numbers starting with 0x, or leading zeros */ + // we do not decode ord values, just skip leading zeroes + while (*p == '0') p++; + if ((*p == '.') || (c == 'e')) + is_double = 1; + } + } + else + { + // check for nan and inf + if (((p[0] == 'n') || (p[0] == 'N')) && ((p[1] == 'a') || (p[1] == 'A')) && ((p[2] == 'n') || (p[2] == 'N'))) + { + if (json_strict_rule) + return 0; /* Reject inf/nan */ + + p += 3; + token->type = T_NUMBER; + token->value.number = NAN; + json->ptr = p; /* Skip the processed number */ + return 1; + } + if (((p[0] == 'i') || (p[0] == 'I')) && ((p[1] == 'n') || (p[1] == 'N')) && ((p[2] == 'f') || (p[2] == 'F'))) + { + if (json_strict_rule) + return 0; /* Reject inf/nan */ + + p += 3; + if (strncasecmp(p, "inity", 5) == 0) p += 5; + token->type = T_NUMBER; + token->value.number = (is_neg) ? -INFINITY : INFINITY; + json->ptr = p; /* Skip the processed number */ + return 1; + } + } + + if (!is_double) + { + const char* b = p; + if (base == 10) + { + // Get digits before decimal point or exponent, if any. + while (valid_digit10(*p)) + { + int_value = int_value * 10 + (*p - zero); + p++; + } + + if (*p == '.' || *p == 'e' || *p == 'E') + is_double = 1; + else + is_int = (b != p); + } + else if (base == 16) + { + for (;; p++) { + if (valid_digit10(*p)) + int_value = (int_value << 4) + (*p - zero); + else + { + char c = *p | 0x20; + if ((c >= 'a') && (c <= 'f')) + int_value = (int_value << 4) + (c - 'a' + 10); + else break; + } + } + is_int = (b != p); + } + } + + if (is_int) + { + token->value.integer = (is_neg) ? -int_value : int_value; + token->type = T_INTEGER; + json->ptr = p; /* Skip the processed number */ + return 1; + } + + if (is_double && (base == 10)) + { + double double_value = int_value; + // Get digits after decimal point, if any. + if (*p == '.') { + double pow10 = 10.0; + p++; + while (valid_digit10(*p)) { + double_value += (*p - zero) / pow10; + pow10 *= 10.0; + p++; + } + } + // Handle exponent, if any. + int frac = 0; + double scale = 1.0; + if ((*p == 'e') || (*p == 'E')) { + // Get sign of exponent, if any. + p += 1; + if (*p == minus) { + frac = 1; + p++; + } + else + if (*p == plus) + p++; + + // Get digits of exponent, if any. + unsigned int expon; + for (expon = 0U; valid_digit10(*p); p++) { + expon = expon * 10U + (*p - (char)zero); + } + if (expon > 308U) expon = 308U; + + // Calculate scaling factor. + while (expon >= 50U) { scale *= 1E50; expon -= 50U; } + if (expon > 0U) scale *= e_mul[expon]; + } + + // Return signed and scaled floating point result. + double_value = (frac ? (double_value / scale) : (double_value * scale)); + token->value.number = (is_neg) ? -double_value : double_value; + token->type = T_NUMBER; + json->ptr = p; /* Skip the processed number */ + return 1; + } + return 0; +} + diff --git a/atoluanumber.h b/atoluanumber.h new file mode 100644 index 0000000..7a1d31e --- /dev/null +++ b/atoluanumber.h @@ -0,0 +1,31 @@ +#pragma once +#ifndef ATOLUANUMBER_H +#define ATOLUANUMBER_H +/* + * 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 "pch.h" +#include "lua_cjson.h" + +// Converts string to token value (int64 or double). returns 1 on success or 0 on fail +extern int json_next_number_token(json_parse_t* json, json_token_t* token); + +#endif // ! ATOLUANUMBER_H diff --git a/base64.c b/base64.c new file mode 100644 index 0000000..6a1ce23 --- /dev/null +++ b/base64.c @@ -0,0 +1,202 @@ +/* + Licensed under the MIT License . + SPDX-License-Identifier: MIT + 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 "pch.h" +#include "base64.h" + +/** Lookup table that converts a integer to base64 digit. */ +static char const base64encode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** Get the first base 64 digit of a block of 4. + * @param a The first byte of the source block of 3. + * @return A base 64 digit. */ +static char get0(unsigned char a) { + int const index = a >> 2u; + return base64encode[index]; +} + +/** Get the second base 64 digit of a block of 4. + * @param a The first byte of the source block of 3. + * @param b The second byte of the source block of 3. + * @return A base 64 digit. */ +static char get1(unsigned char a, unsigned char b) { + int const indexA = (a & 3) << 4u; + int const indexB = b >> 4u; + int const index = indexA | indexB; + return base64encode[index]; +} + +/** Get the third base 64 digit of a block of 4. + * @param b The second byte of the source block of 3. + * @param c The third byte of the source block of 3. + * @return A base 64 digit. */ +static char get2(unsigned char b, unsigned char c) { + int const indexB = (b & 15) << 2u; + int const indexC = c >> 6u; + int const index = indexB | indexC; + return base64encode[index]; +} + +/** Get the fourth base 64 digit of a block of 4. + * @param c The third byte of the source block of 3. + * @return A base 64 digit. */ +static char get3(int c) { + int const index = c & 0x3f; + return base64encode[index]; +} + +size_t base64_req_len(size_t src_len) +{ + return ((src_len + 2) / 3) * 4 + 1; +} + +/* Convert a binary memory block in a base64 null-terminated string. */ +size_t base64_encode(void const* src, size_t size, char* dest, size_t dsize) +{ + typedef struct { unsigned char a; unsigned char b; unsigned char c; } block_t; + + size_t req_len = base64_req_len(size); + if (req_len > dsize) return 0; + + block_t const* block = (block_t*)src; + size_t i = 0; + for (; size >= sizeof(block_t); size -= sizeof(block_t), ++block) { + dest[i++] = get0(block->a); + dest[i++] = get1(block->a, block->b); + dest[i++] = get2(block->b, block->c); + dest[i++] = get3(block->c); + } + if (size) + { + dest[i++] = get0(block->a); + if (--size) + { + dest[i++] = get1(block->a, block->b); + dest[i++] = get2(block->b, 0); + dest[i++] = '='; + } + else + { + dest[i++] = get1(block->a, 0); + dest[i++] = '='; + dest[i++] = '='; + } + } + dest[i] = '\0'; + return i; +} + +// Convert a 8Byte binary memory block in a base64 null-terminated string. Requires dest len >= 13 bytes. +int int64_to_base64(bytemap64 b64, char* dest, size_t dsize) +{ + if (dsize < 13) return 0; + + *dest++ = get0(b64.as_bytes[0]); + *dest++ = get1(b64.as_bytes[0], b64.as_bytes[1]); + *dest++ = get2(b64.as_bytes[1], b64.as_bytes[2]); + *dest++ = get3(b64.as_bytes[2]); + + *dest++ = get0(b64.as_bytes[3]); + *dest++ = get1(b64.as_bytes[3], b64.as_bytes[4]); + *dest++ = get2(b64.as_bytes[4], b64.as_bytes[5]); + *dest++ = get3(b64.as_bytes[5]); + + *dest++ = get0(b64.as_bytes[6]); + *dest++ = get1(b64.as_bytes[6], b64.as_bytes[7]); + *dest++ = get2(b64.as_bytes[7], 0); + + *dest++ = '='; + *dest = '\0'; + return 12; +} + +/** Lookup table that converts a base64 digit to integer. */ +static char const base64decode[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 65, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}; + +/** Escape values. */ +enum special_e { + notabase64 = 64, /**< Value to return when a non base64 digit is found. */ + terminator = 65, /**< Value to return when the character '=' is found. */ +}; + +// Convert a base64 null-terminated string to binary format. +size_t base64_decode(char const* src, void* dest, size_t dsize) +{ + unsigned char const* s = (unsigned char*)src; + char* p = dest; + size_t len = 0; + for (;; s++) { + unsigned char const a = base64decode[*s]; + if (a == notabase64) break; + if (a == terminator) break; + + unsigned char const b = base64decode[*++s]; + if (b == notabase64) return 0; + if (b == terminator) return 0; + + if (len >= dsize) return -1; + p[len++] = ((a << 2) | (b >> 4)) & 0xFF; + + unsigned char const c = base64decode[*++s]; + if (c == notabase64) return 0; + + unsigned char const d = base64decode[*++s]; + if (d == notabase64) return 0; + + if (c == terminator) + if (d == terminator) + break; + else + return 0; + + if (len >= dsize) return -1; + p[len++] = ((b << 4) | (c >> 2)) & 0xFF; + + if (d == terminator) break; + p[len++] = ((c << 6) | (d >> 0)) & 0xFF; + } + return len; +} + +// Convert a base64 null-terminated string to 8Byte binary memory block in. +// Returns 1 if success +int base64_to_bin64(const char* src, bytemap64 *out_b64) +{ + size_t len = base64_decode(src, out_b64, sizeof(*out_b64)); + return (len == 8); +} diff --git a/base64.h b/base64.h new file mode 100644 index 0000000..4ac76e0 --- /dev/null +++ b/base64.h @@ -0,0 +1,39 @@ +#pragma once +/*********************************************************** +* Base64 library * +* Purpose: encode and decode base64 format * +***********************************************************/ +#ifndef BASE46_H +#define BASE46_H + +#include +#include + +/*********************************************** +Encodes ASCCI string into base64 format string +@param plain ASCII string to be encoded +@return encoded base64 format string +***********************************************/ +// Encodes src bytes, output to dst_buf +// returns length of encoded string (positive number) or required length x -1 (negative number) +extern size_t base64_encode(void const* src, size_t size, char* dest, size_t dsize); +// mem size required for base64 encoded string. where src_len is unencoded memory block length +extern size_t base64_req_len(size_t src_len); +// Convert a base64 null-terminated string to binary format. +extern size_t base64_decode(char const* src, void* dest, size_t dsize); + + +typedef union { + const char as_bytes[]; + double as_double; + signed __int64 as_int64; + lua_Integer as_lua64; +} bytemap64; + +// Convert a 8Byte binary memory block in a base64 null-terminated string. +extern int int64_to_base64(bytemap64 b64, char* dest, size_t dsize); + +// Convert a base64 null-terminated string to 8Byte binary memory block in. Returns 1 if success +extern int base64_to_bin64(const char* src, bytemap64* out_b64); + +#endif \ No newline at end of file diff --git a/devel/json_parser_outline.txt b/devel/json_parser_outline.txt new file mode 100644 index 0000000..01db78d --- /dev/null +++ b/devel/json_parser_outline.txt @@ -0,0 +1,50 @@ +parser: + - call parse_value + - next_token + ? nop. + +parse_value: + - next_token + ? call parse_object. + ? call parse_array. + ? push. return. + ? push. return. + ? push. return. + ? push. return. + +parse_object: + - push table + - next_token + ? push. + - next_token + ? nop. + - call parse_value + - set table + - next_token + ? return. + ? loop parse_object. + +parse_array: + - push table + - call parse_value + - table append + - next_token + ? loop parse_array. + ? ] return. + +next_token: + - check next character + ? { return + ? } return + ? [ return + ? ] return + ? , return + ? : return + ? [-0-9] gobble number. return + ? " gobble string. return + ? [ \t\n] eat whitespace. + ? n Check "null". return or + ? t Check "true". return or + ? f Check "false". return or + ? . return + ? \0 return diff --git a/dllmain.c b/dllmain.c new file mode 100644 index 0000000..f840505 --- /dev/null +++ b/dllmain.c @@ -0,0 +1,19 @@ +// dllmain.cpp : Определяет точку входа для приложения DLL. +#include "pch.h" + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/examples/cjson/util.lua b/examples/cjson/util.lua new file mode 100644 index 0000000..5bb0d7d --- /dev/null +++ b/examples/cjson/util.lua @@ -0,0 +1,276 @@ +local json = require "cjson" + +-- Various common routines used by the Lua CJSON package +-- +-- Mark Pulford + +-- Determine with a Lua table can be treated as an array. +-- Explicitly returns "not an array" for very sparse arrays. +-- Returns: +-- -1 Not an array +-- 0 Empty table +-- >0 Highest index in the array + +-- Provide unpack for Lua 5.3+ built without LUA_COMPAT_UNPACK +local unpack = unpack +if table.unpack then unpack = table.unpack end + +local function is_array(table) + local max = 0 + local count = 0 + for k, v in pairs(table) do + if type(k) == "number" then + if k > max then max = k end + count = count + 1 + else + return -1 + end + end + if max > count * 2 then + return -1 + end + + return max +end + +local serialise_value + +local function serialise_table(value, indent, depth) + local spacing, spacing2, indent2 + if indent then + spacing = "\n" .. indent + spacing2 = spacing .. " " + indent2 = indent .. " " + else + spacing, spacing2, indent2 = " ", " ", false + end + depth = depth + 1 + if depth > 50 then + return "Cannot serialise any further: too many nested tables" + end + + local max = is_array(value) + + local comma = false + local fragment = { "{" .. spacing2 } + if max > 0 then + -- Serialise array + for i = 1, max do + if comma then + table.insert(fragment, "," .. spacing2) + end + table.insert(fragment, serialise_value(value[i], indent2, depth)) + comma = true + end + elseif max < 0 then + -- Serialise table + for k, v in pairs(value) do + if comma then + table.insert(fragment, "," .. spacing2) + end + table.insert(fragment, + ("[%s] = %s"):format(serialise_value(k, indent2, depth), + serialise_value(v, indent2, depth))) + comma = true + end + end + table.insert(fragment, spacing .. "}") + + return table.concat(fragment) +end + +function serialise_value(value, indent, depth) + if indent == nil then indent = "" end + if depth == nil then depth = 0 end + + if value == json.null then + return "json.null" + elseif type(value) == "string" then + return ("%q"):format(value) + elseif type(value) == "nil" or type(value) == "number" or + type(value) == "boolean" then + return tostring(value) + elseif type(value) == "table" then + return serialise_table(value, indent, depth) + else + return "\"<" .. type(value) .. ">\"" + end +end + +local function file_load(filename) + local file + if filename == nil then + file = io.stdin + else + local err + file, err = io.open(filename, "rb") + if file == nil then + error(("Unable to read '%s': %s"):format(filename, err)) + end + end + local data = file:read("*a") + + if filename ~= nil then + file:close() + end + + if data == nil then + error("Failed to read " .. filename) + end + + return data +end + +local function file_save(filename, data) + local file + if filename == nil then + file = io.stdout + else + local err + file, err = io.open(filename, "wb") + if file == nil then + error(("Unable to write '%s': %s"):format(filename, err)) + end + end + file:write(data) + if filename ~= nil then + file:close() + end +end + +local function compare_values(val1, val2) + local type1 = type(val1) + local type2 = type(val2) + if type1 ~= type2 then + return false + end + + -- Check for NaN + if type1 == "number" and val1 ~= val1 and val2 ~= val2 then + return true + end + + if type1 ~= "table" then + return val1 == val2 + end + + -- check_keys stores all the keys that must be checked in val2 + local check_keys = {} + for k, _ in pairs(val1) do + check_keys[k] = true + end + + for k, v in pairs(val2) do + if not check_keys[k] then + return false + end + + if not compare_values(val1[k], val2[k]) then + return false + end + + check_keys[k] = nil + end + for k, _ in pairs(check_keys) do + -- Not the same if any keys from val1 were not found in val2 + return false + end + return true +end + +local test_count_pass = 0 +local test_count_total = 0 + +local function run_test_summary() + return test_count_pass, test_count_total +end + +local function run_test(testname, func, input, should_work, output) + local function status_line(name, status, value) + local statusmap = { [true] = ":success", [false] = ":error" } + if status ~= nil then + name = name .. statusmap[status] + end + print(("[%s] %s"):format(name, serialise_value(value, false))) + end + + local result = { pcall(func, unpack(input)) } + local success = table.remove(result, 1) + + local correct = false + if success == should_work and compare_values(result, output) then + correct = true + test_count_pass = test_count_pass + 1 + end + test_count_total = test_count_total + 1 + + local teststatus = { [true] = "PASS", [false] = "FAIL" } + print(("==> Test [%d] %s: %s"):format(test_count_total, testname, + teststatus[correct])) + + status_line("Input", nil, input) + if not correct then + status_line("Expected", should_work, output) + end + status_line("Received", success, result) + print() + + return correct, result +end + +local function run_test_group(tests) + local function run_helper(name, func, input) + if type(name) == "string" and #name > 0 then + print("==> " .. name) + end + -- Not a protected call, these functions should never generate errors. + func(unpack(input or {})) + print() + end + + for _, v in ipairs(tests) do + -- Run the helper if "should_work" is missing + if v[4] == nil then + run_helper(unpack(v)) + else + run_test(unpack(v)) + end + end +end + +-- Run a Lua script in a separate environment +local function run_script(script, env) + local env = env or {} + local func + + -- Use setfenv() if it exists, otherwise assume Lua 5.2 load() exists + if _G.setfenv then + func = loadstring(script) + if func then + setfenv(func, env) + end + else + func = load(script, nil, nil, env) + end + + if func == nil then + error("Invalid syntax.") + end + func() + + return env +end + +-- Export functions +return { + serialise_value = serialise_value, + file_load = file_load, + file_save = file_save, + compare_values = compare_values, + run_test_summary = run_test_summary, + run_test = run_test, + run_test_group = run_test_group, + run_script = run_script +} + +-- vi:ai et sw=4 ts=4: diff --git a/examples/json2lua.lua b/examples/json2lua.lua new file mode 100644 index 0000000..014416d --- /dev/null +++ b/examples/json2lua.lua @@ -0,0 +1,14 @@ +#!/usr/bin/env lua + +-- usage: json2lua.lua [json_file] +-- +-- Eg: +-- echo '[ "testing" ]' | ./json2lua.lua +-- ./json2lua.lua test.json + +local json = require "cjson" +local util = require "cjson.util" + +local json_text = util.file_load(arg[1]) +local t = json.decode(json_text) +print(util.serialise_value(t)) diff --git a/examples/lua2json.lua b/examples/lua2json.lua new file mode 100644 index 0000000..aee8869 --- /dev/null +++ b/examples/lua2json.lua @@ -0,0 +1,20 @@ +#!/usr/bin/env lua + +-- usage: lua2json.lua [lua_file] +-- +-- Eg: +-- echo '{ "testing" }' | ./lua2json.lua +-- ./lua2json.lua test.lua + +local json = require "cjson" +local util = require "cjson.util" + +local env = { + json = { null = json.null }, + null = json.null +} + +local t = util.run_script("data = " .. util.file_load(arg[1]), env) +print(json.encode(t.data)) + +-- vi:ai et sw=4 ts=4: diff --git a/framework.h b/framework.h new file mode 100644 index 0000000..61ca0b6 --- /dev/null +++ b/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Исключите редко используемые компоненты из заголовков Windows +// Файлы заголовков Windows +#include diff --git a/itoa.c b/itoa.c new file mode 100644 index 0000000..0914cad --- /dev/null +++ b/itoa.c @@ -0,0 +1,86 @@ +/* + * 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 "pch.h" +#include "itoa.h" + + +static UINT16 const str100p[100] = { + 0x3030, 0x3130, 0x3230, 0x3330, 0x3430, 0x3530, 0x3630, 0x3730, 0x3830, 0x3930, + 0x3031, 0x3131, 0x3231, 0x3331, 0x3431, 0x3531, 0x3631, 0x3731, 0x3831, 0x3931, + 0x3032, 0x3132, 0x3232, 0x3332, 0x3432, 0x3532, 0x3632, 0x3732, 0x3832, 0x3932, + 0x3033, 0x3133, 0x3233, 0x3333, 0x3433, 0x3533, 0x3633, 0x3733, 0x3833, 0x3933, + 0x3034, 0x3134, 0x3234, 0x3334, 0x3434, 0x3534, 0x3634, 0x3734, 0x3834, 0x3934, + 0x3035, 0x3135, 0x3235, 0x3335, 0x3435, 0x3535, 0x3635, 0x3735, 0x3835, 0x3935, + 0x3036, 0x3136, 0x3236, 0x3336, 0x3436, 0x3536, 0x3636, 0x3736, 0x3836, 0x3936, + 0x3037, 0x3137, 0x3237, 0x3337, 0x3437, 0x3537, 0x3637, 0x3737, 0x3837, 0x3937, + 0x3038, 0x3138, 0x3238, 0x3338, 0x3438, 0x3538, 0x3638, 0x3738, 0x3838, 0x3938, + 0x3039, 0x3139, 0x3239, 0x3339, 0x3439, 0x3539, 0x3639, 0x3739, 0x3839, 0x3939, }; + +int itoa_vitaut(INT64 val, char* dst, size_t dsize) +{ + INT64 v1, v2, m; + v1 = (val < 0) ? -val : val; + + char buf[ITOA_BUFSIZE]; // Max length of UINT64 is 20 symbols, + sign, + null string terminator + buf[sizeof(buf)-1] = '\0'; + int i = sizeof(buf)-3; + while (v1 >= 100) + { + v2 = v1 / 100; + m = v1 - (100 * v2); + memcpy(&buf[i], &str100p[m], sizeof(UINT16)); + i -= 2; + v1 = v2; + if (i < 0) return 0; + } + memcpy(&buf[i], &str100p[v1], sizeof(UINT16)); + if (v1 < 10) i++; + if (val < 0) buf[--i] = '-'; + + size_t len = sizeof(buf) - i; + + if (len <= dsize) + { + memcpy(dst, &buf[i], len); + return (int)(len - 1); + } + return 0; +} + + +/* + * Bit 63 Sign + * Bits 62-52 Exponent + * Bits 51-00 Mantissa + */ +int is_IEEE754_64Bit_double_AnInt(double val) +{ + // Put the value in an long long so we can do bitwise operations. + uint64_t val64bit = *((uint64_t*)(&val)); + + // Remember to subtract 1023 from the exponent (to get real value) + int exponent = ((val64bit >> 52) & 0x7FF) - 1023; + int bitsInFraction = 52 - exponent; + uint64_t mask = exponent < 0 ? 0x7FFFFFFFFFFFFFFFLL : exponent > 52 ? 0x00 : (1LL << bitsInFraction) - 1; + return !(val64bit & mask); +} + diff --git a/itoa.h b/itoa.h new file mode 100644 index 0000000..f360cdb --- /dev/null +++ b/itoa.h @@ -0,0 +1,34 @@ +#pragma once +/* + * 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. + */ +#ifndef ITOA_H +#define ITOA_H + +#include "pch.h" + + // Max length of UINT64 is 20 symbols, + sign, + null string terminator +#define ITOA_BUFSIZE 22 + + // Converts int64 to string, returns length of string, or 0 if error. +extern int itoa_vitaut(INT64 val, char* dst, size_t dsize); +extern int is_IEEE754_64Bit_double_AnInt(double val); + +#endif // ! ITOA_H \ No newline at end of file diff --git a/lua-cjson.sln b/lua-cjson.sln new file mode 100644 index 0000000..30c4e23 --- /dev/null +++ b/lua-cjson.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31613.86 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lua-cjson", "lua-cjson.vcxproj", "{8942DA18-A0F9-4383-818A-7BCA8D34D4E8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8942DA18-A0F9-4383-818A-7BCA8D34D4E8}.Debug|x64.ActiveCfg = Debug|x64 + {8942DA18-A0F9-4383-818A-7BCA8D34D4E8}.Debug|x64.Build.0 = Debug|x64 + {8942DA18-A0F9-4383-818A-7BCA8D34D4E8}.Debug|x86.ActiveCfg = Debug|Win32 + {8942DA18-A0F9-4383-818A-7BCA8D34D4E8}.Debug|x86.Build.0 = Debug|Win32 + {8942DA18-A0F9-4383-818A-7BCA8D34D4E8}.Release|x64.ActiveCfg = Release|x64 + {8942DA18-A0F9-4383-818A-7BCA8D34D4E8}.Release|x64.Build.0 = Release|x64 + {8942DA18-A0F9-4383-818A-7BCA8D34D4E8}.Release|x86.ActiveCfg = Release|Win32 + {8942DA18-A0F9-4383-818A-7BCA8D34D4E8}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {54C08BFD-2DCD-43D1-8C01-4F2EB6CBB788} + EndGlobalSection +EndGlobal diff --git a/lua-cjson.spec b/lua-cjson.spec index 075f461..3d3cb16 100644 --- a/lua-cjson.spec +++ b/lua-cjson.spec @@ -1,22 +1,28 @@ %define luaver 5.1 %define lualibdir %{_libdir}/lua/%{luaver} +%define luadatadir %{_datadir}/lua/%{luaver} Name: lua-cjson -Version: 1.0.3 +Version: 2.1devel Release: 1%{?dist} -Summary: JSON support for the Lua language +Summary: A fast JSON encoding/parsing module for Lua Group: Development/Libraries License: MIT URL: http://www.kyne.com.au/~mark/software/lua-cjson/ -Source0: http://www.kyne.com.au/~mark/software/lua-cjson/lua-cjson-%{version}.tar.gz +Source0: http://www.kyne.com.au/~mark/software/lua-cjson/download/lua-cjson-%{version}.tar.gz BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) BuildRequires: lua >= %{luaver}, lua-devel >= %{luaver} Requires: lua >= %{luaver} %description -Lua CJSON provides fast, standards compliant JSON support for Lua. +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 %prep @@ -29,28 +35,46 @@ make %{?_smp_mflags} CFLAGS="%{optflags}" LUA_INCLUDE_DIR="%{_includedir}" %install rm -rf "$RPM_BUILD_ROOT" -make install DESTDIR="$RPM_BUILD_ROOT" LUA_LIB_DIR="%{lualibdir}" +make install DESTDIR="$RPM_BUILD_ROOT" LUA_CMODULE_DIR="%{lualibdir}" +make install-extra DESTDIR="$RPM_BUILD_ROOT" LUA_MODULE_DIR="%{luadatadir}" \ + LUA_BIN_DIR="%{_bindir}" %clean rm -rf "$RPM_BUILD_ROOT" +%preun +/bin/rm -f "%{luadatadir}/cjson/tests/utf8.dat" + + %files %defattr(-,root,root,-) -%doc LICENSE NEWS performance.txt README rfc4627.txt tests THANKS TODO +%doc LICENSE NEWS performance.html performance.adoc manual.html manual.adoc rfc4627.txt THANKS %{lualibdir}/* +%{luadatadir}/* +%{_bindir}/* %changelog +* Thu Mar 1 2012 Mark Pulford - 2.1.0-1 +- Update for 2.1.0 + +* Sun Jan 22 2012 Mark Pulford - 2.0.0-1 +- Update for 2.0.0 +- Install lua2json / json2lua utilities + +* Wed Nov 27 2011 Mark Pulford - 1.0.4-1 +- Update for 1.0.4 + * Wed Sep 15 2011 Mark Pulford - 1.0.3-1 -- Updated for 1.0.3 +- Update for 1.0.3 * Sun May 29 2011 Mark Pulford - 1.0.2-1 -- Updated for 1.0.2 +- Update for 1.0.2 * Sun May 10 2011 Mark Pulford - 1.0.1-1 -- Updated for 1.0.1 +- Update for 1.0.1 * Sun May 1 2011 Mark Pulford - 1.0-1 - Initial package diff --git a/lua-cjson.vcxproj b/lua-cjson.vcxproj new file mode 100644 index 0000000..a77cd3a --- /dev/null +++ b/lua-cjson.vcxproj @@ -0,0 +1,204 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {8942da18-a0f9-4383-818a-7bca8d34d4e8} + luacjson + 10.0 + lua_cjson + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;LUACJSON_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + false + + + + + Level3 + true + true + true + WIN32;NDEBUG;LUACJSON_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + + + Windows + true + true + true + false + + + + + Level3 + true + _DEBUG;LUACJSON_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + stdcpp17 + stdc17 + MultiThreadedDebug + true + false + + + Windows + true + false + true + + + + + Level3 + true + true + true + NDEBUG;LUACJSON_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + pch.h + MultiThreaded + stdcpp17 + stdc17 + Speed + true + + + Windows + true + true + true + false + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lua-cjson.vcxproj.filters b/lua-cjson.vcxproj.filters new file mode 100644 index 0000000..debd0f1 --- /dev/null +++ b/lua-cjson.vcxproj.filters @@ -0,0 +1,94 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + Файлы заголовков + + + + + Исходные файлы + + + Исходные файлы + + + Исходные файлы + + + Исходные файлы + + + Исходные файлы + + + Исходные файлы + + + Исходные файлы + + + + + Файлы ресурсов + + + Файлы ресурсов + + + + + Файлы ресурсов + + + \ No newline at end of file diff --git a/lua/cjson/util.lua b/lua/cjson/util.lua new file mode 100644 index 0000000..5bb0d7d --- /dev/null +++ b/lua/cjson/util.lua @@ -0,0 +1,276 @@ +local json = require "cjson" + +-- Various common routines used by the Lua CJSON package +-- +-- Mark Pulford + +-- Determine with a Lua table can be treated as an array. +-- Explicitly returns "not an array" for very sparse arrays. +-- Returns: +-- -1 Not an array +-- 0 Empty table +-- >0 Highest index in the array + +-- Provide unpack for Lua 5.3+ built without LUA_COMPAT_UNPACK +local unpack = unpack +if table.unpack then unpack = table.unpack end + +local function is_array(table) + local max = 0 + local count = 0 + for k, v in pairs(table) do + if type(k) == "number" then + if k > max then max = k end + count = count + 1 + else + return -1 + end + end + if max > count * 2 then + return -1 + end + + return max +end + +local serialise_value + +local function serialise_table(value, indent, depth) + local spacing, spacing2, indent2 + if indent then + spacing = "\n" .. indent + spacing2 = spacing .. " " + indent2 = indent .. " " + else + spacing, spacing2, indent2 = " ", " ", false + end + depth = depth + 1 + if depth > 50 then + return "Cannot serialise any further: too many nested tables" + end + + local max = is_array(value) + + local comma = false + local fragment = { "{" .. spacing2 } + if max > 0 then + -- Serialise array + for i = 1, max do + if comma then + table.insert(fragment, "," .. spacing2) + end + table.insert(fragment, serialise_value(value[i], indent2, depth)) + comma = true + end + elseif max < 0 then + -- Serialise table + for k, v in pairs(value) do + if comma then + table.insert(fragment, "," .. spacing2) + end + table.insert(fragment, + ("[%s] = %s"):format(serialise_value(k, indent2, depth), + serialise_value(v, indent2, depth))) + comma = true + end + end + table.insert(fragment, spacing .. "}") + + return table.concat(fragment) +end + +function serialise_value(value, indent, depth) + if indent == nil then indent = "" end + if depth == nil then depth = 0 end + + if value == json.null then + return "json.null" + elseif type(value) == "string" then + return ("%q"):format(value) + elseif type(value) == "nil" or type(value) == "number" or + type(value) == "boolean" then + return tostring(value) + elseif type(value) == "table" then + return serialise_table(value, indent, depth) + else + return "\"<" .. type(value) .. ">\"" + end +end + +local function file_load(filename) + local file + if filename == nil then + file = io.stdin + else + local err + file, err = io.open(filename, "rb") + if file == nil then + error(("Unable to read '%s': %s"):format(filename, err)) + end + end + local data = file:read("*a") + + if filename ~= nil then + file:close() + end + + if data == nil then + error("Failed to read " .. filename) + end + + return data +end + +local function file_save(filename, data) + local file + if filename == nil then + file = io.stdout + else + local err + file, err = io.open(filename, "wb") + if file == nil then + error(("Unable to write '%s': %s"):format(filename, err)) + end + end + file:write(data) + if filename ~= nil then + file:close() + end +end + +local function compare_values(val1, val2) + local type1 = type(val1) + local type2 = type(val2) + if type1 ~= type2 then + return false + end + + -- Check for NaN + if type1 == "number" and val1 ~= val1 and val2 ~= val2 then + return true + end + + if type1 ~= "table" then + return val1 == val2 + end + + -- check_keys stores all the keys that must be checked in val2 + local check_keys = {} + for k, _ in pairs(val1) do + check_keys[k] = true + end + + for k, v in pairs(val2) do + if not check_keys[k] then + return false + end + + if not compare_values(val1[k], val2[k]) then + return false + end + + check_keys[k] = nil + end + for k, _ in pairs(check_keys) do + -- Not the same if any keys from val1 were not found in val2 + return false + end + return true +end + +local test_count_pass = 0 +local test_count_total = 0 + +local function run_test_summary() + return test_count_pass, test_count_total +end + +local function run_test(testname, func, input, should_work, output) + local function status_line(name, status, value) + local statusmap = { [true] = ":success", [false] = ":error" } + if status ~= nil then + name = name .. statusmap[status] + end + print(("[%s] %s"):format(name, serialise_value(value, false))) + end + + local result = { pcall(func, unpack(input)) } + local success = table.remove(result, 1) + + local correct = false + if success == should_work and compare_values(result, output) then + correct = true + test_count_pass = test_count_pass + 1 + end + test_count_total = test_count_total + 1 + + local teststatus = { [true] = "PASS", [false] = "FAIL" } + print(("==> Test [%d] %s: %s"):format(test_count_total, testname, + teststatus[correct])) + + status_line("Input", nil, input) + if not correct then + status_line("Expected", should_work, output) + end + status_line("Received", success, result) + print() + + return correct, result +end + +local function run_test_group(tests) + local function run_helper(name, func, input) + if type(name) == "string" and #name > 0 then + print("==> " .. name) + end + -- Not a protected call, these functions should never generate errors. + func(unpack(input or {})) + print() + end + + for _, v in ipairs(tests) do + -- Run the helper if "should_work" is missing + if v[4] == nil then + run_helper(unpack(v)) + else + run_test(unpack(v)) + end + end +end + +-- Run a Lua script in a separate environment +local function run_script(script, env) + local env = env or {} + local func + + -- Use setfenv() if it exists, otherwise assume Lua 5.2 load() exists + if _G.setfenv then + func = loadstring(script) + if func then + setfenv(func, env) + end + else + func = load(script, nil, nil, env) + end + + if func == nil then + error("Invalid syntax.") + end + func() + + return env +end + +-- Export functions +return { + serialise_value = serialise_value, + file_load = file_load, + file_save = file_save, + compare_values = compare_values, + run_test_summary = run_test_summary, + run_test = run_test, + run_test_group = run_test_group, + run_script = run_script +} + +-- vi:ai et sw=4 ts=4: diff --git a/lua/json2lua.lua b/lua/json2lua.lua new file mode 100644 index 0000000..014416d --- /dev/null +++ b/lua/json2lua.lua @@ -0,0 +1,14 @@ +#!/usr/bin/env lua + +-- usage: json2lua.lua [json_file] +-- +-- Eg: +-- echo '[ "testing" ]' | ./json2lua.lua +-- ./json2lua.lua test.json + +local json = require "cjson" +local util = require "cjson.util" + +local json_text = util.file_load(arg[1]) +local t = json.decode(json_text) +print(util.serialise_value(t)) diff --git a/lua/lauxlib.h b/lua/lauxlib.h new file mode 100644 index 0000000..9857d3a --- /dev/null +++ b/lua/lauxlib.h @@ -0,0 +1,264 @@ +/* +** $Id: lauxlib.h,v 1.131.1.1 2017/04/19 17:20:42 roberto Exp $ +** Auxiliary functions for building Lua libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lauxlib_h +#define lauxlib_h + + +#include +#include + +#include "lua.h" + + + +/* extra error code for 'luaL_loadfilex' */ +#define LUA_ERRFILE (LUA_ERRERR+1) + + +/* key, in the registry, for table of loaded modules */ +#define LUA_LOADED_TABLE "_LOADED" + + +/* key, in the registry, for table of preloaded loaders */ +#define LUA_PRELOAD_TABLE "_PRELOAD" + + +typedef struct luaL_Reg { + const char *name; + lua_CFunction func; +} luaL_Reg; + + +#define LUAL_NUMSIZES (sizeof(lua_Integer)*16 + sizeof(lua_Number)) + +LUALIB_API void (luaL_checkversion_) (lua_State *L, lua_Number ver, size_t sz); +#define luaL_checkversion(L) \ + luaL_checkversion_(L, LUA_VERSION_NUM, LUAL_NUMSIZES) + +LUALIB_API int (luaL_getmetafield) (lua_State *L, int obj, const char *e); +LUALIB_API int (luaL_callmeta) (lua_State *L, int obj, const char *e); +LUALIB_API const char *(luaL_tolstring) (lua_State *L, int idx, size_t *len); +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg); +LUALIB_API const char *(luaL_checklstring) (lua_State *L, int arg, + size_t *l); +LUALIB_API const char *(luaL_optlstring) (lua_State *L, int arg, + const char *def, size_t *l); +LUALIB_API lua_Number (luaL_checknumber) (lua_State *L, int arg); +LUALIB_API lua_Number (luaL_optnumber) (lua_State *L, int arg, lua_Number def); + +LUALIB_API lua_Integer (luaL_checkinteger) (lua_State *L, int arg); +LUALIB_API lua_Integer (luaL_optinteger) (lua_State *L, int arg, + lua_Integer def); + +LUALIB_API void (luaL_checkstack) (lua_State *L, int sz, const char *msg); +LUALIB_API void (luaL_checktype) (lua_State *L, int arg, int t); +LUALIB_API void (luaL_checkany) (lua_State *L, int arg); + +LUALIB_API int (luaL_newmetatable) (lua_State *L, const char *tname); +LUALIB_API void (luaL_setmetatable) (lua_State *L, const char *tname); +LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname); +LUALIB_API void *(luaL_checkudata) (lua_State *L, int ud, const char *tname); + +LUALIB_API void (luaL_where) (lua_State *L, int lvl); +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...); + +LUALIB_API int (luaL_checkoption) (lua_State *L, int arg, const char *def, + const char *const lst[]); + +LUALIB_API int (luaL_fileresult) (lua_State *L, int stat, const char *fname); +LUALIB_API int (luaL_execresult) (lua_State *L, int stat); + +/* predefined references */ +#define LUA_NOREF (-2) +#define LUA_REFNIL (-1) + +LUALIB_API int (luaL_ref) (lua_State *L, int t); +LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); + +LUALIB_API int (luaL_loadfilex) (lua_State *L, const char *filename, + const char *mode); + +#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL) + +LUALIB_API int (luaL_loadbufferx) (lua_State *L, const char *buff, size_t sz, + const char *name, const char *mode); +LUALIB_API int (luaL_loadstring) (lua_State *L, const char *s); + +LUALIB_API lua_State *(luaL_newstate) (void); + +LUALIB_API lua_Integer (luaL_len) (lua_State *L, int idx); + +LUALIB_API const char *(luaL_gsub) (lua_State *L, const char *s, const char *p, + const char *r); + +LUALIB_API void (luaL_setfuncs) (lua_State *L, const luaL_Reg *l, int nup); + +LUALIB_API int (luaL_getsubtable) (lua_State *L, int idx, const char *fname); + +LUALIB_API void (luaL_traceback) (lua_State *L, lua_State *L1, + const char *msg, int level); + +LUALIB_API void (luaL_requiref) (lua_State *L, const char *modname, + lua_CFunction openf, int glb); + +/* +** =============================================================== +** some useful macros +** =============================================================== +*/ + + +#define luaL_newlibtable(L,l) \ + lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1) + +#define luaL_newlib(L,l) \ + (luaL_checkversion(L), luaL_newlibtable(L,l), luaL_setfuncs(L,l,0)) + +#define luaL_argcheck(L, cond,arg,extramsg) \ + ((void)((cond) || luaL_argerror(L, (arg), (extramsg)))) +#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL)) +#define luaL_optstring(L,n,d) (luaL_optlstring(L, (n), (d), NULL)) + +#define luaL_typename(L,i) lua_typename(L, lua_type(L,(i))) + +#define luaL_dofile(L, fn) \ + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_dostring(L, s) \ + (luaL_loadstring(L, s) || lua_pcall(L, 0, LUA_MULTRET, 0)) + +#define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) + +#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n))) + +#define luaL_loadbuffer(L,s,sz,n) luaL_loadbufferx(L,s,sz,n,NULL) + + +/* +** {====================================================== +** Generic Buffer manipulation +** ======================================================= +*/ + +typedef struct luaL_Buffer { + char *b; /* buffer address */ + size_t size; /* buffer size */ + size_t n; /* number of characters in buffer */ + lua_State *L; + char initb[LUAL_BUFFERSIZE]; /* initial buffer */ +} luaL_Buffer; + + +#define luaL_addchar(B,c) \ + ((void)((B)->n < (B)->size || luaL_prepbuffsize((B), 1)), \ + ((B)->b[(B)->n++] = (c))) + +#define luaL_addsize(B,s) ((B)->n += (s)) + +LUALIB_API void (luaL_buffinit) (lua_State *L, luaL_Buffer *B); +LUALIB_API char *(luaL_prepbuffsize) (luaL_Buffer *B, size_t sz); +LUALIB_API void (luaL_addlstring) (luaL_Buffer *B, const char *s, size_t l); +LUALIB_API void (luaL_addstring) (luaL_Buffer *B, const char *s); +LUALIB_API void (luaL_addvalue) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresult) (luaL_Buffer *B); +LUALIB_API void (luaL_pushresultsize) (luaL_Buffer *B, size_t sz); +LUALIB_API char *(luaL_buffinitsize) (lua_State *L, luaL_Buffer *B, size_t sz); + +#define luaL_prepbuffer(B) luaL_prepbuffsize(B, LUAL_BUFFERSIZE) + +/* }====================================================== */ + + + +/* +** {====================================================== +** File handles for IO library +** ======================================================= +*/ + +/* +** A file handle is a userdata with metatable 'LUA_FILEHANDLE' and +** initial structure 'luaL_Stream' (it may contain other fields +** after that initial structure). +*/ + +#define LUA_FILEHANDLE "FILE*" + + +typedef struct luaL_Stream { + FILE *f; /* stream (NULL for incompletely created streams) */ + lua_CFunction closef; /* to close stream (NULL for closed streams) */ +} luaL_Stream; + +/* }====================================================== */ + + + +/* compatibility with old module system */ +#if defined(LUA_COMPAT_MODULE) + +LUALIB_API void (luaL_pushmodule) (lua_State *L, const char *modname, + int sizehint); +LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname, + const luaL_Reg *l, int nup); + +#define luaL_register(L,n,l) (luaL_openlib(L,(n),(l),0)) + +#endif + + +/* +** {================================================================== +** "Abstraction Layer" for basic report of messages and errors +** =================================================================== +*/ + +/* print a string */ +#if !defined(lua_writestring) +#define lua_writestring(s,l) fwrite((s), sizeof(char), (l), stdout) +#endif + +/* print a newline and flush the output */ +#if !defined(lua_writeline) +#define lua_writeline() (lua_writestring("\n", 1), fflush(stdout)) +#endif + +/* print an error message */ +#if !defined(lua_writestringerror) +#define lua_writestringerror(s,p) \ + (fprintf(stderr, (s), (p)), fflush(stderr)) +#endif + +/* }================================================================== */ + + +/* +** {============================================================ +** Compatibility with deprecated conversions +** ============================================================= +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define luaL_checkunsigned(L,a) ((lua_Unsigned)luaL_checkinteger(L,a)) +#define luaL_optunsigned(L,a,d) \ + ((lua_Unsigned)luaL_optinteger(L,a,(lua_Integer)(d))) + +#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n))) +#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d))) + +#define luaL_checklong(L,n) ((long)luaL_checkinteger(L, (n))) +#define luaL_optlong(L,n,d) ((long)luaL_optinteger(L, (n), (d))) + +#endif +/* }============================================================ */ + + + +#endif + + diff --git a/lua/lua.h b/lua/lua.h new file mode 100644 index 0000000..c236e36 --- /dev/null +++ b/lua/lua.h @@ -0,0 +1,486 @@ +/* +** $Id: lua.h,v 1.332.1.2 2018/06/13 16:58:17 roberto Exp $ +** Lua - A Scripting Language +** Lua.org, PUC-Rio, Brazil (http://www.lua.org) +** See Copyright Notice at the end of this file +*/ + + +#ifndef lua_h +#define lua_h + +#include +#include + + +#include "luaconf.h" + + +#define LUA_VERSION_MAJOR "5" +#define LUA_VERSION_MINOR "3" +#define LUA_VERSION_NUM 503 +#define LUA_VERSION_RELEASE "5" + +#define LUA_VERSION "Lua " LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#define LUA_RELEASE LUA_VERSION "." LUA_VERSION_RELEASE +#define LUA_COPYRIGHT LUA_RELEASE " Copyright (C) 1994-2018 Lua.org, PUC-Rio" +#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo, W. Celes" + + +/* mark for precompiled code ('Lua') */ +#define LUA_SIGNATURE "\x1bLua" + +/* option for multiple returns in 'lua_pcall' and 'lua_call' */ +#define LUA_MULTRET (-1) + + +/* +** Pseudo-indices +** (-LUAI_MAXSTACK is the minimum valid index; we keep some free empty +** space after that to help overflow detection) +*/ +#define LUA_REGISTRYINDEX (-LUAI_MAXSTACK - 1000) +#define lua_upvalueindex(i) (LUA_REGISTRYINDEX - (i)) + + +/* thread status */ +#define LUA_OK 0 +#define LUA_YIELD 1 +#define LUA_ERRRUN 2 +#define LUA_ERRSYNTAX 3 +#define LUA_ERRMEM 4 +#define LUA_ERRGCMM 5 +#define LUA_ERRERR 6 + + +typedef struct lua_State lua_State; + + +/* +** basic types +*/ +#define LUA_TNONE (-1) + +#define LUA_TNIL 0 +#define LUA_TBOOLEAN 1 +#define LUA_TLIGHTUSERDATA 2 +#define LUA_TNUMBER 3 +#define LUA_TSTRING 4 +#define LUA_TTABLE 5 +#define LUA_TFUNCTION 6 +#define LUA_TUSERDATA 7 +#define LUA_TTHREAD 8 + +#define LUA_NUMTAGS 9 + + + +/* minimum Lua stack available to a C function */ +#define LUA_MINSTACK 20 + + +/* predefined values in the registry */ +#define LUA_RIDX_MAINTHREAD 1 +#define LUA_RIDX_GLOBALS 2 +#define LUA_RIDX_LAST LUA_RIDX_GLOBALS + + +/* type of numbers in Lua */ +typedef LUA_NUMBER lua_Number; + + +/* type for integer functions */ +typedef LUA_INTEGER lua_Integer; + +/* unsigned integer type */ +typedef LUA_UNSIGNED lua_Unsigned; + +/* type for continuation-function contexts */ +typedef LUA_KCONTEXT lua_KContext; + + +/* +** Type for C functions registered with Lua +*/ +typedef int (*lua_CFunction) (lua_State *L); + +/* +** Type for continuation functions +*/ +typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx); + + +/* +** Type for functions that read/write blocks when loading/dumping Lua chunks +*/ +typedef const char * (*lua_Reader) (lua_State *L, void *ud, size_t *sz); + +typedef int (*lua_Writer) (lua_State *L, const void *p, size_t sz, void *ud); + + +/* +** Type for memory-allocation functions +*/ +typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize); + + + +/* +** generic extra include file +*/ +#if defined(LUA_USER_H) +#include LUA_USER_H +#endif + + +/* +** RCS ident string +*/ +extern const char lua_ident[]; + + +/* +** state manipulation +*/ +LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud); +LUA_API void (lua_close) (lua_State *L); +LUA_API lua_State *(lua_newthread) (lua_State *L); + +LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf); + + +LUA_API const lua_Number *(lua_version) (lua_State *L); + + +/* +** basic stack manipulation +*/ +LUA_API int (lua_absindex) (lua_State *L, int idx); +LUA_API int (lua_gettop) (lua_State *L); +LUA_API void (lua_settop) (lua_State *L, int idx); +LUA_API void (lua_pushvalue) (lua_State *L, int idx); +LUA_API void (lua_rotate) (lua_State *L, int idx, int n); +LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); +LUA_API int (lua_checkstack) (lua_State *L, int n); + +LUA_API void (lua_xmove) (lua_State *from, lua_State *to, int n); + + +/* +** access functions (stack -> C) +*/ + +LUA_API int (lua_isnumber) (lua_State *L, int idx); +LUA_API int (lua_isstring) (lua_State *L, int idx); +LUA_API int (lua_iscfunction) (lua_State *L, int idx); +LUA_API int (lua_isinteger) (lua_State *L, int idx); +LUA_API int (lua_isuserdata) (lua_State *L, int idx); +LUA_API int (lua_type) (lua_State *L, int idx); +LUA_API const char *(lua_typename) (lua_State *L, int tp); + +LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); +LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); +LUA_API int (lua_toboolean) (lua_State *L, int idx); +LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); +LUA_API size_t (lua_rawlen) (lua_State *L, int idx); +LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); +LUA_API void *(lua_touserdata) (lua_State *L, int idx); +LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); +LUA_API const void *(lua_topointer) (lua_State *L, int idx); + + +/* +** Comparison and arithmetic functions +*/ + +#define LUA_OPADD 0 /* ORDER TM, ORDER OP */ +#define LUA_OPSUB 1 +#define LUA_OPMUL 2 +#define LUA_OPMOD 3 +#define LUA_OPPOW 4 +#define LUA_OPDIV 5 +#define LUA_OPIDIV 6 +#define LUA_OPBAND 7 +#define LUA_OPBOR 8 +#define LUA_OPBXOR 9 +#define LUA_OPSHL 10 +#define LUA_OPSHR 11 +#define LUA_OPUNM 12 +#define LUA_OPBNOT 13 + +LUA_API void (lua_arith) (lua_State *L, int op); + +#define LUA_OPEQ 0 +#define LUA_OPLT 1 +#define LUA_OPLE 2 + +LUA_API int (lua_rawequal) (lua_State *L, int idx1, int idx2); +LUA_API int (lua_compare) (lua_State *L, int idx1, int idx2, int op); + + +/* +** push functions (C -> stack) +*/ +LUA_API void (lua_pushnil) (lua_State *L); +LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); +LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); +LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); +LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); +LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, + va_list argp); +LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); +LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); +LUA_API void (lua_pushboolean) (lua_State *L, int b); +LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); +LUA_API int (lua_pushthread) (lua_State *L); + + +/* +** get functions (Lua -> stack) +*/ +LUA_API int (lua_getglobal) (lua_State *L, const char *name); +LUA_API int (lua_gettable) (lua_State *L, int idx); +LUA_API int (lua_getfield) (lua_State *L, int idx, const char *k); +LUA_API int (lua_geti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawget) (lua_State *L, int idx); +LUA_API int (lua_rawgeti) (lua_State *L, int idx, lua_Integer n); +LUA_API int (lua_rawgetp) (lua_State *L, int idx, const void *p); + +LUA_API void (lua_createtable) (lua_State *L, int narr, int nrec); +LUA_API void *(lua_newuserdata) (lua_State *L, size_t sz); +LUA_API int (lua_getmetatable) (lua_State *L, int objindex); +LUA_API int (lua_getuservalue) (lua_State *L, int idx); + + +/* +** set functions (stack -> Lua) +*/ +LUA_API void (lua_setglobal) (lua_State *L, const char *name); +LUA_API void (lua_settable) (lua_State *L, int idx); +LUA_API void (lua_setfield) (lua_State *L, int idx, const char *k); +LUA_API void (lua_seti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawset) (lua_State *L, int idx); +LUA_API void (lua_rawseti) (lua_State *L, int idx, lua_Integer n); +LUA_API void (lua_rawsetp) (lua_State *L, int idx, const void *p); +LUA_API int (lua_setmetatable) (lua_State *L, int objindex); +LUA_API void (lua_setuservalue) (lua_State *L, int idx); + + +/* +** 'load' and 'call' functions (load and run Lua code) +*/ +LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, + lua_KContext ctx, lua_KFunction k); +#define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) + +LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, + lua_KContext ctx, lua_KFunction k); +#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL) + +LUA_API int (lua_load) (lua_State *L, lua_Reader reader, void *dt, + const char *chunkname, const char *mode); + +LUA_API int (lua_dump) (lua_State *L, lua_Writer writer, void *data, int strip); + + +/* +** coroutine functions +*/ +LUA_API int (lua_yieldk) (lua_State *L, int nresults, lua_KContext ctx, + lua_KFunction k); +LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg); +LUA_API int (lua_status) (lua_State *L); +LUA_API int (lua_isyieldable) (lua_State *L); + +#define lua_yield(L,n) lua_yieldk(L, (n), 0, NULL) + + +/* +** garbage-collection function and options +*/ + +#define LUA_GCSTOP 0 +#define LUA_GCRESTART 1 +#define LUA_GCCOLLECT 2 +#define LUA_GCCOUNT 3 +#define LUA_GCCOUNTB 4 +#define LUA_GCSTEP 5 +#define LUA_GCSETPAUSE 6 +#define LUA_GCSETSTEPMUL 7 +#define LUA_GCISRUNNING 9 + +LUA_API int (lua_gc) (lua_State *L, int what, int data); + + +/* +** miscellaneous functions +*/ + +LUA_API int (lua_error) (lua_State *L); + +LUA_API int (lua_next) (lua_State *L, int idx); + +LUA_API void (lua_concat) (lua_State *L, int n); +LUA_API void (lua_len) (lua_State *L, int idx); + +LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s); + +LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud); +LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud); + + + +/* +** {============================================================== +** some useful macros +** =============================================================== +*/ + +#define lua_getextraspace(L) ((void *)((char *)(L) - LUA_EXTRASPACE)) + +#define lua_tonumber(L,i) lua_tonumberx(L,(i),NULL) +#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL) + +#define lua_pop(L,n) lua_settop(L, -(n)-1) + +#define lua_newtable(L) lua_createtable(L, 0, 0) + +#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) + +#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) + +#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) +#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) +#define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) +#define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) +#define lua_isnone(L,n) (lua_type(L, (n)) == LUA_TNONE) +#define lua_isnoneornil(L, n) (lua_type(L, (n)) <= 0) + +#define lua_pushliteral(L, s) lua_pushstring(L, "" s) + +#define lua_pushglobaltable(L) \ + ((void)lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)) + +#define lua_tostring(L,i) lua_tolstring(L, (i), NULL) + + +#define lua_insert(L,idx) lua_rotate(L, (idx), 1) + +#define lua_remove(L,idx) (lua_rotate(L, (idx), -1), lua_pop(L, 1)) + +#define lua_replace(L,idx) (lua_copy(L, -1, (idx)), lua_pop(L, 1)) + +/* }============================================================== */ + + +/* +** {============================================================== +** compatibility macros for unsigned conversions +** =============================================================== +*/ +#if defined(LUA_COMPAT_APIINTCASTS) + +#define lua_pushunsigned(L,n) lua_pushinteger(L, (lua_Integer)(n)) +#define lua_tounsignedx(L,i,is) ((lua_Unsigned)lua_tointegerx(L,i,is)) +#define lua_tounsigned(L,i) lua_tounsignedx(L,(i),NULL) + +#endif +/* }============================================================== */ + +/* +** {====================================================================== +** Debug API +** ======================================================================= +*/ + + +/* +** Event codes +*/ +#define LUA_HOOKCALL 0 +#define LUA_HOOKRET 1 +#define LUA_HOOKLINE 2 +#define LUA_HOOKCOUNT 3 +#define LUA_HOOKTAILCALL 4 + + +/* +** Event masks +*/ +#define LUA_MASKCALL (1 << LUA_HOOKCALL) +#define LUA_MASKRET (1 << LUA_HOOKRET) +#define LUA_MASKLINE (1 << LUA_HOOKLINE) +#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT) + +typedef struct lua_Debug lua_Debug; /* activation record */ + + +/* Functions to be called by the debugger in specific events */ +typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar); + + +LUA_API int (lua_getstack) (lua_State *L, int level, lua_Debug *ar); +LUA_API int (lua_getinfo) (lua_State *L, const char *what, lua_Debug *ar); +LUA_API const char *(lua_getlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_setlocal) (lua_State *L, const lua_Debug *ar, int n); +LUA_API const char *(lua_getupvalue) (lua_State *L, int funcindex, int n); +LUA_API const char *(lua_setupvalue) (lua_State *L, int funcindex, int n); + +LUA_API void *(lua_upvalueid) (lua_State *L, int fidx, int n); +LUA_API void (lua_upvaluejoin) (lua_State *L, int fidx1, int n1, + int fidx2, int n2); + +LUA_API void (lua_sethook) (lua_State *L, lua_Hook func, int mask, int count); +LUA_API lua_Hook (lua_gethook) (lua_State *L); +LUA_API int (lua_gethookmask) (lua_State *L); +LUA_API int (lua_gethookcount) (lua_State *L); + + +struct lua_Debug { + int event; + const char *name; /* (n) */ + const char *namewhat; /* (n) 'global', 'local', 'field', 'method' */ + const char *what; /* (S) 'Lua', 'C', 'main', 'tail' */ + const char *source; /* (S) */ + int currentline; /* (l) */ + int linedefined; /* (S) */ + int lastlinedefined; /* (S) */ + unsigned char nups; /* (u) number of upvalues */ + unsigned char nparams;/* (u) number of parameters */ + char isvararg; /* (u) */ + char istailcall; /* (t) */ + char short_src[LUA_IDSIZE]; /* (S) */ + /* private part */ + struct CallInfo *i_ci; /* active function */ +}; + +/* }====================================================================== */ + + +/****************************************************************************** +* Copyright (C) 1994-2018 Lua.org, PUC-Rio. +* +* 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. +******************************************************************************/ + + +#endif diff --git a/lua/lua.hpp b/lua/lua.hpp new file mode 100644 index 0000000..ec417f5 --- /dev/null +++ b/lua/lua.hpp @@ -0,0 +1,9 @@ +// lua.hpp +// Lua header files for C++ +// <> not supplied automatically because Lua also compiles as C++ + +extern "C" { +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +} diff --git a/lua/lua2json.lua b/lua/lua2json.lua new file mode 100644 index 0000000..aee8869 --- /dev/null +++ b/lua/lua2json.lua @@ -0,0 +1,20 @@ +#!/usr/bin/env lua + +-- usage: lua2json.lua [lua_file] +-- +-- Eg: +-- echo '{ "testing" }' | ./lua2json.lua +-- ./lua2json.lua test.lua + +local json = require "cjson" +local util = require "cjson.util" + +local env = { + json = { null = json.null }, + null = json.null +} + +local t = util.run_script("data = " .. util.file_load(arg[1]), env) +print(json.encode(t.data)) + +-- vi:ai et sw=4 ts=4: diff --git a/lua/lua53.dll b/lua/lua53.dll new file mode 100644 index 0000000..949c139 Binary files /dev/null and b/lua/lua53.dll differ diff --git a/lua/lua53.lib b/lua/lua53.lib new file mode 100644 index 0000000..5d204d3 Binary files /dev/null and b/lua/lua53.lib differ diff --git a/lua/luaconf.h b/lua/luaconf.h new file mode 100644 index 0000000..f955556 --- /dev/null +++ b/lua/luaconf.h @@ -0,0 +1,792 @@ +/* +** $Id: luaconf.h,v 1.259.1.1 2017/04/19 17:29:57 roberto Exp $ +** Configuration file for Lua +** See Copyright Notice in lua.h +*/ + + +#ifndef luaconf_h +#define luaconf_h + +#include +#include + + +/* +** =================================================================== +** Search for "@@" to find all configurable definitions. +** =================================================================== +*/ + + +/* +** {==================================================================== +** System Configuration: macros to adapt (if needed) Lua to some +** particular platform, for instance compiling it with 32-bit numbers or +** restricting it to C89. +** ===================================================================== +*/ + +/* +@@ LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. You +** can also define LUA_32BITS in the make file, but changing here you +** ensure that all software connected to Lua will be compiled with the +** same configuration. +*/ +/* #define LUA_32BITS */ + + +/* +@@ LUA_USE_C89 controls the use of non-ISO-C89 features. +** Define it if you want Lua to avoid the use of a few C99 features +** or Windows-specific features on Windows. +*/ +/* #define LUA_USE_C89 */ + + +/* +** By default, Lua on Windows use (some) specific Windows features +*/ +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#define LUA_USE_WINDOWS /* enable goodies for regular Windows */ +#endif + + +#if defined(LUA_USE_WINDOWS) +#define LUA_DL_DLL /* enable support for DLL */ +#define LUA_USE_C89 /* broadly, Windows is C89 */ +#endif + + +#if defined(LUA_USE_LINUX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* needs an extra library: -ldl */ +#define LUA_USE_READLINE /* needs some extra libraries */ +#endif + + +#if defined(LUA_USE_MACOSX) +#define LUA_USE_POSIX +#define LUA_USE_DLOPEN /* MacOS does not need -ldl */ +#define LUA_USE_READLINE /* needs an extra library: -lreadline */ +#endif + + +/* +@@ LUA_C89_NUMBERS ensures that Lua uses the largest types available for +** C89 ('long' and 'double'); Windows always has '__int64', so it does +** not need to use this case. +*/ +#if defined(LUA_USE_C89) && !defined(LUA_USE_WINDOWS) +#define LUA_C89_NUMBERS +#endif + + + +/* +@@ LUAI_BITSINT defines the (minimum) number of bits in an 'int'. +*/ +/* avoid undefined shifts */ +#if ((INT_MAX >> 15) >> 15) >= 1 +#define LUAI_BITSINT 32 +#else +/* 'int' always must have at least 16 bits */ +#define LUAI_BITSINT 16 +#endif + + +/* +@@ LUA_INT_TYPE defines the type for Lua integers. +@@ LUA_FLOAT_TYPE defines the type for Lua floats. +** Lua should work fine with any mix of these options (if supported +** by your C compiler). The usual configurations are 64-bit integers +** and 'double' (the default), 32-bit integers and 'float' (for +** restricted platforms), and 'long'/'double' (for C compilers not +** compliant with C99, which may not have support for 'long long'). +*/ + +/* predefined options for LUA_INT_TYPE */ +#define LUA_INT_INT 1 +#define LUA_INT_LONG 2 +#define LUA_INT_LONGLONG 3 + +/* predefined options for LUA_FLOAT_TYPE */ +#define LUA_FLOAT_FLOAT 1 +#define LUA_FLOAT_DOUBLE 2 +#define LUA_FLOAT_LONGDOUBLE 3 + +#if defined(LUA_32BITS) /* { */ +/* +** 32-bit integers and 'float' +*/ +#if LUAI_BITSINT >= 32 /* use 'int' if big enough */ +#define LUA_INT_TYPE LUA_INT_INT +#else /* otherwise use 'long' */ +#define LUA_INT_TYPE LUA_INT_LONG +#endif +#define LUA_FLOAT_TYPE LUA_FLOAT_FLOAT + +#elif defined(LUA_C89_NUMBERS) /* }{ */ +/* +** largest types available for C89 ('long' and 'double') +*/ +#define LUA_INT_TYPE LUA_INT_LONG +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE + +#endif /* } */ + + +/* +** default configuration for 64-bit Lua ('long long' and 'double') +*/ +#if !defined(LUA_INT_TYPE) +#define LUA_INT_TYPE LUA_INT_LONGLONG +#endif + +#if !defined(LUA_FLOAT_TYPE) +#define LUA_FLOAT_TYPE LUA_FLOAT_DOUBLE +#endif + +/* }================================================================== */ + + + + +/* +** {================================================================== +** Configuration for Paths. +** =================================================================== +*/ + +/* +** LUA_PATH_SEP is the character that separates templates in a path. +** LUA_PATH_MARK is the string that marks the substitution points in a +** template. +** LUA_EXEC_DIR in a Windows path is replaced by the executable's +** directory. +*/ +#define LUA_PATH_SEP ";" +#define LUA_PATH_MARK "?" +#define LUA_EXEC_DIR "!" + + +/* +@@ LUA_PATH_DEFAULT is the default path that Lua uses to look for +** Lua libraries. +@@ LUA_CPATH_DEFAULT is the default path that Lua uses to look for +** C libraries. +** CHANGE them if your machine has a non-conventional directory +** hierarchy or if you want to install your libraries in +** non-conventional directories. +*/ +#define LUA_VDIR LUA_VERSION_MAJOR "." LUA_VERSION_MINOR +#if defined(_WIN32) /* { */ +/* +** In Windows, any exclamation mark ('!') in the path is replaced by the +** path of the directory of the executable file of the current process. +*/ +#define LUA_LDIR "!\\lua\\" +#define LUA_CDIR "!\\" +#define LUA_SHRDIR "!\\..\\share\\lua\\" LUA_VDIR "\\" +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?\\init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?\\init.lua;" \ + LUA_SHRDIR"?.lua;" LUA_SHRDIR"?\\init.lua;" \ + ".\\?.lua;" ".\\?\\init.lua" +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.dll;" \ + LUA_CDIR"..\\lib\\lua\\" LUA_VDIR "\\?.dll;" \ + LUA_CDIR"loadall.dll;" ".\\?.dll;" \ + LUA_CDIR"?53.dll;" ".\\?53.dll" + +#else /* }{ */ + +#define LUA_ROOT "/usr/local/" +#define LUA_LDIR LUA_ROOT "share/lua/" LUA_VDIR "/" +#define LUA_CDIR LUA_ROOT "lib/lua/" LUA_VDIR "/" +#define LUA_PATH_DEFAULT \ + LUA_LDIR"?.lua;" LUA_LDIR"?/init.lua;" \ + LUA_CDIR"?.lua;" LUA_CDIR"?/init.lua;" \ + "./?.lua;" "./?/init.lua" +#define LUA_CPATH_DEFAULT \ + LUA_CDIR"?.so;" LUA_CDIR"loadall.so;" "./?.so;" \ + LUA_CDIR"lib?53.so;" "./lib?53.so" +#endif /* } */ + + +/* +@@ LUA_DIRSEP is the directory separator (for submodules). +** CHANGE it if your machine does not use "/" as the directory separator +** and is not Windows. (On Windows Lua automatically uses "\".) +*/ +#if defined(_WIN32) +#define LUA_DIRSEP "\\" +#else +#define LUA_DIRSEP "/" +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Marks for exported symbols in the C code +** =================================================================== +*/ + +/* +@@ LUA_API is a mark for all core API functions. +@@ LUALIB_API is a mark for all auxiliary library functions. +@@ LUAMOD_API is a mark for all standard library opening functions. +** CHANGE them if you need to define those functions in some special way. +** For instance, if you want to create one Windows DLL with the core and +** the libraries, you may want to use the following definition (define +** LUA_BUILD_AS_DLL to get it). +*/ +#if defined(LUA_BUILD_AS_DLL) /* { */ + +#if defined(LUA_CORE) || defined(LUA_LIB) /* { */ +#define LUA_API __declspec(dllexport) +#else /* }{ */ +#define LUA_API __declspec(dllimport) +#endif /* } */ + +#else /* }{ */ + +#define LUA_API extern + +#endif /* } */ + + +/* more often than not the libs go together with the core */ +#define LUALIB_API LUA_API +#define LUAMOD_API LUALIB_API + + +/* +@@ LUAI_FUNC is a mark for all extern functions that are not to be +** exported to outside modules. +@@ LUAI_DDEF and LUAI_DDEC are marks for all extern (const) variables +** that are not to be exported to outside modules (LUAI_DDEF for +** definitions and LUAI_DDEC for declarations). +** CHANGE them if you need to mark them in some special way. Elf/gcc +** (versions 3.2 and later) mark them as "hidden" to optimize access +** when Lua is compiled as a shared library. Not all elf targets support +** this attribute. Unfortunately, gcc does not offer a way to check +** whether the target offers that support, and those without support +** give a warning about it. To avoid these warnings, change to the +** default definition. +*/ +#if defined(__GNUC__) && ((__GNUC__*100 + __GNUC_MINOR__) >= 302) && \ + defined(__ELF__) /* { */ +#define LUAI_FUNC __attribute__((visibility("hidden"))) extern +#else /* }{ */ +#define LUAI_FUNC extern +#endif /* } */ + +#define LUAI_DDEC LUAI_FUNC +#define LUAI_DDEF /* empty */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Compatibility with previous versions +** =================================================================== +*/ + +/* +@@ LUA_COMPAT_5_2 controls other macros for compatibility with Lua 5.2. +@@ LUA_COMPAT_5_1 controls other macros for compatibility with Lua 5.1. +** You can define it to get all options, or change specific options +** to fit your specific needs. +*/ +#if defined(LUA_COMPAT_5_2) /* { */ + +/* +@@ LUA_COMPAT_MATHLIB controls the presence of several deprecated +** functions in the mathematical library. +*/ +#define LUA_COMPAT_MATHLIB + +/* +@@ LUA_COMPAT_BITLIB controls the presence of library 'bit32'. +*/ +#define LUA_COMPAT_BITLIB + +/* +@@ LUA_COMPAT_IPAIRS controls the effectiveness of the __ipairs metamethod. +*/ +#define LUA_COMPAT_IPAIRS + +/* +@@ LUA_COMPAT_APIINTCASTS controls the presence of macros for +** manipulating other integer types (lua_pushunsigned, lua_tounsigned, +** luaL_checkint, luaL_checklong, etc.) +*/ +#define LUA_COMPAT_APIINTCASTS + +#endif /* } */ + + +#if defined(LUA_COMPAT_5_1) /* { */ + +/* Incompatibilities from 5.2 -> 5.3 */ +#define LUA_COMPAT_MATHLIB +#define LUA_COMPAT_APIINTCASTS + +/* +@@ LUA_COMPAT_UNPACK controls the presence of global 'unpack'. +** You can replace it with 'table.unpack'. +*/ +#define LUA_COMPAT_UNPACK + +/* +@@ LUA_COMPAT_LOADERS controls the presence of table 'package.loaders'. +** You can replace it with 'package.searchers'. +*/ +#define LUA_COMPAT_LOADERS + +/* +@@ macro 'lua_cpcall' emulates deprecated function lua_cpcall. +** You can call your C function directly (with light C functions). +*/ +#define lua_cpcall(L,f,u) \ + (lua_pushcfunction(L, (f)), \ + lua_pushlightuserdata(L,(u)), \ + lua_pcall(L,1,0,0)) + + +/* +@@ LUA_COMPAT_LOG10 defines the function 'log10' in the math library. +** You can rewrite 'log10(x)' as 'log(x, 10)'. +*/ +#define LUA_COMPAT_LOG10 + +/* +@@ LUA_COMPAT_LOADSTRING defines the function 'loadstring' in the base +** library. You can rewrite 'loadstring(s)' as 'load(s)'. +*/ +#define LUA_COMPAT_LOADSTRING + +/* +@@ LUA_COMPAT_MAXN defines the function 'maxn' in the table library. +*/ +#define LUA_COMPAT_MAXN + +/* +@@ The following macros supply trivial compatibility for some +** changes in the API. The macros themselves document how to +** change your code to avoid using them. +*/ +#define lua_strlen(L,i) lua_rawlen(L, (i)) + +#define lua_objlen(L,i) lua_rawlen(L, (i)) + +#define lua_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#define lua_lessthan(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPLT) + +/* +@@ LUA_COMPAT_MODULE controls compatibility with previous +** module functions 'module' (Lua) and 'luaL_register' (C). +*/ +#define LUA_COMPAT_MODULE + +#endif /* } */ + + +/* +@@ LUA_COMPAT_FLOATSTRING makes Lua format integral floats without a +@@ a float mark ('.0'). +** This macro is not on by default even in compatibility mode, +** because this is not really an incompatibility. +*/ +/* #define LUA_COMPAT_FLOATSTRING */ + +/* }================================================================== */ + + + +/* +** {================================================================== +** Configuration for Numbers. +** Change these definitions if no predefined LUA_FLOAT_* / LUA_INT_* +** satisfy your needs. +** =================================================================== +*/ + +/* +@@ LUA_NUMBER is the floating-point type used by Lua. +@@ LUAI_UACNUMBER is the result of a 'default argument promotion' +@@ over a floating number. +@@ l_mathlim(x) corrects limit name 'x' to the proper float type +** by prefixing it with one of FLT/DBL/LDBL. +@@ LUA_NUMBER_FRMLEN is the length modifier for writing floats. +@@ LUA_NUMBER_FMT is the format for writing floats. +@@ lua_number2str converts a float to a string. +@@ l_mathop allows the addition of an 'l' or 'f' to all math operations. +@@ l_floor takes the floor of a float. +@@ lua_str2number converts a decimal numeric string to a number. +*/ + + +/* The following definitions are good for most cases here */ + +#define l_floor(x) (l_mathop(floor)(x)) + +#define lua_number2str(s,sz,n) \ + l_sprintf((s), sz, LUA_NUMBER_FMT, (LUAI_UACNUMBER)(n)) + +/* +@@ lua_numbertointeger converts a float number to an integer, or +** returns 0 if float is not within the range of a lua_Integer. +** (The range comparisons are tricky because of rounding. The tests +** here assume a two-complement representation, where MININTEGER always +** has an exact representation as a float; MAXINTEGER may not have one, +** and therefore its conversion to float may have an ill-defined value.) +*/ +#define lua_numbertointeger(n,p) \ + ((n) >= (LUA_NUMBER)(LUA_MININTEGER) && \ + (n) < -(LUA_NUMBER)(LUA_MININTEGER) && \ + (*(p) = (LUA_INTEGER)(n), 1)) + + +/* now the variable definitions */ + +#if LUA_FLOAT_TYPE == LUA_FLOAT_FLOAT /* { single float */ + +#define LUA_NUMBER float + +#define l_mathlim(n) (FLT_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.7g" + +#define l_mathop(op) op##f + +#define lua_str2number(s,p) strtof((s), (p)) + + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE /* }{ long double */ + +#define LUA_NUMBER long double + +#define l_mathlim(n) (LDBL_##n) + +#define LUAI_UACNUMBER long double + +#define LUA_NUMBER_FRMLEN "L" +#define LUA_NUMBER_FMT "%.19Lg" + +#define l_mathop(op) op##l + +#define lua_str2number(s,p) strtold((s), (p)) + +#elif LUA_FLOAT_TYPE == LUA_FLOAT_DOUBLE /* }{ double */ + +#define LUA_NUMBER double + +#define l_mathlim(n) (DBL_##n) + +#define LUAI_UACNUMBER double + +#define LUA_NUMBER_FRMLEN "" +#define LUA_NUMBER_FMT "%.14g" + +#define l_mathop(op) op + +#define lua_str2number(s,p) strtod((s), (p)) + +#else /* }{ */ + +#error "numeric float type not defined" + +#endif /* } */ + + + +/* +@@ LUA_INTEGER is the integer type used by Lua. +** +@@ LUA_UNSIGNED is the unsigned version of LUA_INTEGER. +** +@@ LUAI_UACINT is the result of a 'default argument promotion' +@@ over a lUA_INTEGER. +@@ LUA_INTEGER_FRMLEN is the length modifier for reading/writing integers. +@@ LUA_INTEGER_FMT is the format for writing integers. +@@ LUA_MAXINTEGER is the maximum value for a LUA_INTEGER. +@@ LUA_MININTEGER is the minimum value for a LUA_INTEGER. +@@ lua_integer2str converts an integer to a string. +*/ + + +/* The following definitions are good for most cases here */ + +#define LUA_INTEGER_FMT "%" LUA_INTEGER_FRMLEN "d" + +#define LUAI_UACINT LUA_INTEGER + +#define lua_integer2str(s,sz,n) \ + l_sprintf((s), sz, LUA_INTEGER_FMT, (LUAI_UACINT)(n)) + +/* +** use LUAI_UACINT here to avoid problems with promotions (which +** can turn a comparison between unsigneds into a signed comparison) +*/ +#define LUA_UNSIGNED unsigned LUAI_UACINT + + +/* now the variable definitions */ + +#if LUA_INT_TYPE == LUA_INT_INT /* { int */ + +#define LUA_INTEGER int +#define LUA_INTEGER_FRMLEN "" + +#define LUA_MAXINTEGER INT_MAX +#define LUA_MININTEGER INT_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONG /* }{ long */ + +#define LUA_INTEGER long +#define LUA_INTEGER_FRMLEN "l" + +#define LUA_MAXINTEGER LONG_MAX +#define LUA_MININTEGER LONG_MIN + +#elif LUA_INT_TYPE == LUA_INT_LONGLONG /* }{ long long */ + +/* use presence of macro LLONG_MAX as proxy for C99 compliance */ +#if defined(LLONG_MAX) /* { */ +/* use ISO C99 stuff */ + +#define LUA_INTEGER long long +#define LUA_INTEGER_FRMLEN "ll" + +#define LUA_MAXINTEGER LLONG_MAX +#define LUA_MININTEGER LLONG_MIN + +#elif defined(LUA_USE_WINDOWS) /* }{ */ +/* in Windows, can use specific Windows types */ + +#define LUA_INTEGER __int64 +#define LUA_INTEGER_FRMLEN "I64" + +#define LUA_MAXINTEGER _I64_MAX +#define LUA_MININTEGER _I64_MIN + +#else /* }{ */ + +#error "Compiler does not support 'long long'. Use option '-DLUA_32BITS' \ + or '-DLUA_C89_NUMBERS' (see file 'luaconf.h' for details)" + +#endif /* } */ + +#else /* }{ */ + +#error "numeric integer type not defined" + +#endif /* } */ + +/* }================================================================== */ + + +/* +** {================================================================== +** Dependencies with C99 and other C details +** =================================================================== +*/ + +/* +@@ l_sprintf is equivalent to 'snprintf' or 'sprintf' in C89. +** (All uses in Lua have only one format item.) +*/ +#if !defined(LUA_USE_C89) +#define l_sprintf(s,sz,f,i) snprintf(s,sz,f,i) +#else +#define l_sprintf(s,sz,f,i) ((void)(sz), sprintf(s,f,i)) +#endif + + +/* +@@ lua_strx2number converts an hexadecimal numeric string to a number. +** In C99, 'strtod' does that conversion. Otherwise, you can +** leave 'lua_strx2number' undefined and Lua will provide its own +** implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_strx2number(s,p) lua_str2number(s,p) +#endif + + +/* +@@ lua_pointer2str converts a pointer to a readable string in a +** non-specified way. +*/ +#define lua_pointer2str(buff,sz,p) l_sprintf(buff,sz,"%p",p) + + +/* +@@ lua_number2strx converts a float to an hexadecimal numeric string. +** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that. +** Otherwise, you can leave 'lua_number2strx' undefined and Lua will +** provide its own implementation. +*/ +#if !defined(LUA_USE_C89) +#define lua_number2strx(L,b,sz,f,n) \ + ((void)L, l_sprintf(b,sz,f,(LUAI_UACNUMBER)(n))) +#endif + + +/* +** 'strtof' and 'opf' variants for math functions are not valid in +** C89. Otherwise, the macro 'HUGE_VALF' is a good proxy for testing the +** availability of these variants. ('math.h' is already included in +** all files that use these macros.) +*/ +#if defined(LUA_USE_C89) || (defined(HUGE_VAL) && !defined(HUGE_VALF)) +#undef l_mathop /* variants not available */ +#undef lua_str2number +#define l_mathop(op) (lua_Number)op /* no variant */ +#define lua_str2number(s,p) ((lua_Number)strtod((s), (p))) +#endif + + +/* +@@ LUA_KCONTEXT is the type of the context ('ctx') for continuation +** functions. It must be a numerical type; Lua will use 'intptr_t' if +** available, otherwise it will use 'ptrdiff_t' (the nearest thing to +** 'intptr_t' in C89) +*/ +#define LUA_KCONTEXT ptrdiff_t + +#if !defined(LUA_USE_C89) && defined(__STDC_VERSION__) && \ + __STDC_VERSION__ >= 199901L +#include +#if defined(INTPTR_MAX) /* even in C99 this type is optional */ +#undef LUA_KCONTEXT +#define LUA_KCONTEXT intptr_t +#endif +#endif + + +/* +@@ lua_getlocaledecpoint gets the locale "radix character" (decimal point). +** Change that if you do not want to use C locales. (Code using this +** macro must include header 'locale.h'.) +*/ +#if !defined(lua_getlocaledecpoint) +#define lua_getlocaledecpoint() (localeconv()->decimal_point[0]) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Language Variations +** ===================================================================== +*/ + +/* +@@ LUA_NOCVTN2S/LUA_NOCVTS2N control how Lua performs some +** coercions. Define LUA_NOCVTN2S to turn off automatic coercion from +** numbers to strings. Define LUA_NOCVTS2N to turn off automatic +** coercion from strings to numbers. +*/ +/* #define LUA_NOCVTN2S */ +/* #define LUA_NOCVTS2N */ + + +/* +@@ LUA_USE_APICHECK turns on several consistency checks on the C API. +** Define it as a help when debugging C code. +*/ +#if defined(LUA_USE_APICHECK) +#include +#define luai_apicheck(l,e) assert(e) +#endif + +/* }================================================================== */ + + +/* +** {================================================================== +** Macros that affect the API and must be stable (that is, must be the +** same when you compile Lua and when you compile code that links to +** Lua). You probably do not want/need to change them. +** ===================================================================== +*/ + +/* +@@ LUAI_MAXSTACK limits the size of the Lua stack. +** CHANGE it if you need a different limit. This limit is arbitrary; +** its only purpose is to stop Lua from consuming unlimited stack +** space (and to reserve some numbers for pseudo-indices). +*/ +#if LUAI_BITSINT >= 32 +#define LUAI_MAXSTACK 1000000 +#else +#define LUAI_MAXSTACK 15000 +#endif + + +/* +@@ LUA_EXTRASPACE defines the size of a raw memory area associated with +** a Lua state with very fast access. +** CHANGE it if you need a different size. +*/ +#define LUA_EXTRASPACE (sizeof(void *)) + + +/* +@@ LUA_IDSIZE gives the maximum size for the description of the source +@@ of a function in debug information. +** CHANGE it if you want a different size. +*/ +#define LUA_IDSIZE 60 + + +/* +@@ LUAL_BUFFERSIZE is the buffer size used by the lauxlib buffer system. +** CHANGE it if it uses too much C-stack space. (For long double, +** 'string.format("%.99f", -1e4932)' needs 5034 bytes, so a +** smaller buffer would force a memory allocation for each call to +** 'string.format'.) +*/ +#if LUA_FLOAT_TYPE == LUA_FLOAT_LONGDOUBLE +#define LUAL_BUFFERSIZE 8192 +#else +#define LUAL_BUFFERSIZE ((int)(0x80 * sizeof(void*) * sizeof(lua_Integer))) +#endif + +/* }================================================================== */ + + +/* +@@ LUA_QL describes how error messages quote program elements. +** Lua does not use these macros anymore; they are here for +** compatibility only. +*/ +#define LUA_QL(x) "'" x "'" +#define LUA_QS LUA_QL("%s") + + + + +/* =================================================================== */ + +/* +** Local configuration. You can use this space to add your redefinitions +** without modifying the main part of the file. +*/ + + + + + +#endif + diff --git a/lua/lualib.h b/lua/lualib.h new file mode 100644 index 0000000..f5304aa --- /dev/null +++ b/lua/lualib.h @@ -0,0 +1,61 @@ +/* +** $Id: lualib.h,v 1.45.1.1 2017/04/19 17:20:42 roberto Exp $ +** Lua standard libraries +** See Copyright Notice in lua.h +*/ + + +#ifndef lualib_h +#define lualib_h + +#include "lua.h" + + +/* version suffix for environment variable names */ +#define LUA_VERSUFFIX "_" LUA_VERSION_MAJOR "_" LUA_VERSION_MINOR + + +LUAMOD_API int (luaopen_base) (lua_State *L); + +#define LUA_COLIBNAME "coroutine" +LUAMOD_API int (luaopen_coroutine) (lua_State *L); + +#define LUA_TABLIBNAME "table" +LUAMOD_API int (luaopen_table) (lua_State *L); + +#define LUA_IOLIBNAME "io" +LUAMOD_API int (luaopen_io) (lua_State *L); + +#define LUA_OSLIBNAME "os" +LUAMOD_API int (luaopen_os) (lua_State *L); + +#define LUA_STRLIBNAME "string" +LUAMOD_API int (luaopen_string) (lua_State *L); + +#define LUA_UTF8LIBNAME "utf8" +LUAMOD_API int (luaopen_utf8) (lua_State *L); + +#define LUA_BITLIBNAME "bit32" +LUAMOD_API int (luaopen_bit32) (lua_State *L); + +#define LUA_MATHLIBNAME "math" +LUAMOD_API int (luaopen_math) (lua_State *L); + +#define LUA_DBLIBNAME "debug" +LUAMOD_API int (luaopen_debug) (lua_State *L); + +#define LUA_LOADLIBNAME "package" +LUAMOD_API int (luaopen_package) (lua_State *L); + + +/* open all previous libraries */ +LUALIB_API void (luaL_openlibs) (lua_State *L); + + + +#if !defined(lua_assert) +#define lua_assert(x) ((void)0) +#endif + + +#endif diff --git a/lua_cjson.c b/lua_cjson.c index 3868de3..d30f729 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -1,6 +1,6 @@ -/* CJSON - JSON support for Lua +/* Lua CJSON - JSON support for Lua * - * Copyright (c) 2010-2011 Mark Pulford + * Copyright (c) 2010-2012 Mark Pulford * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -36,99 +36,18 @@ * difficult to know object/array sizes ahead of time. */ +#include "pch.h" #include +#include #include #include -#include -#include +#include -#include "strbuf.h" - -#ifdef MISSING_ISINF -#define isinf(x) (!isnan(x) && isnan((x) - (x))) -#endif - -#define DEFAULT_SPARSE_CONVERT 0 -#define DEFAULT_SPARSE_RATIO 2 -#define DEFAULT_SPARSE_SAFE 10 -#define DEFAULT_MAX_DEPTH 20 -#define DEFAULT_ENCODE_REFUSE_BADNUM 1 -#define DEFAULT_DECODE_REFUSE_BADNUM 0 -#define DEFAULT_ENCODE_KEEP_BUFFER 1 - -typedef enum { - T_OBJ_BEGIN, - T_OBJ_END, - T_ARR_BEGIN, - T_ARR_END, - T_STRING, - T_NUMBER, - T_BOOLEAN, - T_NULL, - T_COLON, - T_COMMA, - T_END, - T_WHITESPACE, - T_ERROR, - T_UNKNOWN -} json_token_type_t; - -static const char *json_token_type_name[] = { - "T_OBJ_BEGIN", - "T_OBJ_END", - "T_ARR_BEGIN", - "T_ARR_END", - "T_STRING", - "T_NUMBER", - "T_BOOLEAN", - "T_NULL", - "T_COLON", - "T_COMMA", - "T_END", - "T_WHITESPACE", - "T_ERROR", - "T_UNKNOWN", - NULL -}; - -typedef struct { - json_token_type_t ch2token[256]; - char escape2char[256]; /* Decoding */ -#if 0 - char escapes[35][8]; /* Pre-generated escape string buffer */ - char *char2escape[256]; /* Encoding */ -#endif - strbuf_t encode_buf; - char number_fmt[8]; /* "%.XXg\0" */ - int current_depth; - - int encode_sparse_convert; - int encode_sparse_ratio; - int encode_sparse_safe; - int encode_max_depth; - int encode_refuse_badnum; - int decode_refuse_badnum; - int encode_keep_buffer; - int encode_number_precision; -} json_config_t; - -typedef struct { - const char *data; - int index; - strbuf_t *tmp; /* Temporary storage for strings */ - json_config_t *cfg; -} json_parse_t; - -typedef struct { - json_token_type_t type; - int index; - union { - const char *string; - double number; - int boolean; - } value; - int string_len; -} json_token_t; +#include "lua_cjson.h" +#include "utf8_decoder.h" +#include "base64.h" +#include "itoa.h" +#include "atoluanumber.h" static const char *char2escape[256] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", @@ -169,29 +88,76 @@ static const char *char2escape[256] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, }; -static int json_config_key; - /* ===== CONFIGURATION ===== */ static json_config_t *json_fetch_config(lua_State *l) { json_config_t *cfg; - lua_pushlightuserdata(l, &json_config_key); - lua_gettable(l, LUA_REGISTRYINDEX); - cfg = lua_touserdata(l, -1); + cfg = (json_config_t*)lua_touserdata(l, lua_upvalueindex(1)); if (!cfg) luaL_error(l, "BUG: Unable to fetch CJSON configuration"); - lua_pop(l, 1); - return cfg; } -static void json_verify_arg_count(lua_State *l, int args) +/* Ensure the correct number of arguments have been provided. + * Pad with nil to allow other functions to simply check arg[i] + * to find whether an argument was provided */ +static json_config_t *json_arg_init(lua_State *l, int args) { luaL_argcheck(l, lua_gettop(l) <= args, args + 1, "found too many arguments"); + + while (lua_gettop(l) < args) + lua_pushnil(l); + + return json_fetch_config(l); +} + +/* Process integer options for configuration functions */ +static int json_integer_option(lua_State *l, int optindex, int *setting, + int min, int max) +{ + char errmsg[64]; + int value; + + if (!lua_isnil(l, optindex)) { + value = (int)luaL_checkinteger(l, optindex); + snprintf(errmsg, sizeof(errmsg), "expected integer between %d and %d", min, max); + luaL_argcheck(l, min <= value && value <= max, 1, errmsg); + *setting = value; + } + + lua_pushinteger(l, *setting); + + return 1; +} + +/* Process enumerated arguments for a configuration function */ +static int json_enum_option(lua_State *l, int optindex, int *setting, + const char **options, int bool_true) +{ + static const char *bool_options[] = { "off", "on", NULL }; + + if (!options) { + options = bool_options; + bool_true = 1; + } + + if (!lua_isnil(l, optindex)) { + if (bool_true && lua_isboolean(l, optindex)) + *setting = lua_toboolean(l, optindex) * bool_true; + else + *setting = luaL_checkoption(l, optindex, NULL, options); + } + + if (bool_true && (*setting == 0 || *setting == bool_true)) + lua_pushboolean(l, *setting); + else + lua_pushstring(l, options[*setting]); + + return 1; } /* Configures handling of extremely sparse arrays: @@ -200,29 +166,11 @@ static void json_verify_arg_count(lua_State *l, int args) * safe: Always use an array when the max index <= safe */ static int json_cfg_encode_sparse_array(lua_State *l) { - json_config_t *cfg; - int val; - - json_verify_arg_count(l, 3); - cfg = json_fetch_config(l); - - switch (lua_gettop(l)) { - case 3: - val = luaL_checkinteger(l, 3); - luaL_argcheck(l, val >= 0, 3, "expected integer >= 0"); - cfg->encode_sparse_safe = val; - case 2: - val = luaL_checkinteger(l, 2); - luaL_argcheck(l, val >= 0, 2, "expected integer >= 0"); - cfg->encode_sparse_ratio = val; - case 1: - luaL_argcheck(l, lua_isboolean(l, 1), 1, "expected boolean"); - cfg->encode_sparse_convert = lua_toboolean(l, 1); - } + json_config_t *cfg = json_arg_init(l, 3); - lua_pushboolean(l, cfg->encode_sparse_convert); - lua_pushinteger(l, cfg->encode_sparse_ratio); - lua_pushinteger(l, cfg->encode_sparse_safe); + json_enum_option(l, 1, &cfg->encode_sparse_convert, NULL, 1); + json_integer_option(l, 2, &cfg->encode_sparse_ratio, 0, INT_MAX); + json_integer_option(l, 3, &cfg->encode_sparse_safe, 0, INT_MAX); return 3; } @@ -231,110 +179,175 @@ static int json_cfg_encode_sparse_array(lua_State *l) * encoding */ static int json_cfg_encode_max_depth(lua_State *l) { - json_config_t *cfg; - int depth; - - json_verify_arg_count(l, 1); - cfg = json_fetch_config(l); + json_config_t *cfg = json_arg_init(l, 1); - if (lua_gettop(l)) { - depth = luaL_checkinteger(l, 1); - luaL_argcheck(l, depth > 0, 1, "expected positive integer"); - cfg->encode_max_depth = depth; - } - - lua_pushinteger(l, cfg->encode_max_depth); - - return 1; + return json_integer_option(l, 1, &cfg->encode_max_depth, 1, INT_MAX); } -static void json_set_number_precision(json_config_t *cfg, int prec) +/* Configures the maximum number of nested arrays/objects allowed when + * encoding */ +static int json_cfg_decode_max_depth(lua_State *l) { - cfg->encode_number_precision = prec; - sprintf(cfg->number_fmt, "%%.%dg", prec); + json_config_t *cfg = json_arg_init(l, 1); + + return json_integer_option(l, 1, &cfg->decode_max_depth, 1, INT_MAX); } /* Configures number precision when converting doubles to text */ static int json_cfg_encode_number_precision(lua_State *l) { - json_config_t *cfg; - int precision; + json_config_t *cfg = json_arg_init(l, 1); + int r = json_integer_option(l, 1, &cfg->encode_number_precision, 1, 16); + return r; +} - json_verify_arg_count(l, 1); - cfg = json_fetch_config(l); +/* Configures JSON encoding converting utf-8 text to \uxxxx */ +/* false, true, char */ +static int json_cfg_encode_escape_utf8(lua_State* l) +{ + json_config_t* cfg = json_arg_init(l, 1); + const unsigned char* str; + size_t len; - if (lua_gettop(l)) { - precision = luaL_checkinteger(l, 1); - luaL_argcheck(l, 1 <= precision && precision <= 14, 1, - "expected integer between 1 and 14"); - json_set_number_precision(cfg, precision); + switch (lua_type(l, 1)) { + case LUA_TBOOLEAN: + cfg->encode_escape_utf8 = lua_toboolean(l, 1) ? 1 : 0; + break; + case LUA_TSTRING: + str = (unsigned char*)lua_tolstring(l, 1, &len); + if (len > 0) { + cfg->encode_escape_utf8 = -1 * (unsigned int)str[0]; + } + break; } - lua_pushinteger(l, cfg->encode_number_precision); - return 1; } +/* Configures how to treat empty table when encode lua table */ +static int json_cfg_encode_empty_table_as_object(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + return json_enum_option(l, 1, &cfg->encode_empty_table_as_object, NULL, 1); +} + /* Configures JSON encoding buffer persistence */ static int json_cfg_encode_keep_buffer(lua_State *l) { - json_config_t *cfg; + json_config_t *cfg = json_arg_init(l, 1); + int old_value; + + old_value = cfg->encode_keep_buffer; + + json_enum_option(l, 1, &cfg->encode_keep_buffer, NULL, 1); + + /* Init / free the buffer if the setting has changed */ + if (old_value ^ cfg->encode_keep_buffer) { + if (cfg->encode_keep_buffer) + strbuf_init(&cfg->encode_buf, 0); + else + strbuf_free(&cfg->encode_buf); + } - json_verify_arg_count(l, 1); - cfg = json_fetch_config(l); + return 1; +} - if (lua_gettop(l)) { - luaL_checktype(l, 1, LUA_TBOOLEAN); - cfg->encode_keep_buffer = lua_toboolean(l, 1); +#if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV) +void json_verify_invalid_number_setting(lua_State *l, int *setting) +{ + if (*setting == 1) { + *setting = 0; + luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported."); } +} +#else +#define json_verify_invalid_number_setting(l, s) do { } while(0) +#endif + +static int json_cfg_encode_invalid_numbers(lua_State *l) +{ + static const char *options[] = { "off", "on", "null", NULL }; + json_config_t *cfg = json_arg_init(l, 1); - lua_pushboolean(l, cfg->encode_keep_buffer); + json_enum_option(l, 1, &cfg->encode_invalid_numbers, options, 1); + + json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers); return 1; } -/* On argument: decode enum and set config variables - * **options must point to a NULL terminated array of 4 enums - * Returns: current enum value */ -static void json_enum_option(lua_State *l, const char **options, - int *opt1, int *opt2) +static int json_cfg_decode_invalid_numbers(lua_State *l) { - int setting; + json_config_t *cfg = json_arg_init(l, 1); - if (lua_gettop(l)) { - if (lua_isboolean(l, 1)) - setting = lua_toboolean(l, 1) * 3; - else - setting = luaL_checkoption(l, 1, NULL, options); + json_enum_option(l, 1, &cfg->decode_invalid_numbers, NULL, 1); - *opt1 = setting & 1 ? 1 : 0; - *opt2 = setting & 2 ? 1 : 0; - } else { - setting = *opt1 | (*opt2 << 1); - } + json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers); - if (setting) - lua_pushstring(l, options[setting]); - else - lua_pushboolean(l, 0); + return 1; } +static void json_push_registry_table(lua_State* l) +{ + lua_pushlightuserdata(l, CJSON_LIGHTUSERDATA); + lua_rawget(l, LUA_REGISTRYINDEX); +} -/* When enabled, rejects: NaN, Infinity, hexidecimal numbers */ -static int json_cfg_refuse_invalid_numbers(lua_State *l) +static int json_cfg_set_invalid_type_encoder(lua_State* l) { - static const char *options_enc_dec[] = { "none", "encode", "decode", - "both", NULL }; - json_config_t *cfg; + luaL_checktype(l, 1, LUA_TFUNCTION); // [func] + lua_pushnumber(l, CJSON_REGISTRY_INVALID_TYPE_ENCODER); // [func, idx] + json_push_registry_table(l); // [func, idx, cjson_registry] + lua_insert(l, -3); // [cjson_registry, func, idx] + lua_insert(l, -2); // [cjson_registry, idx, func] + lua_rawset(l, -3); // [cjson_registry] + lua_pop(l, 1); + return 0; +} + +static int json_push_invalid_type_encoder(lua_State* l) +{ + json_push_registry_table(l); // [cjson_registry] + lua_pushnumber(l, CJSON_REGISTRY_INVALID_TYPE_ENCODER); // [cjson_registry, idx] + lua_rawget(l, -2); // [cjson_registry, invalid_type_encoder] + lua_remove(l, -2); // [invalid_type_encoder] + if (lua_type(l, -1) == LUA_TNIL) { + lua_pop(l, 1); + return 0; + } + else { + return 1; + } +} - json_verify_arg_count(l, 1); - cfg = json_fetch_config(l); +static int json_cfg_encode_escape_forward_slash(lua_State *l) +{ + int ret; + json_config_t *cfg = json_arg_init(l, 1); - json_enum_option(l, options_enc_dec, - &cfg->encode_refuse_badnum, - &cfg->decode_refuse_badnum); + ret = json_enum_option(l, 1, &cfg->encode_escape_forward_slash, NULL, 1); + if (cfg->encode_escape_forward_slash) { + char2escape['/'] = "\\/"; + } else { + char2escape['/'] = NULL; + } + return ret; +} - return 1; +static int json_cfg_encode_numbers_as_base64(lua_State* l) +{ + json_config_t* cfg = json_arg_init(l, 1); + int ret = json_enum_option(l, 1, &cfg->encode_numbers_as_base64, NULL, 1); + return ret; +} + + +static int json_cfg_decode_numbers_as_base64(lua_State* l) +{ + json_config_t* cfg = json_arg_init(l, 1); + int ret = json_enum_option(l, 1, &cfg->decode_numbers_as_base64, NULL, 1); + return ret; } static int json_destroy_config(lua_State *l) @@ -362,16 +375,24 @@ static void json_create_config(lua_State *l) lua_setfield(l, -2, "__gc"); lua_setmetatable(l, -2); - strbuf_init(&cfg->encode_buf, 0); - cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT; cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO; cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; - cfg->encode_max_depth = DEFAULT_MAX_DEPTH; - cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM; - cfg->decode_refuse_badnum = DEFAULT_DECODE_REFUSE_BADNUM; + cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH; + cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH; + cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS; + cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS; cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; - json_set_number_precision(cfg, 14); + cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; + cfg->encode_empty_table_as_object = DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT; + cfg->encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH; + cfg->encode_escape_utf8 = DEFAULT_ENCODE_ESCAPE_UTF8; + cfg->encode_numbers_as_base64 = DEFAULT_ENCODE_NUMBERS_AS_BASE64; + cfg->decode_numbers_as_base64 = DEFAULT_ENCODE_NUMBERS_AS_BASE64; + +#if DEFAULT_ENCODE_KEEP_BUFFER > 0 + strbuf_init(&cfg->encode_buf, 0); +#endif /* Decoding init */ @@ -417,74 +438,96 @@ static void json_create_config(lua_State *l) cfg->escape2char['f'] = '\f'; cfg->escape2char['r'] = '\r'; cfg->escape2char['u'] = 'u'; /* Unicode parsing required */ - - -#if 0 - /* Initialise separate storage for pre-generated escape codes. - * Escapes 0-31 map directly, 34, 92, 127 follow afterwards to - * save memory. */ - for (i = 0 ; i < 32; i++) - sprintf(cfg->escapes[i], "\\u%04x", i); - strcpy(cfg->escapes[8], "\b"); /* Override simpler escapes */ - strcpy(cfg->escapes[9], "\t"); - strcpy(cfg->escapes[10], "\n"); - strcpy(cfg->escapes[12], "\f"); - strcpy(cfg->escapes[13], "\r"); - strcpy(cfg->escapes[32], "\\\""); /* chr(34) */ - strcpy(cfg->escapes[33], "\\\\"); /* chr(92) */ - sprintf(cfg->escapes[34], "\\u%04x", 127); /* char(127) */ - - /* Initialise encoding escape lookup table */ - for (i = 0; i < 32; i++) - cfg->char2escape[i] = cfg->escapes[i]; - for (i = 32; i < 256; i++) - cfg->char2escape[i] = NULL; - cfg->char2escape[34] = cfg->escapes[32]; - cfg->char2escape[92] = cfg->escapes[33]; - cfg->char2escape[127] = cfg->escapes[34]; -#endif } /* ===== ENCODING ===== */ -static void json_encode_exception(lua_State *l, json_config_t *cfg, int lindex, +static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex, const char *reason) { if (!cfg->encode_keep_buffer) - strbuf_free(&cfg->encode_buf); + strbuf_free(json); luaL_error(l, "Cannot serialise %s: %s", lua_typename(l, lua_type(l, lindex)), reason); } /* json_append_string args: * - lua_State + * - JSON config * - JSON strbuf * - String (Lua stack index) * * Returns nothing. Doesn't remove string from Lua stack */ -static void json_append_string(lua_State *l, strbuf_t *json, int lindex) +static void json_append_string(lua_State* l, json_config_t* cfg, strbuf_t* json, int lindex) { const char *escstr; - int i; + unsigned i; const char *str; size_t len; + /* for utf8 escape */ + uint32_t prev_state = 0; + uint32_t state = 0; + uint32_t codepoint; + unsigned char utf8_place_char; + char utf8_buf[7]; + str = lua_tolstring(l, lindex, &len); /* Worst case is len * 6 (all unicode escapes). * This buffer is reused constantly for small strings * If there are any excess pages, they won't be hit anyway. * This gains ~5% speedup. */ - strbuf_ensure_empty_length(json, len * 6 + 2); + size_t buf_len = len * 6 + 2; + if (buf_len > INT_MAX) buf_len = INT_MAX; + strbuf_ensure_empty_length(json, (int)buf_len); strbuf_append_char_unsafe(json, '\"'); - for (i = 0; i < len; i++) { - escstr = char2escape[(unsigned char)str[i]]; - if (escstr) - strbuf_append_string(json, escstr); - else - strbuf_append_char_unsafe(json, str[i]); + if (cfg->encode_escape_utf8 < 0 || cfg->encode_escape_utf8 == 1) { + utf8_place_char = cfg->encode_escape_utf8 == 1 ? 0 : ((unsigned char)(-1 * cfg->encode_escape_utf8)); + /* fprintf(stderr, "%c", utf8_place_char); */ + + for (i = 0; i < len; prev_state = state, ++i) { + switch (utf8_decode(&state, &codepoint, (unsigned char)str[i])) { + case UTF8_ACCEPT: + /* A properly encoded character has been found. */ + if (codepoint >= 0 && codepoint <= 127) { + escstr = char2escape[(unsigned char)codepoint]; + if (escstr) + strbuf_append_string(json, escstr); + else + strbuf_append_char_unsafe(json, codepoint); + } + else { + snprintf(utf8_buf, 7, "\\u%04x", codepoint); + strbuf_append_string(json, utf8_buf); + } + break; + + case UTF8_REJECT: + /* The byte is invalid, replace it and restart. */ + if (!utf8_place_char) { + luaL_error(l, "utf8 string (%s) invalid at (%d)", str, i); + } + + strbuf_append_char_unsafe(json, utf8_place_char); + state = UTF8_ACCEPT; + if (prev_state != UTF8_ACCEPT) + --i; + break; + } + } + } + else { + for (i = 0; i < len; i++) { + escstr = char2escape[(unsigned char)str[i]]; + if (escstr) + strbuf_append_string(json, escstr); + else + strbuf_append_char_unsafe(json, str[i]); + } } + strbuf_append_char_unsafe(json, '\"'); } @@ -492,34 +535,74 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex) * -1 object (not a pure array) * >=0 elements in array */ -static int lua_array_length(lua_State *l, json_config_t *cfg) +static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json) { double k; - int max; - int items; + size_t max; + size_t items; max = 0; items = 0; - lua_pushnil(l); - /* table, startkey */ - while (lua_next(l, -2) != 0) { - /* table, key, value */ - if (lua_type(l, -2) == LUA_TNUMBER && - (k = lua_tonumber(l, -2))) { - /* Integer >= 1 ? */ - if (floor(k) == k && k >= 1) { - if (k > max) - max = k; - items++; - lua_pop(l, 1); - continue; + if (luaL_getmetafield(l, -1, "__pairs")) { + int index = lua_gettop(l) - 1; + int type; + lua_pushvalue(l, -2); + lua_call(l, 1, 3); + lua_newtable(l); + lua_replace(l, index); + while (1) { + lua_pushvalue(l, -2); + lua_pushvalue(l, -2); + lua_copy(l, -5, -3); + lua_call(l, 2, 2); + type = lua_type(l, -2); + if (type == LUA_TNIL) + break; + if (items >= 0) { + if (type == LUA_TNUMBER && + (k = lua_tonumber(l, -2))) { + if (floor(k) == k && k >= -1) { + if (k > max) + max = (size_t)k; + items++; + } + else { + items = -1; + } + } + else { + items = -1; + } } + lua_pushvalue(l, -1); + lua_copy(l, -3, -2); + lua_rawset(l, index); + } + lua_settop(l, index); + if (items < 0) + return -1; + } + else { + lua_pushnil(l); + /* table, startkey */ + while (lua_next(l, -2) != 0) { + /* table, key, value */ + if (lua_type(l, -2) == LUA_TNUMBER && + (k = lua_tonumber(l, -2))) { + /* Integer >= 1 ? */ + if (floor(k) == k && k >= 1) { + if (k > max) + max = (size_t)k; + items++; + lua_pop(l, 1); + continue; + } + } + /* Must not be an array (non integer key) */ + lua_pop(l, 2); + return -1; } - - /* Must not be an array (non integer key) */ - lua_pop(l, 2); - return -1; } /* Encode excessively sparse arrays as objects (if enabled) */ @@ -527,39 +610,50 @@ static int lua_array_length(lua_State *l, json_config_t *cfg) max > items * cfg->encode_sparse_ratio && max > cfg->encode_sparse_safe) { if (!cfg->encode_sparse_convert) - json_encode_exception(l, cfg, -1, "excessively sparse array"); + json_encode_exception(l, cfg, json, -1, "excessively sparse array"); return -1; } - return max; + if (max > INT_MAX) max = INT_MAX; + return (int)max; } -static void json_encode_descend(lua_State *l, json_config_t *cfg) +static void json_check_encode_depth(lua_State *l, json_config_t *cfg, + int current_depth, strbuf_t *json) { - cfg->current_depth++; + /* Ensure there are enough slots free to traverse a table (key, + * value) and push a string for a potential error message. + * + * Unlike "decode", the key and value are still on the stack when + * lua_checkstack() is called. Hence an extra slot for luaL_error() + * below is required just in case the next check to lua_checkstack() + * fails. + * + * While this won't cause a crash due to the EXTRA_STACK reserve + * slots, it would still be an improper use of the API. */ + if (current_depth <= cfg->encode_max_depth && lua_checkstack(l, 3)) + return; - if (cfg->current_depth > cfg->encode_max_depth) { - if (!cfg->encode_keep_buffer) - strbuf_free(&cfg->encode_buf); - luaL_error(l, "Cannot serialise, excessive nesting (%d)", - cfg->current_depth); - } + if (!cfg->encode_keep_buffer) + strbuf_free(json); + + luaL_error(l, "Cannot serialise, excessive nesting (%d)", + current_depth); } -static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json); +static void json_append_data(lua_State *l, json_config_t *cfg, + int current_depth, strbuf_t *json); /* json_append_array args: * - lua_State * - JSON strbuf * - Size of passwd Lua array (top of stack) */ -static void json_append_array(lua_State *l, json_config_t *cfg, strbuf_t *json, - int array_length) +static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth, + strbuf_t *json, int array_length) { int comma, i; - json_encode_descend(l, cfg); - strbuf_append_char(json, '['); comma = 0; @@ -570,38 +664,141 @@ static void json_append_array(lua_State *l, json_config_t *cfg, strbuf_t *json, comma = 1; lua_rawgeti(l, -1, i); - json_append_data(l, cfg, json); + json_append_data(l, cfg, current_depth, json); lua_pop(l, 1); } strbuf_append_char(json, ']'); +} + +// base64 encoded value should be at least: 17 chars = 12chars + header 2 chars + 2x'"' + "\0" +// "L=0123456789AB\0" or "D=0123456789AB\0" +// header should be with heading " +static int json_append_number_as_base64(strbuf_t* json, bytemap64 nb, const char* header, const int header_len) +{ + //int buf_len = base64_req_len(sizeof(nb)); + const int buf_len = 13; + strbuf_ensure_empty_length(json, buf_len + header_len + 1); + strbuf_append_mem_unsafe(json, header, header_len); + int len = int64_to_base64(nb, strbuf_empty_ptr(json), buf_len); + if (len > 0) + strbuf_extend_length(json, len); + strbuf_append_char(json, '"'); + return (len > 0); +} - cfg->current_depth--; + +static void json_append_int64_number(INT64 num, lua_State* l, json_config_t* cfg, strbuf_t* json, int lindex) +{ + strbuf_ensure_empty_length(json, ITOA_BUFSIZE); /* max length of int64 is 22 */ + int len = itoa_vitaut(num, strbuf_empty_ptr(json), ITOA_BUFSIZE); + if (len > 0) + strbuf_extend_length(json, len); + else + json_encode_exception(l, cfg, json, lindex, "itoa fail!"); } -static void json_append_number(lua_State *l, strbuf_t *json, int index, - json_config_t *cfg) +static void json_append_number(lua_State* l, json_config_t* cfg, + strbuf_t* json, int lindex) { - double num = lua_tonumber(l, index); + int len = 0; - if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num))) - json_encode_exception(l, cfg, index, "must not be NaN or Inf"); +#if LUA_VERSION_NUM >= 503 + if (lua_isinteger(l, lindex)) { + lua_Integer num = lua_tointeger(l, lindex); + json_append_int64_number(num, l, cfg, json, lindex); + return; + } +#endif - /* Lowest double printed with %.14g is 21 characters long: - * -1.7976931348623e+308 - * - * Use 32 to include the \0, and a few extra just in case.. - */ - strbuf_append_fmt(json, 32, cfg->number_fmt, num); + double num = lua_tonumber(l, lindex); + if (is_IEEE754_64Bit_double_AnInt(num)) + { + INT64 asInt = (INT64)num; + json_append_int64_number(num, l, cfg, json, lindex); + return; + } + + if (cfg->encode_numbers_as_base64) + { + //int buf_len = base64_req_len(sizeof(nb)); + // base64 encoded value should be at least: 17 chars = 12chars + header 2 chars + 2x'"' + "\0" + // "L=0123456789AB\0" or "D=0123456789AB\0" + const char header[] = "\"D="; + if (!json_append_number_as_base64(json, *((bytemap64*)&num), header, sizeof(header) - 1)) + json_encode_exception(l, cfg, json, lindex, "json_append_number_as_base64 fail!"); + return; + } + + if (cfg->encode_invalid_numbers == 0) { + /* Prevent encoding invalid numbers */ + if (isinf(num) || isnan(num)) + json_encode_exception(l, cfg, json, lindex, + "must not be NaN or Infinity"); + } + else if (cfg->encode_invalid_numbers == 1) { + /* Encode NaN/Infinity separately to ensure Javascript compatible + * values are used. */ + if (isnan(num)) { + strbuf_append_mem(json, "NaN", 3); + return; + } + if (isinf(num)) { + if (num < 0) + strbuf_append_mem(json, "-Infinity", 9); + else + strbuf_append_mem(json, "Infinity", 8); + return; + } + } + else { + /* Encode invalid numbers as "null" */ + if (isinf(num) || isnan(num)) { + strbuf_append_mem(json, "null", 4); + return; + } + } + + strbuf_ensure_empty_length(json, _CVTBUFSIZE); + if (0 == _gcvt_s(strbuf_empty_ptr(json), _CVTBUFSIZE, num, cfg->encode_number_precision)) + { + len = (int)strnlen_s(strbuf_empty_ptr(json), _CVTBUFSIZE); + if (len > 0) + { + strbuf_extend_length(json, len); + return; + } + } + + json_encode_exception(l, cfg, json, lindex, "Double to String conversion Fail!"); } +static int json_append_tojson_invoke(lua_State* l, json_config_t* cfg, strbuf_t* json) { + // [data] + if (lua_getmetatable(l, -1)) { // [data, mt] + lua_getfield(l, -1, "__tojson"); // [data, mt, __tojson] + if (lua_type(l, -1) == LUA_TFUNCTION) { + lua_pushvalue(l, -3); // [data, mt, __tojson, data] + lua_call(l, 1, 1); // [data, mt, string] + if (lua_type(l, -1) != LUA_TSTRING) { + json_encode_exception(l, cfg, json, -1, "__tojson(v) did not return a string"); + /* never returns */ + } + strbuf_append_string(json, lua_tolstring(l, -1, 0)); + lua_settop(l, -3); + return 1; + } + lua_settop(l, -3); + } + return 0; +} + + static void json_append_object(lua_State *l, json_config_t *cfg, - strbuf_t *json) + int current_depth, strbuf_t *json) { int comma, keytype; - json_encode_descend(l, cfg); - /* Object */ strbuf_append_char(json, '{'); @@ -618,39 +815,37 @@ static void json_append_object(lua_State *l, json_config_t *cfg, keytype = lua_type(l, -2); if (keytype == LUA_TNUMBER) { strbuf_append_char(json, '"'); - json_append_number(l, json, -2, cfg); + json_append_number(l, cfg, json, -2); strbuf_append_mem(json, "\":", 2); } else if (keytype == LUA_TSTRING) { - json_append_string(l, json, -2); + json_append_string(l, cfg, json, -2); strbuf_append_char(json, ':'); } else { - json_encode_exception(l, cfg, -2, + json_encode_exception(l, cfg, json, -2, "table key must be a number or string"); /* never returns */ } /* table, key, value */ - json_append_data(l, cfg, json); + json_append_data(l, cfg, current_depth, json); lua_pop(l, 1); /* table, key */ } strbuf_append_char(json, '}'); - - cfg->current_depth--; } /* Serialise Lua data into JSON string. */ -static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) +static void json_append_data(lua_State *l, json_config_t *cfg, + int current_depth, strbuf_t *json) { int len; - switch (lua_type(l, -1)) { case LUA_TSTRING: - json_append_string(l, json, -1); + json_append_string(l, cfg, json, -1); break; case LUA_TNUMBER: - json_append_number(l, json, -1, cfg); + json_append_number(l, cfg, json, -1); break; case LUA_TBOOLEAN: if (lua_toboolean(l, -1)) @@ -659,11 +854,20 @@ static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) strbuf_append_mem(json, "false", 5); break; case LUA_TTABLE: - len = lua_array_length(l, cfg); - if (len > 0) - json_append_array(l, cfg, json, len); - else - json_append_object(l, cfg, json); + // invoke getmetatable(val).__tojson() if present #14 + // https://github.com/mpx/lua-cjson/pull/14/commits/639d93954c38bbca28ab07b8786fe488bb8d4f3c + if (json_append_tojson_invoke(l, cfg, json)) { break; } + + current_depth++; + json_check_encode_depth(l, cfg, current_depth, json); + + 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); + } else { + json_append_object(l, cfg, current_depth, json); + } break; case LUA_TNIL: strbuf_append_mem(json, "null", 4); @@ -671,43 +875,57 @@ static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) case LUA_TLIGHTUSERDATA: if (lua_touserdata(l, -1) == NULL) { strbuf_append_mem(json, "null", 4); - break; } + break; default: /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, * and LUA_TLIGHTUSERDATA) cannot be serialised */ - json_encode_exception(l, cfg, -1, "type not supported"); - /* never returns */ + if (json_push_invalid_type_encoder(l)) { // [data, invalid_type_encoder] + lua_pushvalue(l, -2); // [data, invalid_type_encoder, data] + lua_call(l, 1, 1); // [data, encoder_result] + if (lua_type(l, -1) != LUA_TSTRING) { + json_encode_exception(l, cfg, json, -1, "invalid_type_encoder(v) did not return a string"); + /* never returns */ + } + else { + strbuf_append_string(json, lua_tolstring(l, -1, 0)); + lua_pop(l, 1); // [data] + } + } + else { + json_encode_exception(l, cfg, json, -1, "type not supported"); + /* never returns */ + } } } static int json_encode(lua_State *l) { - json_config_t *cfg; + json_config_t *cfg = json_fetch_config(l); + strbuf_t local_encode_buf; + strbuf_t *encode_buf; char *json; int len; - /* Can't use json_verify_arg_count() since we need to ensure - * there is only 1 argument */ luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); - cfg = json_fetch_config(l); - cfg->current_depth = 0; - - /* Reset the persistent buffer if it exists. - * Otherwise allocate a new buffer. */ - if (strbuf_allocated(&cfg->encode_buf)) - strbuf_reset(&cfg->encode_buf); - else - strbuf_init(&cfg->encode_buf, 0); + if (!cfg->encode_keep_buffer) { + /* Use private buffer */ + encode_buf = &local_encode_buf; + strbuf_init(encode_buf, 0); + } else { + /* Reuse existing buffer */ + encode_buf = &cfg->encode_buf; + strbuf_reset(encode_buf); + } - json_append_data(l, cfg, &cfg->encode_buf); - json = strbuf_string(&cfg->encode_buf, &len); + json_append_data(l, cfg, 0, encode_buf); + json = strbuf_string(encode_buf, &len); lua_pushlstring(l, json, len); if (!cfg->encode_keep_buffer) - strbuf_free(&cfg->encode_buf); + strbuf_free(encode_buf); return 1; } @@ -806,7 +1024,7 @@ static int json_append_unicode_escape(json_parse_t *json) int escape_len = 6; /* Fetch UTF-16 code unit */ - codepoint = decode_hex4(&json->data[json->index + 2]); + codepoint = decode_hex4(json->ptr + 2); if (codepoint < 0) return -1; @@ -822,13 +1040,13 @@ static int json_append_unicode_escape(json_parse_t *json) return -1; /* Ensure the next code is a unicode escape */ - if (json->data[json->index + escape_len] != '\\' || - json->data[json->index + escape_len + 1] != 'u') { + if (*(json->ptr + escape_len) != '\\' || + *(json->ptr + escape_len + 1) != 'u') { return -1; } /* Fetch the next codepoint */ - surrogate_low = decode_hex4(&json->data[json->index + 2 + escape_len]); + surrogate_low = decode_hex4(json->ptr + 2 + escape_len); if (surrogate_low < 0) return -1; @@ -850,7 +1068,7 @@ static int json_append_unicode_escape(json_parse_t *json) /* Append bytes and advance parse index */ strbuf_append_mem_unsafe(json->tmp, utf8, len); - json->index += escape_len; + json->ptr += escape_len; return 0; } @@ -859,7 +1077,9 @@ static void json_set_token_error(json_token_t *token, json_parse_t *json, const char *errtype) { token->type = T_ERROR; - token->index = json->index; + size_t idx = json->ptr - json->data; + if (idx > INT_MAX) idx = INT_MAX; + token->index = (int)idx; token->value.string = errtype; } @@ -869,15 +1089,18 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) char ch; /* Caller must ensure a string is next */ - assert(json->data[json->index] == '"'); + assert(*json->ptr == '"'); /* Skip " */ - json->index++; + json->ptr++; /* json->tmp is the temporary strbuf used to accumulate the - * decoded string value. */ + * decoded string value. + * json->tmp is sized to handle JSON containing only a string value. + */ strbuf_reset(json->tmp); - while ((ch = json->data[json->index]) != '"') { + + while ((ch = *json->ptr) != '"') { if (!ch) { /* Premature end of the string */ json_set_token_error(token, json, "unexpected end of string"); @@ -887,7 +1110,7 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) /* Handle escapes */ if (ch == '\\') { /* Fetch escape character */ - ch = json->data[json->index + 1]; + ch = *(json->ptr + 1); /* Translate escape code and append to tmp string */ ch = escape2char[(unsigned char)ch]; @@ -905,14 +1128,14 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) } /* Skip '\' */ - json->index++; + json->ptr++; } /* Append normal character or translated single character * Unicode escapes are handled above */ strbuf_append_char_unsafe(json->tmp, ch); - json->index++; + json->ptr++; } - json->index++; /* Eat final quote (") */ + json->ptr++; /* Eat final quote (") */ strbuf_ensure_null(json->tmp); @@ -920,89 +1143,77 @@ static void json_next_string_token(json_parse_t *json, json_token_t *token) token->value.string = strbuf_string(json->tmp, &token->string_len); } -/* JSON numbers should take the following form: - * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)? - * - * json_next_number_token() uses strtod() which allows other forms: - * - numbers starting with '+' - * - NaN, -NaN, infinity, -infinity - * - hexidecimal numbers - * - numbers with leading zeros - * - * json_is_invalid_number() detects "numbers" which may pass strtod()'s - * error checking, but should not be allowed with strict JSON. - * - * json_is_invalid_number() may pass numbers which cause strtod() - * to generate an error. - */ -static int json_is_invalid_number(json_parse_t *json) +// Try to decode base64 encoded INT64 or Decimal (64bit), if success returns true +static int json_next_base64_number_token(json_parse_t* json, json_token_t* token) { - int i = json->index; - - /* Reject numbers starting with + */ - if (json->data[i] == '+') - return 1; - - /* Skip minus sign if it exists */ - if (json->data[i] == '-') - i++; - - /* Reject numbers starting with 0x, or leading zeros */ - if (json->data[i] == '0') { - int ch2 = json->data[i + 1]; - - if ((ch2 | 0x20) == 'x' || /* Hex */ - ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ - return 1; + const char *head_lng = "\"L="; + const char *head_dbl = "\"D="; + int is_long = strncmp(json->ptr, head_lng, 3) == 0; + int is_double = strncmp(json->ptr, head_dbl, 3) == 0; + if (!is_long && !is_double) return 0; - } else if (json->data[i] <= '9') { - return 0; /* Ordinary number */ - } + // calc token len + const char* b64ptr = json->ptr + 3; + const char* endptr = b64ptr; + while (*endptr != '"') + { + if (!*endptr) + { + /* Premature end of the string */ + json_set_token_error(token, json, "unexpected end of string"); + return 0; + } + endptr++; + } - /* Reject inf/nan */ - if (!strncasecmp(&json->data[i], "inf", 3)) - return 1; - if (!strncasecmp(&json->data[i], "nan", 3)) - return 1; + // base64 encoded value should be at least 12chars + // L=0123456789AB + if (endptr - b64ptr < 12) + return 0; - /* Pass all other numbers which may still be invalid, but - * strtod() will catch them. */ - return 0; -} + bytemap64 b64; + if (!base64_to_bin64(b64ptr, &b64)) + return 0; // Invalid base64 encoded number -static void json_next_number_token(json_parse_t *json, json_token_t *token) -{ - const char *startptr; - char *endptr; - - token->type = T_NUMBER; - startptr = &json->data[json->index]; - token->value.number = strtod(&json->data[json->index], &endptr); - if (startptr == endptr) - json_set_token_error(token, json, "invalid number"); + if (is_double) { + token->type = T_NUMBER; + token->value.number = b64.as_double; + } else - json->index += endptr - startptr; /* Skip the processed number */ - - return; + { + token->type = T_INTEGER; + token->value.integer = b64.as_int64; + } + // Skip the processed number, Eat final quote (") + json->ptr = endptr + 1; + return 1; } /* Fills in the token struct. * T_STRING will return a pointer to the json_parse_t temporary string - * T_ERROR will leave the json->index pointer at the error. + * T_ERROR will leave the json->ptr pointer at the error. */ static void json_next_token(json_parse_t *json, json_token_t *token) { - json_token_type_t *ch2token = json->cfg->ch2token; + const json_token_type_t *ch2token = json->cfg->ch2token; int ch; - /* Eat whitespace. FIXME: UGLY */ - token->type = ch2token[(unsigned char)json->data[json->index]]; - while (token->type == T_WHITESPACE) - token->type = ch2token[(unsigned char)json->data[++json->index]]; + /* Eat whitespace. */ + while (1) { + ch = (unsigned char)*(json->ptr); + token->type = ch2token[ch]; + if (token->type != T_WHITESPACE) + break; + json->ptr++; + } - token->index = json->index; + /* Store location of new token. Required when throwing errors + * for unexpected tokens (syntax errors). */ + size_t idx = json->ptr - json->data; + if (idx > INT_MAX) idx = INT_MAX; + token->index = (int)idx; /* Don't advance the pointer for an error or the end */ if (token->type == T_ERROR) { @@ -1016,51 +1227,43 @@ static void json_next_token(json_parse_t *json, json_token_t *token) /* Found a known single character token, advance index and return */ if (token->type != T_UNKNOWN) { - json->index++; + json->ptr++; return; } - /* Process characters which triggered T_UNKNOWN */ - ch = json->data[json->index]; - - /* Must use strncmp() to match the front of the JSON string. + /* Process characters which triggered T_UNKNOWN + * + * Must use strncmp() to match the front of the JSON string. * JSON identifier must be lowercase. * When strict_numbers if disabled, either case is allowed for * Infinity/NaN (since we are no longer following the spec..) */ - if (ch == '"') { - json_next_string_token(json, token); - return; - } else if (ch == '-' || ('0' <= ch && ch <= '9')) { - if (json->cfg->decode_refuse_badnum && json_is_invalid_number(json)) { - json_set_token_error(token, json, "invalid number"); + if (ch == '"') + { + // Try to decode base64 encoded INT64 or Decimal (64bit), if success returns true + if (json->cfg->decode_numbers_as_base64 && json_next_base64_number_token(json, token)) return; - } - json_next_number_token(json, token); + json_next_string_token(json, token); return; - } else if (!strncmp(&json->data[json->index], "true", 4)) { + } + else if (!strncmp(json->ptr, "true", 4)) { token->type = T_BOOLEAN; token->value.boolean = 1; - json->index += 4; + json->ptr += 4; return; - } else if (!strncmp(&json->data[json->index], "false", 5)) { + } + else if (!strncmp(json->ptr, "false", 5)) { token->type = T_BOOLEAN; token->value.boolean = 0; - json->index += 5; + json->ptr += 5; return; - } else if (!strncmp(&json->data[json->index], "null", 4)) { + } + else if (!strncmp(json->ptr, "null", 4)) { token->type = T_NULL; - json->index += 4; - return; - } else if (!json->cfg->decode_refuse_badnum && - json_is_invalid_number(json)) { - /* When refuse_badnum is disabled, only attempt to process - * numbers we know are invalid JSON (Inf, NaN, hex) - * This is required to generate an appropriate token error, - * otherwise all bad tokens will register as "invalid number" - */ - json_next_number_token(json, token); + json->ptr += 4; return; } + else if (json_next_number_token(json, token)) + return; /* Token starts with t/f/n but isn't recognised above. */ json_set_token_error(token, json, "invalid token"); @@ -1089,13 +1292,37 @@ static void json_throw_parse_error(lua_State *l, json_parse_t *json, exp, found, token->index + 1); } -static void json_decode_checkstack(lua_State *l, json_parse_t *json, int n) +static inline void json_decode_ascend(json_parse_t *json) +{ + json->current_depth--; +} + +static void json_decode_descend(lua_State *l, json_parse_t *json, int slots) { - if (lua_checkstack(l, n)) + json->current_depth++; + + if (json->current_depth <= json->cfg->decode_max_depth && + lua_checkstack(l, slots)) { return; + } strbuf_free(json->tmp); - luaL_error(l, "Too many nested data structures"); + luaL_error(l, "Found too many nested data structures (%d) at character %d", + json->current_depth, json->ptr - json->data); +} + +static void json_set_metatable_field_string(lua_State* l, char* k, char* v) +{ + // [table] + if (!lua_getmetatable(l, -1)) { + lua_newtable(l); // [table, mt] + lua_pushvalue(l, -1); // [table, mt, mt] + lua_setmetatable(l, -3); // [table, mt] + } + // [table, mt] + lua_pushstring(l, v); // [table, mt, v] + lua_setfield(l, -2, k); // [table, mt] + lua_pop(l, 1); // [table] } static void json_parse_object_context(lua_State *l, json_parse_t *json) @@ -1104,14 +1331,16 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json) /* 3 slots required: * .., table, key, value */ - json_decode_checkstack(l, json, 3); + json_decode_descend(l, json, 3); lua_newtable(l); + json_set_metatable_field_string(l, "__jsontype", "object"); json_next_token(json, &token); /* Handle empty objects */ if (token.type == T_OBJ_END) { + json_decode_ascend(json); return; } @@ -1135,8 +1364,10 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json) json_next_token(json, &token); - if (token.type == T_OBJ_END) + if (token.type == T_OBJ_END) { + json_decode_ascend(json); return; + } if (token.type != T_COMMA) json_throw_parse_error(l, json, "comma or object end", &token); @@ -1153,15 +1384,18 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json) /* 2 slots required: * .., table, value */ - json_decode_checkstack(l, json, 2); + json_decode_descend(l, json, 2); lua_newtable(l); + json_set_metatable_field_string(l, "__jsontype", "array"); json_next_token(json, &token); /* Handle empty arrays */ - if (token.type == T_ARR_END) + if (token.type == T_ARR_END) { + json_decode_ascend(json); return; + } for (i = 1; ; i++) { json_process_value(l, json, &token); @@ -1169,8 +1403,10 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json) json_next_token(json, &token); - if (token.type == T_ARR_END) + if (token.type == T_ARR_END) { + json_decode_ascend(json); return; + } if (token.type != T_COMMA) json_throw_parse_error(l, json, "comma or array end", &token); @@ -1190,6 +1426,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;; @@ -1209,20 +1448,32 @@ static void json_process_value(lua_State *l, json_parse_t *json, } } -/* json_text must be null terminated string */ -static void lua_json_decode(lua_State *l, const char *json_text, int json_len) +static int json_decode(lua_State *l) { json_parse_t json; json_token_t token; + size_t json_len; + + luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); json.cfg = json_fetch_config(l); - json.data = json_text; - json.index = 0; + json.data = luaL_checklstring(l, 1, &json_len); + json.current_depth = 0; + json.ptr = json.data; + + /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) + * + * CJSON can support any simple data type, hence only the first + * character is guaranteed to be ASCII (at worst: '"'). This is + * still enough to detect whether the wrong encoding is in use. */ + if (json_len >= 2 && (!json.data[0] || !json.data[1])) + luaL_error(l, "JSON parser does not support UTF-16 or UTF-32"); /* Ensure the temporary buffer can hold the entire string. * This means we no longer need to do length checks since the decoded * string must be smaller than the entire json string */ - json.tmp = strbuf_new(json_len); + if (json_len > INT_MAX) json_len = INT_MAX; + json.tmp = strbuf_new((int)json_len); json_next_token(&json, &token); json_process_value(l, &json, &token); @@ -1234,64 +1485,154 @@ static void lua_json_decode(lua_State *l, const char *json_text, int json_len) json_throw_parse_error(l, &json, "the end", &token); strbuf_free(json.tmp); + + return 1; } -static int json_decode(lua_State *l) +/* ===== INITIALISATION ===== */ + +#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 +/* Compatibility for Lua 5.1 and older LuaJIT. + * + * compat_luaL_setfuncs() is used to create a module table where the functions + * have json_config_t as their first upvalue. Code borrowed from Lua 5.2 + * source's luaL_setfuncs(). + */ +static void compat_luaL_setfuncs(lua_State *l, const luaL_Reg *reg, int nup) { - const char *json; - size_t len; + int i; - json_verify_arg_count(l, 1); + luaL_checkstack(l, nup, "too many upvalues"); + for (; reg->name != NULL; reg++) { /* fill the table with given functions */ + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(l, -nup); + lua_pushcclosure(l, reg->func, nup); /* closure with those upvalues */ + lua_setfield(l, -(nup + 2), reg->name); + } + lua_pop(l, nup); /* remove upvalues */ +} +#else +#define compat_luaL_setfuncs(L, reg, nup) luaL_setfuncs(L, reg, nup) +#endif - json = luaL_checklstring(l, 1, &len); +/* Call target function in protected mode with all supplied args. + * Assumes target function only returns a single non-nil value. + * Convert and return thrown errors as: nil, "error message" */ +static int json_protect_conversion(lua_State *l) +{ + int err; - /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) - * - * CJSON can support any simple data type, hence only the first - * character is guaranteed to be ASCII (at worst: '"'). This is - * still enough to detect whether the wrong encoding is in use. */ - if (len >= 2 && (!json[0] || !json[1])) - luaL_error(l, "JSON parser does not support UTF-16 or UTF-32"); + /* Deliberately throw an error for invalid arguments */ + luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); - lua_json_decode(l, json, len); + /* pcall() the function stored as upvalue(1) */ + lua_pushvalue(l, lua_upvalueindex(1)); + lua_insert(l, 1); + err = lua_pcall(l, 1, 1, 0); + if (!err) + return 1; - return 1; -} + if (err == LUA_ERRRUN) { + lua_pushnil(l); + lua_insert(l, -2); + return 2; + } -/* ===== INITIALISATION ===== */ + /* Since we are not using a custom error handler, the only remaining + * errors are memory related */ + return luaL_error(l, "Memory allocation error in CJSON protected call"); +} -int luaopen_cjson(lua_State *l) +/* Return cjson module table */ +static int lua_cjson_new(lua_State *l) { luaL_Reg reg[] = { { "encode", json_encode }, { "decode", json_decode }, + { "encode_empty_table_as_object", json_cfg_encode_empty_table_as_object }, { "encode_sparse_array", json_cfg_encode_sparse_array }, { "encode_max_depth", json_cfg_encode_max_depth }, + { "decode_max_depth", json_cfg_decode_max_depth }, { "encode_number_precision", json_cfg_encode_number_precision }, { "encode_keep_buffer", json_cfg_encode_keep_buffer }, - { "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers }, + { "encode_invalid_numbers", json_cfg_encode_invalid_numbers }, + { "decode_invalid_numbers", json_cfg_decode_invalid_numbers }, + { "encode_escape_forward_slash", json_cfg_encode_escape_forward_slash }, + { "encode_escape_utf8", json_cfg_encode_escape_utf8 }, + { "set_invalid_type_encoder", json_cfg_set_invalid_type_encoder }, + { "encode_numbers_as_base64", json_cfg_encode_numbers_as_base64 }, + { "decode_numbers_as_base64", json_cfg_decode_numbers_as_base64 }, + { "new", lua_cjson_new }, { NULL, NULL } }; - /* Use json_fetch_config as a pointer. - * It's faster than using a config string, and more unique */ - lua_pushlightuserdata(l, &json_config_key); - json_create_config(l); - lua_settable(l, LUA_REGISTRYINDEX); + /* cjson module table */ + lua_newtable(l); - luaL_register(l, "cjson", reg); + /* Register functions with config data as upvalue */ + json_create_config(l); + compat_luaL_setfuncs(l, reg, 1); /* Set cjson.null */ lua_pushlightuserdata(l, NULL); lua_setfield(l, -2, "null"); - /* Set cjson.version */ - lua_pushliteral(l, VERSION); - lua_setfield(l, -2, "version"); + /* Set module name / version fields */ + lua_pushliteral(l, CJSON_MODNAME); + lua_setfield(l, -2, "_NAME"); + lua_pushliteral(l, CJSON_VERSION); + lua_setfield(l, -2, "_VERSION"); + + /* Create our registry table */ + lua_pushlightuserdata(l, CJSON_LIGHTUSERDATA); + lua_newtable(l); + lua_rawset(l, LUA_REGISTRYINDEX); + + return 1; +} + +/* Return cjson.safe module table */ +static int lua_cjson_safe_new(lua_State *l) +{ + const char *func[] = { "decode", "encode", NULL }; + int i; + + lua_cjson_new(l); + + /* Fix new() method */ + lua_pushcfunction(l, lua_cjson_safe_new); + lua_setfield(l, -2, "new"); + + for (i = 0; func[i]; i++) { + lua_getfield(l, -1, func[i]); + lua_pushcclosure(l, json_protect_conversion, 1); + lua_setfield(l, -2, func[i]); + } + + return 1; +} + +LUALIB_API int luaopen_lua_cjson(lua_State* l) +{ + lua_cjson_new(l); + +#ifdef ENABLE_CJSON_GLOBAL + /* Register a global "cjson" table. */ + lua_pushvalue(l, -1); + lua_setglobal(l, CJSON_MODNAME); +#endif /* Return cjson table */ return 1; } +LUALIB_API int luaopen_lua_cjson_safe(lua_State* l) +{ + lua_cjson_safe_new(l); + + /* Return cjson.safe table */ + return 1; +} + /* vi:ai et sw=4 ts=4: */ diff --git a/lua_cjson.h b/lua_cjson.h new file mode 100644 index 0000000..e171c40 --- /dev/null +++ b/lua_cjson.h @@ -0,0 +1,163 @@ +#pragma once +#ifndef LUA_CJSON_H +#define LUA_CJSON_H +/* + * 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 "pch.h" +#include "strbuf.h" + +#ifndef CJSON_MODNAME +#define CJSON_MODNAME "lua_cjson" +#endif + +#ifndef CJSON_VERSION +#define CJSON_VERSION "2.1.1.QUIKSHARP" +#endif + +#ifdef _MSC_VER +#define snprintf sprintf_s + /* Microsoft C compiler lacks strncasecmp and strcasecmp. */ +#define strncasecmp _strnicmp +#define strcasecmp _stricmp + +#ifndef isnan +#include +#define isnan(x) _isnan(x) +#endif +#endif + + // convention is to use a pointer to anything in our C module for unique lightuserdata +#define CJSON_LIGHTUSERDATA ((void*)json_token_type_name) +#define CJSON_REGISTRY_INVALID_TYPE_ENCODER 1 + +/* Workaround for Solaris platforms missing isinf() */ +#if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF)) +#define isinf(x) (!isnan(x) && isnan((x) - (x))) +#endif + +#define DEFAULT_SPARSE_CONVERT 0 +#define DEFAULT_SPARSE_RATIO 2 +#define DEFAULT_SPARSE_SAFE 10 +#define DEFAULT_ENCODE_MAX_DEPTH 1000 +#define DEFAULT_DECODE_MAX_DEPTH 1000 +#define DEFAULT_ENCODE_INVALID_NUMBERS 0 +#define DEFAULT_DECODE_INVALID_NUMBERS 1 +#define DEFAULT_ENCODE_KEEP_BUFFER 1 +#define DEFAULT_ENCODE_NUMBER_PRECISION 14 +#define DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT 0 +#define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1 +#define DEFAULT_ENCODE_ESCAPE_UTF8 0 +#define DEFAULT_ENCODE_NUMBERS_AS_BASE64 0 + +#if LONG_MAX > ((1UL << 31) - 1) +#define json_lightudata_mask(ludata) \ + ((void *) ((uintptr_t) (ludata) & ((1UL << 47) - 1))) + +#else +#define json_lightudata_mask(ludata) (ludata) +#endif + +#if LUA_VERSION_NUM > 501 +#define lua_objlen(L,i) lua_rawlen(L, (i)) +#endif + +typedef enum { + T_OBJ_BEGIN, + T_OBJ_END, + T_ARR_BEGIN, + T_ARR_END, + T_STRING, + T_NUMBER, + T_INTEGER, + T_BOOLEAN, + T_NULL, + T_COLON, + T_COMMA, + T_END, + T_WHITESPACE, + T_ERROR, + T_UNKNOWN +} json_token_type_t; + +static const char* json_token_type_name[] = { + "T_OBJ_BEGIN", + "T_OBJ_END", + "T_ARR_BEGIN", + "T_ARR_END", + "T_STRING", + "T_NUMBER", + "T_INTEGER", + "T_BOOLEAN", + "T_NULL", + "T_COLON", + "T_COMMA", + "T_END", + "T_WHITESPACE", + "T_ERROR", + "T_UNKNOWN", + NULL +}; + +typedef struct { + json_token_type_t ch2token[256]; + char escape2char[256]; /* Decoding */ + + /* encode_buf is only allocated and used when + * encode_keep_buffer is set */ + strbuf_t encode_buf; + + int encode_sparse_convert; + int encode_sparse_ratio; + int encode_sparse_safe; + int encode_max_depth; + int encode_invalid_numbers; /* 2 => Encode as "null" */ + int encode_number_precision; + int encode_keep_buffer; + int encode_empty_table_as_object; + int encode_escape_forward_slash; + int encode_escape_utf8; /* 0, 1, -i -> char i */ + int decode_invalid_numbers; + int decode_max_depth; + int encode_numbers_as_base64; + int decode_numbers_as_base64; +} json_config_t; + +typedef struct { + const char* data; + const char* ptr; + strbuf_t* tmp; /* Temporary storage for strings */ + json_config_t* cfg; + int current_depth; +} json_parse_t; + +typedef struct { + json_token_type_t type; + int index; + union { + const char* string; + double number; + lua_Integer integer; + int boolean; + } value; + int string_len; +} json_token_t; + +#endif \ No newline at end of file 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 new file mode 100644 index 0000000..429d5d2 --- /dev/null +++ b/manual.txt @@ -0,0 +1,635 @@ += 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]) +escape = cjson.encode_escape_utf8([escape]) +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_escape_utf8]] +encode_escape_utf8 +~~~~~~ + +[source,lua] +------------ +escape = cjson.encode_escape_utf8([escape]) +-- escape can be true, false, or any char. Default: false. +------------ + +Encode all utf-8 char to '\uffff' style. + +escape + +.Example: Escape utf-8 char +[source,lua] +value = { "中文" } +cjson.encode_escape_utf8(true) +json_text = cjson.encode(value) +-- Returns: '["\u4e2d\u6587"]' + +[[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/pch.c b/pch.c new file mode 100644 index 0000000..9211a5e --- /dev/null +++ b/pch.c @@ -0,0 +1,5 @@ +// pch.cpp: файл исходного кода, соответствующий предварительно скомпилированному заголовочному файлу + +#include "pch.h" + +// При использовании предварительно скомпилированных заголовочных файлов необходим следующий файл исходного кода для выполнения сборки. diff --git a/pch.h b/pch.h new file mode 100644 index 0000000..b9a57cd --- /dev/null +++ b/pch.h @@ -0,0 +1,70 @@ +// pch.h: это предварительно скомпилированный заголовочный файл. +// Перечисленные ниже файлы компилируются только один раз, что ускоряет последующие сборки. +// Это также влияет на работу IntelliSense, включая многие функции просмотра и завершения кода. +// Однако изменение любого из приведенных здесь файлов между операциями сборки приведет к повторной компиляции всех(!) этих файлов. +// Не добавляйте сюда файлы, которые планируете часто изменять, так как в этом случае выигрыша в производительности не будет. + +#ifndef PCH_H +#define PCH_H +// Добавьте сюда заголовочные файлы для предварительной компиляции + +#include "framework.h" +#include + +#ifdef _WIN32 +#define inline __inline +#define snprintf_s _snprintf_s +#endif + + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +/* Microsoft C compiler lacks strncasecmp and strcasecmp. */ +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + + +#define LUA_LIB +#define LUA_BUILD_AS_DLL + +#ifdef __cplusplus +#include "lua\lua.hpp" +#else +#include "lua\lua.h" +#endif + +#pragma once +#ifdef __cplusplus +extern "C" { +#endif + +#include "lua\luaconf.h" +#include "lua\lauxlib.h" + +#ifdef __cplusplus +} +#endif + +#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 + +#endif //PCH_H 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/runtests.sh b/runtests.sh new file mode 100644 index 0000000..87db0bc --- /dev/null +++ b/runtests.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +PLATFORM="`uname -s`" +[ "$1" ] && VERSION="$1" || VERSION="2.1devel" + +set -e + +# Portable "ggrep -A" replacement. +# Work around Solaris awk record limit of 2559 bytes. +# contextgrep PATTERN POST_MATCH_LINES +contextgrep() { + cut -c -2500 | awk "/$1/ { count = ($2 + 1) } count > 0 { count--; print }" +} + +do_tests() { + echo + cd tests + lua -e 'print("Testing Lua CJSON version " .. require("cjson")._VERSION)' + ./test.lua | contextgrep 'FAIL|Summary' 3 | grep -v PASS | cut -c -150 + cd .. +} + +echo "===== Setting LuaRocks PATH =====" +eval "`luarocks path`" + +echo "===== Building UTF-8 test data =====" +( cd tests && ./genutf8.pl; ) + +echo "===== Cleaning old build data =====" +make clean +rm -f tests/cjson.so + +echo "===== Verifying cjson.so is not installed =====" + +cd tests +if lua -e 'require "cjson"' 2>/dev/null +then + cat < "$LOG" + RPM="`awk '/^Wrote: / && ! /debuginfo/ { print $2}' < "$LOG"`" + sudo -- rpm -Uvh \"$RPM\" + do_tests + sudo -- rpm -e lua-cjson + rm -f "$LOG" + else + echo "==> skipping, $TGZ not found" + fi +fi + +# vi:ai et sw=4 ts=4: diff --git a/strbuf.c b/strbuf.c index 976925a..c36cc37 100644 --- a/strbuf.c +++ b/strbuf.c @@ -1,6 +1,6 @@ -/* strbuf - string buffer routines +/* strbuf - String buffer routines * - * Copyright (c) 2010-2011 Mark Pulford + * Copyright (c) 2010-2012 Mark Pulford * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,6 +22,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include "pch.h" #include #include #include @@ -29,7 +30,7 @@ #include "strbuf.h" -void die(const char *fmt, ...) +static void die(const char *fmt, ...) { va_list arg; @@ -58,27 +59,27 @@ void strbuf_init(strbuf_t *s, int len) s->reallocs = 0; s->debug = 0; - s->buf = malloc(size); + s->buf = (char *)malloc(size); if (!s->buf) die("Out of memory"); - - strbuf_ensure_null(s); + else + strbuf_ensure_null(s); } strbuf_t *strbuf_new(int len) { strbuf_t *s; + s = (strbuf_t*)malloc(sizeof(strbuf_t)); + if ( s ) + { + strbuf_init(s, len); + /* Dynamic strbuf allocation / deallocation */ + s->dynamic = 1; + return s; + } - s = malloc(sizeof(strbuf_t)); - if (!s) - die("Out of memory"); - - strbuf_init(s, len); - - /* Dynamic strbuf allocation / deallocation */ - s->dynamic = 1; - - return s; + die("Out of memory"); + return NULL; } void strbuf_set_increment(strbuf_t *s, int increment) @@ -94,8 +95,8 @@ void strbuf_set_increment(strbuf_t *s, int increment) static inline void debug_stats(strbuf_t *s) { if (s->debug) { - fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n", - (long)s, s->reallocs, s->length, s->size); + fprintf(stderr, "strbuf(%px) reallocs: %d, length: %d, size: %d\n", + (void*)s, s->reallocs, s->length, s->size); } } @@ -168,14 +169,15 @@ void strbuf_resize(strbuf_t *s, int len) newsize = calculate_new_size(s, len); if (s->debug > 1) { - fprintf(stderr, "strbuf(%lx) resize: %d => %d\n", - (long)s, s->size, newsize); + fprintf(stderr, "strbuf(%px) resize: %d => %d\n", + (void*)s, s->size, newsize); } s->size = newsize; - s->buf = realloc(s->buf, s->size); - if (!s->buf) + void * new_ptr = realloc(s->buf, s->size); + if (!new_ptr) die("Out of memory"); + s->buf = (char*)new_ptr; s->reallocs++; } @@ -216,36 +218,5 @@ void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...) s->length += fmt_len; } -/* strbuf_append_fmt_retry() can be used when the there is no known - * upper bound for the output string. */ -void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...) -{ - va_list arg; - int fmt_len, try; - int empty_len; - - /* If the first attempt to append fails, resize the buffer appropriately - * and try again */ - for (try = 0; ; try++) { - va_start(arg, fmt); - /* Append the new formatted string */ - /* fmt_len is the length of the string required, excluding the - * trailing NULL */ - empty_len = strbuf_empty_length(s); - /* Add 1 since there is also space to store the terminating NULL. */ - fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg); - va_end(arg); - - if (fmt_len <= empty_len) - break; /* SUCCESS */ - if (try > 0) - die("BUG: length of formatted string changed"); - - strbuf_resize(s, s->length + fmt_len); - } - - s->length += fmt_len; -} - /* vi:ai et sw=4 ts=4: */ diff --git a/strbuf.h b/strbuf.h index f856543..fe68f07 100644 --- a/strbuf.h +++ b/strbuf.h @@ -1,6 +1,6 @@ /* strbuf - String buffer routines * - * Copyright (c) 2010-2011 Mark Pulford + * Copyright (c) 2010-2012 Mark Pulford * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,6 +25,10 @@ #include #include +#ifdef _WIN32 +#define inline __inline +#endif + /* Size: Total bytes allocated to *buf * Length: String length, excluding optional NULL terminator. * Increment: Allocation increments when resizing the string buffer. @@ -62,7 +66,9 @@ extern void strbuf_resize(strbuf_t *s, int len); static int strbuf_empty_length(strbuf_t *s); static int strbuf_length(strbuf_t *s); static char *strbuf_string(strbuf_t *s, int *len); -static void strbuf_ensure_empty_length(strbuf_t *s, int len); +static void strbuf_ensure_empty_length(strbuf_t *s, int len); +static char *strbuf_empty_ptr(strbuf_t *s); +static void strbuf_extend_length(strbuf_t *s, int len); /* Update */ extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); @@ -96,6 +102,16 @@ static inline void strbuf_ensure_empty_length(strbuf_t *s, int len) strbuf_resize(s, s->length + len); } +static inline char *strbuf_empty_ptr(strbuf_t *s) +{ + return s->buf + s->length; +} + +static inline void strbuf_extend_length(strbuf_t *s, int len) +{ + s->length += len; +} + static inline int strbuf_length(strbuf_t *s) { return s->length; diff --git a/tests/.vscode/launch.json b/tests/.vscode/launch.json new file mode 100644 index 0000000..a8e6a8a --- /dev/null +++ b/tests/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Используйте IntelliSense, чтобы узнать о возможных атрибутах. + // Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов. + // Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lua", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/test2.lua" + } + ] +} \ No newline at end of file diff --git a/tests/.vscode/settings.json b/tests/.vscode/settings.json new file mode 100644 index 0000000..56e834b --- /dev/null +++ b/tests/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "lua.debug.settings.luaVersion": "5.3" +} \ No newline at end of file diff --git a/tests/bench.lua b/tests/bench.lua index fdd0bb0..648020b 100755 --- a/tests/bench.lua +++ b/tests/bench.lua @@ -1,34 +1,69 @@ #!/usr/bin/env lua --- Simple JSON benchmark. +-- This benchmark script measures wall clock time and should be +-- run on an unloaded system. -- -- Your Mileage May Vary. -- -- Mark Pulford -require "common" +local json_module = os.getenv("JSON_MODULE") or "cjson" + require "socket" +local json = require(json_module) +local util = require "cjson.util" + +local function find_func(mod, funcnames) + for _, v in ipairs(funcnames) do + if mod[v] then + return mod[v] + end + end + + return nil +end -local json = require "cjson" +local json_encode = find_func(json, { "encode", "Encode", "to_string", "stringify", "json" }) +local json_decode = find_func(json, { "decode", "Decode", "to_value", "parse" }) + +local function average(t) + local total = 0 + for _, v in ipairs(t) do + total = total + v + end + return total / #t +end function benchmark(tests, seconds, rep) local function bench(func, iter) - -- collectgarbage("stop") - collectgarbage("collect") + -- Use socket.gettime() to measure microsecond resolution + -- wall clock time. local t = socket.gettime() for i = 1, iter do func(i) end t = socket.gettime() - t - -- collectgarbage("restart") + + -- Don't trust any results when the run lasted for less than a + -- millisecond - return nil. + if t < 0.001 then + return nil + end + return (iter / t) end -- Roughly calculate the number of interations required -- to obtain a particular time period. local function calc_iter(func, seconds) - local base_iter = 10 - local rate = (bench(func, base_iter) + bench(func, base_iter)) / 2 + local iter = 1 + local rate + -- Warm up the bench function first. + func() + while not rate do + rate = bench(func, iter) + iter = iter * 10 + end return math.ceil(seconds * rate) end @@ -41,43 +76,55 @@ function benchmark(tests, seconds, rep) name = func func = _G[name] end + local iter = calc_iter(func, seconds) + local result = {} for i = 1, rep do result[i] = bench(func, iter) end + + -- Remove the slowest half (round down) of the result set table.sort(result) - test_results[name] = result[rep] + for i = 1, math.floor(#result / 2) do + table.remove(result, 1) + end + + test_results[name] = average(result) end return test_results end function bench_file(filename) - local data_json = file_load(filename) - local data_obj = json.decode(data_json) + local data_json = util.file_load(filename) + local data_obj = json_decode(data_json) - local function test_encode () - json.encode(data_obj) + local function test_encode() + json_encode(data_obj) end - local function test_decode () - json.decode(data_json) + local function test_decode() + json_decode(data_json) end - local tests = { - encode = test_encode, - decode = test_decode - } + local tests = {} + if json_encode then tests.encode = test_encode end + if json_decode then tests.decode = test_decode end return benchmark(tests, 0.1, 5) end -cjson.encode_keep_buffer(true) +-- Optionally load any custom configuration required for this module +local success, data = pcall(util.file_load, ("bench-%s.lua"):format(json_module)) +if success then + util.run_script(data, _G) + configure(json) +end for i = 1, #arg do local results = bench_file(arg[i]) for k, v in pairs(results) do - print(string.format("%s: %s: %d", arg[i], k, v)) + print(("%s\t%s\t%d"):format(arg[i], k, v)) end end diff --git a/tests/genutf8.pl b/tests/genutf8.pl index bbef91f..c79f238 100755 --- a/tests/genutf8.pl +++ b/tests/genutf8.pl @@ -2,31 +2,23 @@ # Create test comparison data using a different UTF-8 implementation. -# The generation utf8.dat file must have the following MD5 sum: +# The generated utf8.dat file must have the following MD5 sum: # cff03b039d850f370a7362f3313e5268 use strict; -use warnings; -use FileHandle; +no warnings 'nonchar'; # 0xD800 - 0xDFFF are used to encode supplementary codepoints # 0x10000 - 0x10FFFF are supplementary codepoints my (@codepoints) = (0 .. 0xD7FF, 0xE000 .. 0x10FFFF); -my ($utf8); -{ - # Hide "Unicode character X is illegal" warnings. - # We want all the codes to test the UTF-8 escape decoder. - no warnings; - $utf8 = pack("U*", @codepoints); -} +my $utf8 = pack("U*", @codepoints); defined($utf8) or die "Unable create UTF-8 string\n"; -my $fh = FileHandle->new(); -$fh->open("utf8.dat", ">:utf8") +open(FH, ">:utf8", "utf8.dat") or die "Unable to open utf8.dat: $!\n"; -$fh->write($utf8) +print FH $utf8 or die "Unable to write utf8.dat\n"; -$fh->close(); +close(FH); # vi:ai et sw=4 ts=4: diff --git a/tests/lua53.dll b/tests/lua53.dll new file mode 100644 index 0000000..d9c297f Binary files /dev/null and b/tests/lua53.dll differ diff --git a/tests/lua53.exe b/tests/lua53.exe new file mode 100644 index 0000000..3be8bd3 Binary files /dev/null and b/tests/lua53.exe differ diff --git a/tests/luac53.exe b/tests/luac53.exe new file mode 100644 index 0000000..9a661ca Binary files /dev/null and b/tests/luac53.exe differ diff --git a/tests/numbers.json b/tests/numbers.json index ef11a26..4f981ff 100644 --- a/tests/numbers.json +++ b/tests/numbers.json @@ -1,7 +1,7 @@ -[ 0.110001000000000000000001, - 0.12345678910111213141516, +[ 0.110001, + 0.12345678910111, 0.412454033640, - 2.6651441426902251886502972498731, - 2.718281828459045235360287471352, - 3.141592653589793238462643383279, - 2.14069263277926 ] + 2.6651441426902, + 2.718281828459, + 3.1415926535898, + 2.1406926327793 ] diff --git a/tests/test.lua b/tests/test.lua index 7a75243..0edab32 100755 --- a/tests/test.lua +++ b/tests/test.lua @@ -1,15 +1,29 @@ #!/usr/bin/env lua --- CJSON tests +-- Lua CJSON tests -- -- Mark Pulford -- -- Note: The output of this script is easier to read with "less -S" +package.cpath = package.cpath .. ";" .. '..\\x64\\Release\\' .. '?.dll' -require "common" -local json = require "cjson" -local function gen_ascii() +local json = require "lua_cjson" +local json_safe = require "lua_cjson.safe" +local util = require "util" + +local function json_encode_output_type(value) + local text = json.encode(value) + if string.match(text, "{.*}") then + return "object" + elseif string.match(text, "%[.*%]") then + return "array" + else + return "scalar" + end +end + +local function gen_raw_octets() local chars = {} for i = 0, 255 do chars[i + 1] = string.char(i) end return table.concat(chars) @@ -22,7 +36,7 @@ local function gen_utf16_escaped() local count = 0 local function append_escape(code) - local esc = string.format('\\u%04X', code) + local esc = ('\\u%04X'):format(code) table.insert(utf16_escaped, esc) end @@ -47,190 +61,435 @@ local function gen_utf16_escaped() return table.concat(utf16_escaped) end +function load_testdata() + local data = {} + + -- Data for 8bit raw <-> escaped octets tests + data.octets_raw = gen_raw_octets() + data.octets_escaped = util.file_load("octets-escaped.dat") + + -- Data for \uXXXX -> UTF-8 test + data.utf16_escaped = gen_utf16_escaped() + + -- Load matching data for utf16_escaped + local utf8_loaded + utf8_loaded, data.utf8_raw = pcall(util.file_load, "utf8.dat") + if not utf8_loaded then + data.utf8_raw = "Failed to load utf8.dat - please run genutf8.pl" + end + + data.table_cycle = {} + data.table_cycle[1] = data.table_cycle + + local big = {} + for i = 1, 1100 do + big = { { 10, false, true, json.null }, "string", a = big } + end + data.deeply_nested_data = big + + return data +end + function test_decode_cycle(filename) - local obj1 = json.decode(file_load(filename)) + local obj1 = json.decode(util.file_load(filename)) local obj2 = json.decode(json.encode(obj1)) - return compare_values(obj1, obj2) + return util.compare_values(obj1, obj2) end +-- Set up data used in tests local Inf = math.huge; local NaN = math.huge * 0; -local octets_raw = gen_ascii() -local octets_escaped = file_load("octets-escaped.dat") -local utf8_loaded, utf8_raw = pcall(file_load, "utf8.dat") -if not utf8_loaded then - utf8_raw = "Failed to load utf8.dat" -end -local utf16_escaped = gen_utf16_escaped() -local nested5 = {{{{{ "nested" }}}}} -local table_cycle = {} -local table_cycle2 = { table_cycle } -table_cycle[1] = table_cycle2 - -local decode_simple_tests = { - { json.decode, { '"test string"' }, true, { "test string" } }, - { json.decode, { '-5e3' }, true, { -5000 } }, - { json.decode, { 'null' }, true, { json.null } }, - { json.decode, { 'true' }, true, { true } }, - { json.decode, { 'false' }, true, { false } }, - { json.decode, { '{ "1": "one", "3": "three" }' }, + +local testdata = load_testdata() + +local cjson_tests = { + -- Test API variables + { "Check module name, version", + function () return json._NAME, json._VERSION end, { }, + true, { "lua_cjson", "2.1.1.QUIKSHARP" } }, + + -- Test decoding simple types + { "Decode string", + json.decode, { '"test string"' }, true, { "test string" } }, + { "Decode numbers", + json.decode, { '[ 0.0, -5e3, -1, 0.3e-3, 1023.2, 0e10 ]' }, + true, { { 0.0, -5000, -1, 0.0003, 1023.2, 0 } } }, + { "Decode null", + json.decode, { 'null' }, true, { json.null } }, + { "Decode true", + json.decode, { 'true' }, true, { true } }, + { "Decode false", + json.decode, { 'false' }, true, { false } }, + { "Decode object with numeric keys", + json.decode, { '{ "1": "one", "3": "three" }' }, true, { { ["1"] = "one", ["3"] = "three" } } }, - { json.decode, { '[ "one", null, "three" ]' }, - true, { { "one", json.null, "three" } } } -} + { "Decode object with string keys", + json.decode, { '{ "a": "a", "b": "b" }' }, + true, { { a = "a", b = "b" } } }, + { "Decode array", + json.decode, { '[ "one", null, "three" ]' }, + true, { { "one", json.null, "three" } } }, -local encode_simple_tests = { - { json.encode, { json.null }, true, { 'null' } }, - { json.encode, { true }, true, { 'true' } }, - { json.encode, { false }, true, { 'false' } }, - { json.encode, { { } }, true, { '{}' } }, - { json.encode, { 10 }, true, { '10' } }, - { json.encode, { NaN }, - false, { "Cannot serialise number: must not be NaN or Inf" } }, - { json.encode, { Inf }, - false, { "Cannot serialise number: must not be NaN or Inf" } }, - { json.encode, { "hello" }, true, { '"hello"' } }, -} + -- Test decoding errors + { "Decode UTF-16BE [throw error]", + json.decode, { '\0"\0"' }, + false, { "JSON parser does not support UTF-16 or UTF-32" } }, + { "Decode UTF-16LE [throw error]", + json.decode, { '"\0"\0' }, + false, { "JSON parser does not support UTF-16 or UTF-32" } }, + { "Decode UTF-32BE [throw error]", + json.decode, { '\0\0\0"' }, + false, { "JSON parser does not support UTF-16 or UTF-32" } }, + { "Decode UTF-32LE [throw error]", + json.decode, { '"\0\0\0' }, + false, { "JSON parser does not support UTF-16 or UTF-32" } }, + { "Decode partial JSON [throw error]", + json.decode, { '{ "unexpected eof": ' }, + false, { "Expected value but found T_END at character 21" } }, + { "Decode with extra comma [throw error]", + json.decode, { '{ "extra data": true }, false' }, + false, { "Expected the end but found T_COMMA at character 23" } }, + { "Decode invalid escape code [throw error]", + json.decode, { [[ { "bad escape \q code" } ]] }, + false, { "Expected object key string but found invalid escape code at character 16" } }, + { "Decode invalid unicode escape [throw error]", + json.decode, { [[ { "bad unicode \u0f6 escape" } ]] }, + false, { "Expected object key string but found invalid unicode escape code at character 17" } }, + { "Decode invalid keyword [throw error]", + json.decode, { ' [ "bad barewood", test ] ' }, + false, { "Expected value but found invalid token at character 20" } }, + { "Decode invalid number #1 [throw error]", + json.decode, { '[ -+12 ]' }, + false, { "Expected value but found invalid token at character 3" } }, + { "Decode invalid number #2 [throw error]", + json.decode, { '-v' }, + false, { "Expected value but found invalid token at character 1" } }, + { "Decode invalid number exponent [throw error]", + json.decode, { '[ 0.4eg10 ]' }, -- json.decode, { '[ 0.4eg10 ]' }, + false, { "Expected comma or array end but found invalid token at character 7" } }, + + -- Test decoding nested arrays / objects + { "Set decode_max_depth(5)", + json.decode_max_depth, { 5 }, true, { 5 } }, + { "Decode array at nested limit", + json.decode, { '[[[[[ "nested" ]]]]]' }, + true, { {{{{{ "nested" }}}}} } }, + { "Decode array over nested limit [throw error]", + json.decode, { '[[[[[[ "nested" ]]]]]]' }, + false, { "Found too many nested data structures (6) at character 6" } }, + { "Decode object at nested limit", + json.decode, { '{"a":{"b":{"c":{"d":{"e":"nested"}}}}}' }, + true, { {a={b={c={d={e="nested"}}}}} } }, + { "Decode object over nested limit [throw error]", + json.decode, { '{"a":{"b":{"c":{"d":{"e":{"f":"nested"}}}}}}' }, + false, { "Found too many nested data structures (6) at character 26" } }, + { "Set decode_max_depth(1000)", + json.decode_max_depth, { 1000 }, true, { 1000 } }, + { "Decode deeply nested array [throw error]", + json.decode, { string.rep("[", 1100) .. '1100' .. string.rep("]", 1100)}, + false, { "Found too many nested data structures (1001) at character 1001" } }, + + -- Test encoding nested tables + { "Set encode_max_depth(5)", + json.encode_max_depth, { 5 }, true, { 5 } }, + { "Encode nested table as array at nested limit", + json.encode, { {{{{{"nested"}}}}} }, true, { '[[[[["nested"]]]]]' } }, + { "Encode nested table as array after nested limit [throw error]", + json.encode, { { {{{{{"nested"}}}}} } }, + false, { "Cannot serialise, excessive nesting (6)" } }, + { "Encode nested table as object at nested limit", + json.encode, { {a={b={c={d={e="nested"}}}}} }, + true, { '{"a":{"b":{"c":{"d":{"e":"nested"}}}}}' } }, + { "Encode nested table as object over nested limit [throw error]", + json.encode, { {a={b={c={d={e={f="nested"}}}}}} }, + false, { "Cannot serialise, excessive nesting (6)" } }, + { "Encode table with cycle [throw error]", + json.encode, { testdata.table_cycle }, + false, { "Cannot serialise, excessive nesting (6)" } }, + { "Set encode_max_depth(1000)", + json.encode_max_depth, { 1000 }, true, { 1000 } }, + { "Encode deeply nested data [throw error]", + json.encode, { testdata.deeply_nested_data }, + false, { "Cannot serialise, excessive nesting (1001)" } }, + + -- Test encoding with metatable.__tojson + { "Encode a table that has a __tojson", + function() + local mt = {__tojson=function() return "asdf" end} + local t = {} + setmetatable(t, mt) + return json.encode(t) + end, {}, true, { "asdf" } }, + { "Encode multiple tables that have __tojson", + function() + local mt = {__tojson=function() return "asdf" end} + local t = {} + setmetatable(t, mt) + return json.encode({c={t,t,t}}) + end, {}, true, { '{"c":[asdf,asdf,asdf]}' } }, + { "Encode a table that has a metatable without a __tojson", + function() + local mt = {a=1} + local t = {b=1} + setmetatable(t, mt) + return json.encode(t) + end, {}, true, { '{"b":1}' } }, + -- Test encoding simple types + { "Encode null", + json.encode, { json.null }, true, { 'null' } }, + { "Encode true", + json.encode, { true }, true, { 'true' } }, + { "Encode false", + json.encode, { false }, true, { 'false' } }, + { "Encode empty array", + json.encode, { { } }, true, { '[]' } }, + { "Encode integers", + json.encode, { { 3, 10, 199, 2333, 35555, 46666, 567890, -12, -554474455} }, true, + { "[3,10,199,2333,35555,46666,567890,-12,-554474455]" } }, + { "Encode number", + json.encode, { 10.0 }, true, { '10.' } }, + { "Encode string", + json.encode, { "hello" }, true, { '"hello"' } }, + { "Encode Lua function [throw error]", + json.encode, { function () end }, + false, { "Cannot serialise function: type not supported" } }, -local decode_numeric_tests = { - { json.decode, { '[ 0.0, -1, 0.3e-3, 1023.2 ]' }, - true, { { 0.0, -1, 0.0003, 1023.2 } } }, - { json.decode, { '00123' }, true, { 123 } }, - { json.decode, { '05.2' }, true, { 5.2 } }, - { json.decode, { '0e10' }, true, { 0 } }, - { json.decode, { '0x6' }, true, { 6 } }, - { json.decode, { '[ +Inf, Inf, -Inf ]' }, true, { { Inf, Inf, -Inf } } }, - { json.decode, { '[ +Infinity, Infinity, -Infinity ]' }, + -- Test decoding invalid numbers + { "Set decode_invalid_numbers(true)", + json.decode_invalid_numbers, { true }, true, { true } }, + { "Decode invalid hexadecimal", + json.decode, { '0x6.ffp1' }, false, { "Expected the end but found invalid token at character 4" } }, + { "Decode numbers with leading zero", + json.decode, { '[ 0000123, 0000.32 ]' }, true, { { 123, 0.32 } } }, + { "Decode +-Inf", + json.decode, { '[ +Inf, Inf, -Inf ]' }, true, { { Inf, Inf, -Inf } } }, + { "Decode +-Infinity", + json.decode, { '[ +Infinity, Infinity, -Infinity ]' }, true, { { Inf, Inf, -Inf } } }, - { json.decode, { '[ +NaN, NaN, -NaN ]' }, true, { { NaN, NaN, NaN } } }, - { json.decode, { 'Infrared' }, + { "Decode +-NaN", + json.decode, { '[ +NaN, NaN, -NaN ]' }, true, { { NaN, NaN, NaN } } }, + { "Decode Infrared (not infinity) [throw error]", + json.decode, { 'Infrared' }, false, { "Expected the end but found invalid token at character 4" } }, - { json.decode, { 'Noodle' }, + { "Decode Noodle (not NaN) [throw error]", + json.decode, { 'Noodle' }, false, { "Expected value but found invalid token at character 1" } }, -} + { "Set decode_invalid_numbers(false)", + json.decode_invalid_numbers, { false }, true, { false } }, + { "Decode hexadecimal [throw error]", + json.decode, { '0x6' }, + false, { "Expected value but found invalid token at character 1" } }, + { "Decode numbers with leading zero [throw error]", + json.decode, { '[ 0123, 00.33 ]' }, + false, { "Expected value but found invalid token at character 9" } }, + { "Decode +-Inf [throw error]", + json.decode, { '[ +Inf, Inf, -Inf ]' }, + false, { "Expected value but found invalid token at character 3" } }, + { "Decode +-Infinity [throw error]", + json.decode, { '[ +Infinity, Infinity, -Infinity ]' }, + false, { "Expected value but found invalid token at character 3" } }, + { "Decode +-NaN [throw error]", + json.decode, { '[ +NaN, NaN, -NaN ]' }, + false, { "Expected value but found invalid token at character 3" } }, + { 'Set decode_invalid_numbers("on")', + json.decode_invalid_numbers, { "on" }, true, { true } }, + + -- Test encoding invalid numbers + { "Set encode_invalid_numbers(false)", + json.encode_invalid_numbers, { false }, true, { false } }, + { "Encode NaN [throw error]", + json.encode, { NaN }, + false, { "Cannot serialise number: must not be NaN or Infinity" } }, + { "Encode Infinity [throw error]", + json.encode, { Inf }, + false, { "Cannot serialise number: must not be NaN or Infinity" } }, + { "Set encode_invalid_numbers(\"null\")", + json.encode_invalid_numbers, { "null" }, true, { "null" } }, + { "Encode NaN as null", + json.encode, { NaN }, true, { "null" } }, + { "Encode Infinity as null", + json.encode, { Inf }, true, { "null" } }, + { "Set encode_invalid_numbers(true)", + json.encode_invalid_numbers, { true }, true, { true } }, + { "Encode NaN", + json.encode, { NaN }, true, { "NaN" } }, + { "Encode +Infinity", + json.encode, { Inf }, true, { "Infinity" } }, + { "Encode -Infinity", + json.encode, { -Inf }, true, { "-Infinity" } }, + { 'Set encode_invalid_numbers("off")', + json.encode_invalid_numbers, { "off" }, true, { false } }, -local encode_table_tests = { - function() - cjson.encode_sparse_array(true, 2, 3) - cjson.encode_max_depth(5) - return "Setting sparse array (true, 2, 3) / max depth (5)" - end, - { json.encode, { { [3] = "sparse test" } }, + -- Test encoding tables + { "Set encode_sparse_array(true, 2, 3)", + json.encode_sparse_array, { true, 2, 3 }, true, { true, 2, 3 } }, + { "Encode sparse table as array #1", + json.encode, { { [3] = "sparse test" } }, true, { '[null,null,"sparse test"]' } }, - { json.encode, { { [1] = "one", [4] = "sparse test" } }, + { "Encode sparse table as array #2", + json.encode, { { [1] = "one", [4] = "sparse test" } }, true, { '["one",null,null,"sparse test"]' } }, - { json.encode, { { [1] = "one", [5] = "sparse test" } }, - true, { '{"1":"one","5":"sparse test"}' } }, - - { json.encode, { { ["2"] = "numeric string key test" } }, + { "Encode sparse array as object", + json_encode_output_type, { { [1] = "one", [5] = "sparse test" } }, + true, { 'object' } }, + { "Encode table with numeric string key as object", + json.encode, { { ["2"] = "numeric string key test" } }, true, { '{"2":"numeric string key test"}' } }, - - { json.encode, { nested5 }, true, { '[[[[["nested"]]]]]' } }, - { json.encode, { { nested5 } }, - false, { "Cannot serialise, excessive nesting (6)" } }, - { json.encode, { table_cycle }, - false, { "Cannot serialise, excessive nesting (6)" } } -} - -local encode_error_tests = { - { json.encode, { { [false] = "wrong" } }, + { "Set encode_sparse_array(false)", + json.encode_sparse_array, { false }, true, { false, 2, 3 } }, + { "Encode table with incompatible key [throw error]", + json.encode, { { [false] = "wrong" } }, false, { "Cannot serialise boolean: table key must be a number or string" } }, - { json.encode, { function () end }, - false, { "Cannot serialise function: type not supported" } }, - function () - json.refuse_invalid_numbers("encode") - return 'Setting refuse_invalid_numbers("encode")' - end, - { json.encode, { NaN }, - false, { "Cannot serialise number: must not be NaN or Inf" } }, - { json.encode, { Inf }, - false, { "Cannot serialise number: must not be NaN or Inf" } }, - function () - json.refuse_invalid_numbers(false) - return 'Setting refuse_invalid_numbers(false).' - end, - function () - print('NOTE: receiving "-nan" in the following test is ok..') - return - end, - { json.encode, { NaN }, true, { "nan" } }, - { json.encode, { Inf }, true, { "inf" } }, - function () - json.refuse_invalid_numbers("encode") - return 'Setting refuse_invalid_numbers("encode")' - end, -} - -local json_nested = string.rep("[", 100000) .. string.rep("]", 100000) -local decode_error_tests = { - { json.decode, { '\0"\0"' }, - false, { "JSON parser does not support UTF-16 or UTF-32" } }, - { json.decode, { '"\0"\0' }, - false, { "JSON parser does not support UTF-16 or UTF-32" } }, - { json.decode, { '{ "unexpected eof": ' }, - false, { "Expected value but found T_END at character 21" } }, - { json.decode, { '{ "extra data": true }, false' }, - false, { "Expected the end but found T_COMMA at character 23" } }, - { json.decode, { ' { "bad escape \\q code" } ' }, - false, { "Expected object key string but found invalid escape code at character 16" } }, - { json.decode, { ' { "bad unicode \\u0f6 escape" } ' }, - false, { "Expected object key string but found invalid unicode escape code at character 17" } }, - { json.decode, { ' [ "bad barewood", test ] ' }, - false, { "Expected value but found invalid token at character 20" } }, - { json.decode, { '[ -+12 ]' }, - false, { "Expected value but found invalid number at character 3" } }, - { json.decode, { '-v' }, - false, { "Expected value but found invalid number at character 1" } }, - { json.decode, { '[ 0.4eg10 ]' }, - false, { "Expected comma or array end but found invalid token at character 6" } }, - { json.decode, { json_nested }, - false, { "Too many nested data structures" } } -} - -local escape_tests = { - -- Test 8bit clean - { json.encode, { octets_raw }, true, { octets_escaped } }, - { json.decode, { octets_escaped }, true, { octets_raw } }, - -- Ensure high bits are removed from surrogate codes - { json.decode, { '"\\uF800"' }, true, { "\239\160\128" } }, - -- Test inverted surrogate pairs - { json.decode, { '"\\uDB00\\uD800"' }, + -- Test escaping + -- { "Encode all octets (8-bit clean)", + -- json.encode, { testdata.octets_raw }, true, { testdata.octets_escaped } }, + --{ "Decode all escaped octets", + -- json.decode, { testdata.octets_escaped }, true, { testdata.octets_raw } }, + -- { "Decode single UTF-16 escape", + -- json.decode, { [["\uF800"]] }, true, { "\239\160\128" } }, + -- { "Decode all UTF-16 escapes (including surrogate combinations)", + -- json.decode, { testdata.utf16_escaped }, true, { testdata.utf8_raw } }, + { "Decode swapped surrogate pair [throw error]", + json.decode, { [["\uDC00\uD800"]] }, + false, { "Expected value but found invalid unicode escape code at character 2" } }, + { "Decode duplicate high surrogate [throw error]", + json.decode, { [["\uDB00\uDB00"]] }, false, { "Expected value but found invalid unicode escape code at character 2" } }, - -- Test 2x high surrogate code units - { json.decode, { '"\\uDB00\\uDB00"' }, + { "Decode duplicate low surrogate [throw error]", + json.decode, { [["\uDB00\uDB00"]] }, false, { "Expected value but found invalid unicode escape code at character 2" } }, - -- Test invalid 2nd escape - { json.decode, { '"\\uDB00\\"' }, + { "Decode missing low surrogate [throw error]", + json.decode, { [["\uDB00"]] }, false, { "Expected value but found invalid unicode escape code at character 2" } }, - { json.decode, { '"\\uDB00\\uD"' }, + { "Decode invalid low surrogate [throw error]", + json.decode, { [["\uDB00\uD"]] }, false, { "Expected value but found invalid unicode escape code at character 2" } }, - -- Test decoding of all UTF-16 escapes - { json.decode, { utf16_escaped }, true, { utf8_raw } } + + -- Test locale support + -- + -- The standard Lua interpreter is ANSI C online doesn't support locales + -- by default. Force a known problematic locale to test strtod()/sprintf(). + { "Set locale to cs_CZ (comma separator)", function () + os.setlocale("cs_CZ") + json.new() + end }, + { "Encode number under comma locale", + json.encode, { 1.5 }, true, { '1.5' } }, + { "Decode number in array under comma locale", + json.decode, { '[ 10, "test" ]' }, true, { { 10, "test" } } }, + { "Revert locale to POSIX", function () + os.setlocale("C") + json.new() + end }, + + -- Test encode_keep_buffer() and enable_number_precision() + { "Set encode_keep_buffer(false)", + json.encode_keep_buffer, { false }, true, { false } }, + { "Set encode_number_precision(3)", + json.encode_number_precision, { 3 }, true, { 3 } }, + { "Encode number with precision 3", + json.encode, { 1/3 }, true, { "0.333" } }, + { "Set encode_number_precision(14)", + json.encode_number_precision, { 14 }, true, { 14 } }, + { "Set encode_keep_buffer(true)", + json.encode_keep_buffer, { true }, true, { true } }, + + -- Test config API errors + -- Function is listed as '?' due to pcall + { "Set encode_number_precision(0) [throw error]", + json.encode_number_precision, { 0 }, + false, { "bad argument #1 to 'lua_cjson.encode_number_precision' (expected integer between 1 and 16)" } }, + { "Set encode_number_precision(\"five\") [throw error]", + json.encode_number_precision, { "five" }, + false, { "bad argument #1 to 'lua_cjson.encode_number_precision' (number expected, got string)" } }, + { "Set encode_keep_buffer(nil, true) [throw error]", + json.encode_keep_buffer, { nil, true }, + false, { "bad argument #2 to 'lua_cjson.encode_keep_buffer' (found too many arguments)" } }, + { "Set encode_max_depth(\"wrong\") [throw error]", + json.encode_max_depth, { "wrong" }, + false, { "bad argument #1 to 'lua_cjson.encode_max_depth' (number expected, got string)" } }, + { "Set decode_max_depth(0) [throw error]", + json.decode_max_depth, { "0" }, + false, { "bad argument #1 to 'lua_cjson.decode_max_depth' (expected integer between 1 and 2147483647)" } }, + { "Set encode_invalid_numbers(-2) [throw error]", + json.encode_invalid_numbers, { -2 }, + false, { "bad argument #1 to 'lua_cjson.encode_invalid_numbers' (invalid option '-2')" } }, + { "Set decode_invalid_numbers(true, false) [throw error]", + json.decode_invalid_numbers, { true, false }, + false, { "bad argument #2 to 'lua_cjson.decode_invalid_numbers' (found too many arguments)" } }, + { "Set encode_sparse_array(\"not quite on\") [throw error]", + json.encode_sparse_array, { "not quite on" }, + false, { "bad argument #1 to 'lua_cjson.encode_sparse_array' (invalid option 'not quite on')" } }, + + { "Reset Lua CJSON configuration", function () json = json.new() end }, + -- Wrap in a function to ensure the table returned by json.new() is used + { "Check encode_sparse_array()", + function (...) return json.encode_sparse_array(...) end, { }, + true, { false, 2, 10 } }, + + { "Encode (safe) simple value", + json_safe.encode, { true }, + true, { "true" } }, + { "Encode (safe) argument validation [throw error]", + json_safe.encode, { "arg1", "arg2" }, + false, { "bad argument #1 to 'lua_cjson.safe.encode' (expected 1 argument)" } }, + { "Decode (safe) error generation", + json_safe.decode, { "Oops" }, + true, { nil, "Expected value but found invalid token at character 1" } }, + { "Decode (safe) error generation after new()", + function(...) return json_safe.new().decode(...) end, { "Oops" }, + true, { nil, "Expected value but found invalid token at character 1" } }, + + -- Test encoding invalid type callback + { "set_invalid_type_encoder(...)", + json.set_invalid_type_encoder, { function() return "FOO" end }, true, { } }, + { "Encode Lua function with invalid_type_encoder", + json.encode, { function () end }, + true, { "FOO" } }, + + + -- Test encoding/decoding base64 to Long/Double + { "encode_numbers_as_base64(...)", + json.encode_numbers_as_base64, { true }, true, { true } }, + { "decode_numbers_as_base64(...)", + json.decode_numbers_as_base64, { true }, true, { true } }, + { "Encode number", + json.encode, { 10.0 }, true, { "\"D=AAAAAAAAJEA=\"" } }, + { "Encode number", + json.encode, { -123.123 }, true, { "\"D=HVpkO9/HXsA=\"" } }, + { "Encode number", + json.encode, { 4621819117588971520.0 }, true, { "\"D=AAAAAAAJ0EM=\"" } }, + { "Decode number", + json.decode, { "\"D=AAAAAAAAJEA=\"" }, true, { 10.0 } }, + { "Decode number", + json.decode, { "\"D=HVpkO9/HXsA=\"" }, true, { -123.123 } }, + { "Decode number", + json.decode, { "\"D=AAAAAAAJ0EM=\"" }, true, { 4621819117588971520.0 } }, + { "Ignore invalid", + json.decode, { "\"D=AAA=\"" }, true, { "D=AAA=" } }, + { "Ignore invalid", + json.decode, { "\"D=va;dfjvnladkjv\"" }, true, { "D=va;dfjvnladkjv" } }, + } -print(string.format("Testing CJSON v%s\n", cjson.version)) +print(("==> Testing Lua CJSON version %s\n"):format(json._VERSION)) -run_test_group("decode simple value", decode_simple_tests) -run_test_group("encode simple value", encode_simple_tests) -run_test_group("decode numeric", decode_numeric_tests) +util.run_test_group(cjson_tests) --- INCLUDE: --- - Sparse array exception.. --- - .. --- cjson.encode_sparse_array(true, 2, 3) +for _, filename in ipairs(arg) do + util.run_test("Decode cycle " .. filename, test_decode_cycle, { filename }, + true, { true }) +end -run_test_group("encode table", encode_table_tests) -run_test_group("decode error", decode_error_tests) -run_test_group("encode error", encode_error_tests) -run_test_group("escape", escape_tests) +local pass, total = util.run_test_summary() -cjson.refuse_invalid_numbers(false) -cjson.encode_max_depth(20) -for i = 1, #arg do - run_test("decode cycle " .. arg[i], test_decode_cycle, { arg[i] }, - true, { true }) +if pass == total then + print("==> Summary: all tests succeeded") +else + print(("==> Summary: %d/%d tests failed"):format(total - pass, total)) + os.exit(1) end -- vi:ai et sw=4 ts=4: diff --git a/tests/test2.lua b/tests/test2.lua new file mode 100644 index 0000000..c03abeb --- /dev/null +++ b/tests/test2.lua @@ -0,0 +1,151 @@ +#!/usr/bin/env lua + +-- Lua CJSON tests +-- +-- Mark Pulford +-- +-- Note: The output of this script is easier to read with "less -S" +package.cpath = package.cpath .. ";" .. '..\\x64\\Release\\' .. '?.dll' + + +local json = require "lua_cjson" +local json_safe = require "lua_cjson.safe" +local util = require "util" + +local function json_encode_output_type(value) + local text = json.encode(value) + if string.match(text, "{.*}") then + return "object" + elseif string.match(text, "%[.*%]") then + return "array" + else + return "scalar" + end +end + +local function gen_raw_octets() + local chars = {} + for i = 0, 255 do chars[i + 1] = string.char(i) end + return table.concat(chars) +end + +-- Generate every UTF-16 codepoint, including supplementary codes +local function gen_utf16_escaped() + -- Create raw table escapes + local utf16_escaped = {} + local count = 0 + + local function append_escape(code) + local esc = ('\\u%04X'):format(code) + table.insert(utf16_escaped, esc) + end + + table.insert(utf16_escaped, '"') + for i = 0, 0xD7FF do + append_escape(i) + end + -- Skip 0xD800 - 0xDFFF since they are used to encode supplementary + -- codepoints + for i = 0xE000, 0xFFFF do + append_escape(i) + end + -- Append surrogate pair for each supplementary codepoint + for high = 0xD800, 0xDBFF do + for low = 0xDC00, 0xDFFF do + append_escape(high) + append_escape(low) + end + end + table.insert(utf16_escaped, '"') + + return table.concat(utf16_escaped) +end + +function load_testdata() + local data = {} + + -- Data for 8bit raw <-> escaped octets tests + data.octets_raw = gen_raw_octets() + data.octets_escaped = util.file_load("octets-escaped.dat") + + -- Data for \uXXXX -> UTF-8 test + data.utf16_escaped = gen_utf16_escaped() + + -- Load matching data for utf16_escaped + local utf8_loaded + utf8_loaded, data.utf8_raw = pcall(util.file_load, "utf8.dat") + if not utf8_loaded then + data.utf8_raw = "Failed to load utf8.dat - please run genutf8.pl" + end + + data.table_cycle = {} + data.table_cycle[1] = data.table_cycle + + local big = {} + for i = 1, 1100 do + big = { { 10, false, true, json.null }, "string", a = big } + end + data.deeply_nested_data = big + + return data +end + +function test_decode_cycle(filename) + local obj1 = json.decode(util.file_load(filename)) + local obj2 = json.decode(json.encode(obj1)) + return util.compare_values(obj1, obj2) +end + +-- Set up data used in tests +local Inf = math.huge; +local NaN = math.huge * 0; + +local testdata = load_testdata() + +local cjson_tests = { + -- Test API variables + { "Check module name, version", + function () return json._NAME, json._VERSION end, { }, + true, { "lua_cjson", "2.1.1.QUIKSHARP" } }, + + { "Set encode_number_precision(16)", + json.encode_number_precision, { 16 }, true, { 16 } }, + + -- Test decoding simple types + { "Decode number", + json.encode, { 1/3 }, true, { "0.33333333" } }, + + { "Decode number", + json.encode, { 0.33 }, true, { "0.33" } }, + + { "Decode number", + json.encode, { 1.5 }, true, { "1.5" } }, + + { "Decode number", + json.encode, { 10.0 }, true, { "10.0" } }, + + { "Decode number", + json.encode, { 100200300.123 }, true, { "100200300.123" } }, + +} + +print(("==> Testing Lua CJSON version %s\n"):format(json._VERSION)) + +util.run_test_group(cjson_tests) + +for _, filename in ipairs(arg) do + util.run_test("Decode cycle " .. filename, test_decode_cycle, { filename }, + true, { true }) +end + +local pass, total = util.run_test_summary() + + +if pass == total then + print("==> Summary: all tests succeeded") +else + print(("==> Summary: %d/%d tests failed"):format(total - pass, total)) + os.exit(1) +end + +-- vi:ai et sw=4 ts=4: diff --git a/tests/util.lua b/tests/util.lua new file mode 100644 index 0000000..a381e69 --- /dev/null +++ b/tests/util.lua @@ -0,0 +1,276 @@ +local json = require "lua_cjson" + +-- Various common routines used by the Lua CJSON package +-- +-- Mark Pulford + +-- Determine with a Lua table can be treated as an array. +-- Explicitly returns "not an array" for very sparse arrays. +-- Returns: +-- -1 Not an array +-- 0 Empty table +-- >0 Highest index in the array + +-- Provide unpack for Lua 5.3+ built without LUA_COMPAT_UNPACK +local unpack = unpack +if table.unpack then unpack = table.unpack end + +local function is_array(table) + local max = 0 + local count = 0 + for k, v in pairs(table) do + if type(k) == "number" then + if k > max then max = k end + count = count + 1 + else + return -1 + end + end + if max > count * 2 then + return -1 + end + + return max +end + +local serialise_value + +local function serialise_table(value, indent, depth) + local spacing, spacing2, indent2 + if indent then + spacing = "\n" .. indent + spacing2 = spacing .. " " + indent2 = indent .. " " + else + spacing, spacing2, indent2 = " ", " ", false + end + depth = depth + 1 + if depth > 50 then + return "Cannot serialise any further: too many nested tables" + end + + local max = is_array(value) + + local comma = false + local fragment = { "{" .. spacing2 } + if max > 0 then + -- Serialise array + for i = 1, max do + if comma then + table.insert(fragment, "," .. spacing2) + end + table.insert(fragment, serialise_value(value[i], indent2, depth)) + comma = true + end + elseif max < 0 then + -- Serialise table + for k, v in pairs(value) do + if comma then + table.insert(fragment, "," .. spacing2) + end + table.insert(fragment, + ("[%s] = %s"):format(serialise_value(k, indent2, depth), + serialise_value(v, indent2, depth))) + comma = true + end + end + table.insert(fragment, spacing .. "}") + + return table.concat(fragment) +end + +function serialise_value(value, indent, depth) + if indent == nil then indent = "" end + if depth == nil then depth = 0 end + + if value == json.null then + return "json.null" + elseif type(value) == "string" then + return ("%q"):format(value) + elseif type(value) == "nil" or type(value) == "number" or + type(value) == "boolean" then + return tostring(value) + elseif type(value) == "table" then + return serialise_table(value, indent, depth) + else + return "\"<" .. type(value) .. ">\"" + end +end + +local function file_load(filename) + local file + if filename == nil then + file = io.stdin + else + local err + file, err = io.open(filename, "rb") + if file == nil then + error(("Unable to read '%s': %s"):format(filename, err)) + end + end + local data = file:read("*a") + + if filename ~= nil then + file:close() + end + + if data == nil then + error("Failed to read " .. filename) + end + + return data +end + +local function file_save(filename, data) + local file + if filename == nil then + file = io.stdout + else + local err + file, err = io.open(filename, "wb") + if file == nil then + error(("Unable to write '%s': %s"):format(filename, err)) + end + end + file:write(data) + if filename ~= nil then + file:close() + end +end + +local function compare_values(val1, val2) + local type1 = type(val1) + local type2 = type(val2) + if type1 ~= type2 then + return false + end + + -- Check for NaN + if type1 == "number" and val1 ~= val1 and val2 ~= val2 then + return true + end + + if type1 ~= "table" then + return val1 == val2 + end + + -- check_keys stores all the keys that must be checked in val2 + local check_keys = {} + for k, _ in pairs(val1) do + check_keys[k] = true + end + + for k, v in pairs(val2) do + if not check_keys[k] then + return false + end + + if not compare_values(val1[k], val2[k]) then + return false + end + + check_keys[k] = nil + end + for k, _ in pairs(check_keys) do + -- Not the same if any keys from val1 were not found in val2 + return false + end + return true +end + +local test_count_pass = 0 +local test_count_total = 0 + +local function run_test_summary() + return test_count_pass, test_count_total +end + +local function run_test(testname, func, input, should_work, output) + local function status_line(name, status, value) + local statusmap = { [true] = ":success", [false] = ":error" } + if status ~= nil then + name = name .. statusmap[status] + end + print(("[%s] %s"):format(name, serialise_value(value, false))) + end + + local result = { pcall(func, unpack(input)) } + local success = table.remove(result, 1) + + local correct = false + if success == should_work and compare_values(result, output) then + correct = true + test_count_pass = test_count_pass + 1 + end + test_count_total = test_count_total + 1 + + local teststatus = { [true] = "PASS", [false] = "FAIL" } + print(("==> Test [%d] %s: %s"):format(test_count_total, testname, + teststatus[correct])) + + status_line("Input", nil, input) + if not correct then + status_line("Expected", should_work, output) + end + status_line("Received", success, result) + print() + + return correct, result +end + +local function run_test_group(tests) + local function run_helper(name, func, input) + if type(name) == "string" and #name > 0 then + print("==> " .. name) + end + -- Not a protected call, these functions should never generate errors. + func(unpack(input or {})) + print() + end + + for _, v in ipairs(tests) do + -- Run the helper if "should_work" is missing + if v[4] == nil then + run_helper(unpack(v)) + else + run_test(unpack(v)) + end + end +end + +-- Run a Lua script in a separate environment +local function run_script(script, env) + local env = env or {} + local func + + -- Use setfenv() if it exists, otherwise assume Lua 5.2 load() exists + if _G.setfenv then + func = loadstring(script) + if func then + setfenv(func, env) + end + else + func = load(script, nil, nil, env) + end + + if func == nil then + error("Invalid syntax.") + end + func() + + return env +end + +-- Export functions +return { + serialise_value = serialise_value, + file_load = file_load, + file_save = file_save, + compare_values = compare_values, + run_test_summary = run_test_summary, + run_test = run_test, + run_test_group = run_test_group, + run_script = run_script +} + +-- vi:ai et sw=4 ts=4: diff --git a/tests/wlua53.exe b/tests/wlua53.exe new file mode 100644 index 0000000..d6b54fe Binary files /dev/null and b/tests/wlua53.exe differ diff --git a/utf8_decoder.h b/utf8_decoder.h new file mode 100644 index 0000000..6bf894d --- /dev/null +++ b/utf8_decoder.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2008-2010 Bjoern Hoehrmann + * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + * */ + +#include "pch.h" +#include + +#define UTF8_ACCEPT 0 +#define UTF8_REJECT 12 + +static const uint8_t utf8d[] = { + /* The first part of the table maps bytes to character classes that + * to reduce the size of the transition table and create bitmasks. + * */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + /* The second part is a transition table that maps a combination + * of a state of the automaton and a character class to a state. + * */ + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12 +}; + +uint32_t inline utf8_decode(uint32_t* state, uint32_t* codep, uint32_t byte) +{ + uint32_t type = utf8d[byte]; + *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); + *state = utf8d[256 + *state + type]; + return *state; +} + +int utf8_valide(uint8_t* s) +{ + uint32_t codepoint, state = 0; + while (*s) + utf8_decode(&state, &codepoint, *s++); + + return state == UTF8_ACCEPT; +}