diff options
Diffstat (limited to '')
37 files changed, 4403 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f349fed --- /dev/null +++ b/.gitattributes | |||
@@ -0,0 +1,5 @@ | |||
1 | .gitattributes export-ignore | ||
2 | .gitignore export-ignore | ||
3 | build-packages.sh export-ignore | ||
4 | TODO export-ignore | ||
5 | devel export-ignore | ||
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56345ea --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1,7 @@ | |||
1 | *.o | ||
2 | *.so | ||
3 | manual.html | ||
4 | notes | ||
5 | packages | ||
6 | tags | ||
7 | tests/utf8.dat | ||
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8d8a420 --- /dev/null +++ b/CMakeLists.txt | |||
@@ -0,0 +1,37 @@ | |||
1 | # If Lua is installed in a non-standard location, please set the LUA_DIR | ||
2 | # environment variable to point to prefix for the install. Eg: | ||
3 | # Unix: export LUA_DIR=/home/user/pkg | ||
4 | # Windows: set LUA_DIR=c:\lua51 | ||
5 | |||
6 | project(lua_cjson C) | ||
7 | cmake_minimum_required(VERSION 2.6) | ||
8 | |||
9 | set(CMAKE_BUILD_TYPE Release) | ||
10 | |||
11 | find_package(Lua51 REQUIRED) | ||
12 | include_directories(${LUA_INCLUDE_DIR}) | ||
13 | |||
14 | # Handle platforms missing isinf() macro (Eg, some Solaris systems). | ||
15 | include(CheckSymbolExists) | ||
16 | CHECK_SYMBOL_EXISTS(isinf math.h HAVE_ISINF) | ||
17 | if(NOT HAVE_ISINF) | ||
18 | add_definitions(-DUSE_INTERNAL_ISINF) | ||
19 | endif() | ||
20 | |||
21 | if(APPLE) | ||
22 | set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS | ||
23 | "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -undefined dynamic_lookup") | ||
24 | endif() | ||
25 | |||
26 | get_filename_component(_lua_lib_dir ${LUA_LIBRARY} PATH) | ||
27 | if(WIN32) | ||
28 | set(_lua_module_dir "${_lua_lib_dir}") | ||
29 | else() | ||
30 | set(_lua_module_dir "${_lua_lib_dir}/lua/5.1") | ||
31 | endif() | ||
32 | |||
33 | add_library(cjson MODULE lua_cjson.c strbuf.c fpconv.c) | ||
34 | set_target_properties(cjson PROPERTIES PREFIX "") | ||
35 | install(TARGETS cjson DESTINATION "${_lua_module_dir}") | ||
36 | |||
37 | # vi:ai et sw=4 ts=4: | ||
@@ -0,0 +1,20 @@ | |||
1 | Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au> | ||
2 | |||
3 | Permission is hereby granted, free of charge, to any person obtaining | ||
4 | a copy of this software and associated documentation files (the | ||
5 | "Software"), to deal in the Software without restriction, including | ||
6 | without limitation the rights to use, copy, modify, merge, publish, | ||
7 | distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | permit persons to whom the Software is furnished to do so, subject to | ||
9 | the following conditions: | ||
10 | |||
11 | The above copyright notice and this permission notice shall be | ||
12 | included in all copies or substantial portions of the Software. | ||
13 | |||
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..57f2e1b --- /dev/null +++ b/Makefile | |||
@@ -0,0 +1,70 @@ | |||
1 | ##### Available defines for CJSON_CFLAGS ##### | ||
2 | ## | ||
3 | ## USE_INTERNAL_ISINF: Workaround for Solaris platforms missing isinf(). | ||
4 | ## DISABLE_CJSON_GLOBAL: Do not store module is "cjson" global. | ||
5 | ## DISABLE_INVALID_NUMBERS: Permanently disable invalid JSON numbers: | ||
6 | ## NaN, Infinity, hex. | ||
7 | |||
8 | ##### Build defaults ##### | ||
9 | LUA_VERSION = 5.1 | ||
10 | TARGET = cjson.so | ||
11 | PREFIX = /usr/local | ||
12 | #CFLAGS = -g -Wall -pedantic -fno-inline | ||
13 | CFLAGS = -O3 -Wall -pedantic -DNDEBUG | ||
14 | CJSON_CFLAGS = -fpic | ||
15 | CJSON_LDFLAGS = -shared | ||
16 | LUA_INCLUDE_DIR = $(PREFIX)/include | ||
17 | LUA_MODULE_DIR = $(PREFIX)/lib/lua/$(LUA_VERSION) | ||
18 | INSTALL_CMD = install | ||
19 | |||
20 | ##### Platform overrides ##### | ||
21 | ## | ||
22 | ## Tweak one of the platform sections below to suit your situation. | ||
23 | ## | ||
24 | ## See http://lua-users.org/wiki/BuildingModules for further platform | ||
25 | ## specific details. | ||
26 | |||
27 | ## Linux | ||
28 | |||
29 | ## FreeBSD | ||
30 | #LUA_INCLUDE_DIR = $(PREFIX)/include/lua51 | ||
31 | |||
32 | ## MacOSX (Macports) | ||
33 | #PREFIX = /opt/local | ||
34 | #CJSON_LDFLAGS = -bundle -undefined dynamic_lookup | ||
35 | |||
36 | ## Solaris | ||
37 | #CJSON_CFLAGS = -fpic -DUSE_INTERNAL_ISINF | ||
38 | |||
39 | ## Windows (MinGW) | ||
40 | #TARGET = cjson.dll | ||
41 | #PREFIX = /home/user/opt | ||
42 | #CJSON_CFLAGS = -DDISABLE_INVALID_NUMBERS | ||
43 | #CJSON_LDFLAGS = -shared -L$(PREFIX)/lib -llua51 | ||
44 | |||
45 | ##### End customisable sections ##### | ||
46 | |||
47 | BUILD_CFLAGS = -I$(LUA_INCLUDE_DIR) $(CJSON_CFLAGS) | ||
48 | OBJS := lua_cjson.o strbuf.o fpconv.o | ||
49 | |||
50 | .PHONY: all clean install package doc | ||
51 | |||
52 | all: $(TARGET) | ||
53 | |||
54 | doc: manual.html | ||
55 | |||
56 | .c.o: | ||
57 | $(CC) -c $(CFLAGS) $(CPPFLAGS) $(BUILD_CFLAGS) -o $@ $< | ||
58 | |||
59 | $(TARGET): $(OBJS) | ||
60 | $(CC) $(LDFLAGS) $(CJSON_LDFLAGS) -o $@ $(OBJS) | ||
61 | |||
62 | install: $(TARGET) | ||
63 | mkdir -p $(DESTDIR)/$(LUA_MODULE_DIR) | ||
64 | $(INSTALL_CMD) $(TARGET) $(DESTDIR)/$(LUA_MODULE_DIR) | ||
65 | |||
66 | manual.html: manual.txt | ||
67 | asciidoc -n -a toc manual.txt | ||
68 | |||
69 | clean: | ||
70 | rm -f *.o $(TARGET) | ||
@@ -0,0 +1,31 @@ | |||
1 | Version 1.0.5 (?) | ||
2 | * Added support for Lua 5.2 | ||
3 | * Added HTML reference manual | ||
4 | * Added CMake build support | ||
5 | * Updated Makefile for compatibility with non-GNU make implementations | ||
6 | * Improved compatibility for strtod/sprintf locale workaround | ||
7 | * Encoding now supports pre-emptive threads within a single Lua state | ||
8 | |||
9 | Version 1.0.4 (Nov 30 2011) | ||
10 | * Fixed numeric conversion under locales with a comma decimal separator | ||
11 | |||
12 | Version 1.0.3 (Sep 15 2011) | ||
13 | * Fixed detection of objects with numeric string keys | ||
14 | * Provided work around for missing isinf() on Solaris | ||
15 | |||
16 | Version 1.0.2 (May 30 2011) | ||
17 | * Portability improvements for Windows | ||
18 | - No longer links with -lm | ||
19 | - Use "socket" instead of "posix" for sub-second timing | ||
20 | * Removed UTF-8 test dependency on Perl Text::Iconv | ||
21 | * Added simple CLI commands for testing Lua <-> JSON conversions | ||
22 | * Added cjson.encode_number_precision() | ||
23 | |||
24 | Version 1.0.1 (May 10 2011) | ||
25 | * Added build support for OSX | ||
26 | * Removed unnecessary whitespace from JSON output | ||
27 | * Added cjson.encode_keep_buffer() | ||
28 | * Fixed memory leak on Lua stack overflow exception | ||
29 | |||
30 | Version 1.0 (May 9 2011) | ||
31 | * Initial release | ||
@@ -0,0 +1,9 @@ | |||
1 | The following people have helped with bug reports, testing and/or | ||
2 | suggestions: | ||
3 | |||
4 | - Louis-Philippe Perron (@loopole) | ||
5 | - Ondřej Jirman | ||
6 | - Steve Donovan <steve.j.donovan@gmail.com> | ||
7 | - Zhang "agentzh" Yichun <agentzh@gmail.com> | ||
8 | |||
9 | Thanks! | ||
diff --git a/build-packages.sh b/build-packages.sh new file mode 100755 index 0000000..8d9f620 --- /dev/null +++ b/build-packages.sh | |||
@@ -0,0 +1,36 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | # build-packages.sh [ REF ] | ||
4 | |||
5 | # Build packages. Use current checked out version, or a specific tag/commit. | ||
6 | |||
7 | # Files requiring a version bump | ||
8 | VERSION_FILES="lua-cjson-1.0devel-1.rockspec lua-cjson.spec lua_cjson.c manual.txt runtests.sh" | ||
9 | |||
10 | [ "$1" ] && BRANCH="$1" || BRANCH="`git describe --match '1.[0-9]*'`" | ||
11 | VERSION="`git describe --match '1.[0-9]*' $BRANCH`" | ||
12 | VERSION="${VERSION//-/.}" | ||
13 | |||
14 | PREFIX="lua-cjson-$VERSION" | ||
15 | |||
16 | set -x | ||
17 | set -e | ||
18 | |||
19 | DESTDIR="`pwd`/packages" | ||
20 | mkdir -p "$DESTDIR" | ||
21 | BUILDROOT="`mktemp -d`" | ||
22 | trap "rm -rf '$BUILDROOT'" 0 | ||
23 | |||
24 | git archive --prefix="$PREFIX/" "$BRANCH" | tar xf - -C "$BUILDROOT" | ||
25 | cd "$BUILDROOT" | ||
26 | |||
27 | cd "$PREFIX" | ||
28 | rename 1.0devel "$VERSION" $VERSION_FILES | ||
29 | perl -pi -e "s/\\b1.0devel\\b/$VERSION/g" ${VERSION_FILES/1.0devel/$VERSION}; | ||
30 | cd .. | ||
31 | |||
32 | make -C "$PREFIX" doc | ||
33 | tar cf - "$PREFIX" | gzip -9 > "$DESTDIR/$PREFIX.tar.gz" | ||
34 | zip -9rq "$DESTDIR/$PREFIX.zip" "$PREFIX" | ||
35 | |||
36 | # vi:ai et sw=4 ts=4: | ||
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 @@ | |||
1 | parser: | ||
2 | - call parse_value | ||
3 | - next_token | ||
4 | ? <EOF> nop. | ||
5 | |||
6 | parse_value: | ||
7 | - next_token | ||
8 | ? <OBJ_BEGIN> call parse_object. | ||
9 | ? <ARR_BEGIN> call parse_array. | ||
10 | ? <STRING> push. return. | ||
11 | ? <BOOLEAN> push. return. | ||
12 | ? <NULL> push. return. | ||
13 | ? <NUMBER> push. return. | ||
14 | |||
15 | parse_object: | ||
16 | - push table | ||
17 | - next_token | ||
18 | ? <STRING> push. | ||
19 | - next_token | ||
20 | ? <COLON> nop. | ||
21 | - call parse_value | ||
22 | - set table | ||
23 | - next_token | ||
24 | ? <OBJ_END> return. | ||
25 | ? <COMMA> loop parse_object. | ||
26 | |||
27 | parse_array: | ||
28 | - push table | ||
29 | - call parse_value | ||
30 | - table append | ||
31 | - next_token | ||
32 | ? <COMMA> loop parse_array. | ||
33 | ? ] return. | ||
34 | |||
35 | next_token: | ||
36 | - check next character | ||
37 | ? { return <OBJ_BEGIN> | ||
38 | ? } return <OBJ_END> | ||
39 | ? [ return <ARR_BEGIN> | ||
40 | ? ] return <ARR_END> | ||
41 | ? , return <COMMA> | ||
42 | ? : return <COLON> | ||
43 | ? [-0-9] gobble number. return <NUMBER> | ||
44 | ? " gobble string. return <STRING> | ||
45 | ? [ \t\n] eat whitespace. | ||
46 | ? n Check "null". return <NULL> or <UNKNOWN> | ||
47 | ? t Check "true". return <BOOLEAN> or <UNKNOWN> | ||
48 | ? f Check "false". return <BOOLEAN> or <UNKNOWN> | ||
49 | ? . return <UNKNOWN> | ||
50 | ? \0 return <END> | ||
diff --git a/fpconv.c b/fpconv.c new file mode 100644 index 0000000..3bcef41 --- /dev/null +++ b/fpconv.c | |||
@@ -0,0 +1,173 @@ | |||
1 | /* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries | ||
2 | * with locale support will break when the decimal separator is a comma. | ||
3 | * | ||
4 | * fpconv_* will around these issues with a translation buffer if required. | ||
5 | */ | ||
6 | |||
7 | #include <stdio.h> | ||
8 | #include <stdlib.h> | ||
9 | #include <assert.h> | ||
10 | #include <string.h> | ||
11 | |||
12 | #include "fpconv.h" | ||
13 | |||
14 | /* Lua CJSON assumes the locale is the same for all threads within a | ||
15 | * process and doesn't change after initialisation. | ||
16 | * | ||
17 | * This avoids the need for per thread storage or expensive checks | ||
18 | * for call. */ | ||
19 | static char locale_decimal_point = '.'; | ||
20 | |||
21 | /* In theory multibyte decimal_points are possible, but | ||
22 | * Lua CJSON only supports UTF-8 and known locales only have | ||
23 | * single byte decimal points ([.,]). | ||
24 | * | ||
25 | * localconv() may not be thread safe (=>crash), and nl_langinfo() is | ||
26 | * not supported on some platforms. Use sprintf() instead - if the | ||
27 | * locale does change, at least Lua CJSON won't crash. */ | ||
28 | static void fpconv_update_locale() | ||
29 | { | ||
30 | char buf[8]; | ||
31 | |||
32 | snprintf(buf, sizeof(buf), "%g", 0.5); | ||
33 | |||
34 | /* Failing this test might imply the platform has a buggy dtoa | ||
35 | * implementation or wide characters */ | ||
36 | if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) { | ||
37 | fprintf(stderr, "Error: wide characters found or printf() bug."); | ||
38 | abort(); | ||
39 | } | ||
40 | |||
41 | locale_decimal_point = buf[1]; | ||
42 | } | ||
43 | |||
44 | /* Check for a valid number character: [-+0-9a-yA-Y.] | ||
45 | * Eg: -0.6e+5, infinity, 0xF0.F0pF0 | ||
46 | * | ||
47 | * Used to find the probable end of a number. It doesn't matter if | ||
48 | * invalid characters are counted - strtod() will find the valid | ||
49 | * number if it exists. The risk is that slightly more memory might | ||
50 | * be allocated before a parse error occurs. */ | ||
51 | static inline int valid_number_character(char ch) | ||
52 | { | ||
53 | char lower_ch; | ||
54 | |||
55 | if ('0' <= ch && ch <= '9') | ||
56 | return 1; | ||
57 | if (ch == '-' || ch == '+' || ch == '.') | ||
58 | return 1; | ||
59 | |||
60 | /* Hex digits, exponent (e), base (p), "infinity",.. */ | ||
61 | lower_ch = ch | 0x20; | ||
62 | if ('a' <= lower_ch && lower_ch <= 'y') | ||
63 | return 1; | ||
64 | |||
65 | return 0; | ||
66 | } | ||
67 | |||
68 | /* Calculate the size of the buffer required for a strtod locale | ||
69 | * conversion. */ | ||
70 | static int strtod_buffer_size(const char *s) | ||
71 | { | ||
72 | const char *p = s; | ||
73 | |||
74 | while (valid_number_character(*p)) | ||
75 | p++; | ||
76 | |||
77 | return p - s; | ||
78 | } | ||
79 | |||
80 | /* Similar to strtod(), but must be passed the current locale's decimal point | ||
81 | * character. Guaranteed to be called at the start of any valid number in a string */ | ||
82 | double fpconv_strtod(const char *nptr, char **endptr) | ||
83 | { | ||
84 | char *buf, *endbuf, *dp; | ||
85 | int buflen; | ||
86 | double value; | ||
87 | |||
88 | /* System strtod() is fine when decimal point is '.' */ | ||
89 | if (locale_decimal_point == '.') | ||
90 | return strtod(nptr, endptr); | ||
91 | |||
92 | buflen = strtod_buffer_size(nptr); | ||
93 | if (!buflen) { | ||
94 | /* No valid characters found, standard strtod() return */ | ||
95 | *endptr = (char *)nptr; | ||
96 | return 0; | ||
97 | } | ||
98 | |||
99 | /* Duplicate number into buffer */ | ||
100 | buf = malloc(buflen + 1); | ||
101 | if (!buf) { | ||
102 | fprintf(stderr, "Out of memory"); | ||
103 | abort(); | ||
104 | } | ||
105 | memcpy(buf, nptr, buflen); | ||
106 | buf[buflen] = 0; | ||
107 | |||
108 | /* Update decimal point character if found */ | ||
109 | dp = strchr(buf, '.'); | ||
110 | if (dp) | ||
111 | *dp = locale_decimal_point; | ||
112 | |||
113 | value = strtod(buf, &endbuf); | ||
114 | *endptr = (char *)&nptr[endbuf - buf]; | ||
115 | free(buf); | ||
116 | |||
117 | return value; | ||
118 | } | ||
119 | |||
120 | /* "fmt" must point to a buffer of at least 6 characters */ | ||
121 | static void set_number_format(char *fmt, int precision) | ||
122 | { | ||
123 | int d1, d2, i; | ||
124 | |||
125 | assert(1 <= precision && precision <= 14); | ||
126 | |||
127 | /* Create printf format (%.14g) from precision */ | ||
128 | d1 = precision / 10; | ||
129 | d2 = precision % 10; | ||
130 | fmt[0] = '%'; | ||
131 | fmt[1] = '.'; | ||
132 | i = 2; | ||
133 | if (d1) { | ||
134 | fmt[i++] = '0' + d1; | ||
135 | } | ||
136 | fmt[i++] = '0' + d2; | ||
137 | fmt[i++] = 'g'; | ||
138 | fmt[i] = 0; | ||
139 | } | ||
140 | |||
141 | /* Assumes there is always at least 32 characters available in the target buffer */ | ||
142 | int fpconv_g_fmt(char *str, double num, int precision) | ||
143 | { | ||
144 | char buf[FPCONV_G_FMT_BUFSIZE]; | ||
145 | char fmt[6]; | ||
146 | int len; | ||
147 | char *b; | ||
148 | |||
149 | set_number_format(fmt, precision); | ||
150 | |||
151 | /* Pass through when decimal point character is dot. */ | ||
152 | if (locale_decimal_point == '.') | ||
153 | return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num); | ||
154 | |||
155 | /* snprintf() to a buffer then translate for other decimal point characters */ | ||
156 | len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num); | ||
157 | |||
158 | /* Copy into target location. Translate decimal point if required */ | ||
159 | b = buf; | ||
160 | do { | ||
161 | *str++ = (*b == locale_decimal_point ? '.' : *b); | ||
162 | } while(*b++); | ||
163 | |||
164 | return len; | ||
165 | } | ||
166 | |||
167 | void fpconv_init() | ||
168 | { | ||
169 | fpconv_update_locale(); | ||
170 | } | ||
171 | |||
172 | /* vi:ai et sw=4 ts=4: | ||
173 | */ | ||
diff --git a/fpconv.h b/fpconv.h new file mode 100644 index 0000000..ea875c0 --- /dev/null +++ b/fpconv.h | |||
@@ -0,0 +1,14 @@ | |||
1 | /* Lua CJSON floating point conversion routines */ | ||
2 | |||
3 | /* Buffer required to store the largest string representation of a double. | ||
4 | * | ||
5 | * Longest double printed with %.14g is 21 characters long: | ||
6 | * -1.7976931348623e+308 */ | ||
7 | # define FPCONV_G_FMT_BUFSIZE 32 | ||
8 | |||
9 | extern void fpconv_init(); | ||
10 | extern int fpconv_g_fmt(char*, double, int); | ||
11 | extern double fpconv_strtod(const char*, char**); | ||
12 | |||
13 | /* vi:ai et sw=4 ts=4: | ||
14 | */ | ||
diff --git a/lua-cjson-1.0devel-1.rockspec b/lua-cjson-1.0devel-1.rockspec new file mode 100644 index 0000000..315ec2f --- /dev/null +++ b/lua-cjson-1.0devel-1.rockspec | |||
@@ -0,0 +1,47 @@ | |||
1 | package = "lua-cjson" | ||
2 | version = "1.0devel-1" | ||
3 | |||
4 | source = { | ||
5 | url = "http://www.kyne.com.au/~mark/software/lua-cjson-1.0devel.zip", | ||
6 | } | ||
7 | |||
8 | description = { | ||
9 | summary = "A fast JSON encoding/parsing library", | ||
10 | detailed = [[ | ||
11 | The Lua CJSON library provides JSON support for Lua. It features: | ||
12 | - Fast, standards compliant encoding/parsing routines | ||
13 | - Full support for JSON with UTF-8, including decoding surrogate pairs | ||
14 | - Optional run-time support for common exceptions to the JSON specification | ||
15 | (NaN, Infinity,..) | ||
16 | - No external dependencies | ||
17 | ]], | ||
18 | homepage = "http://www.kyne.com.au/~mark/software/lua-cjson.php", | ||
19 | license = "MIT" | ||
20 | } | ||
21 | |||
22 | dependencies = { | ||
23 | "lua >= 5.1" | ||
24 | } | ||
25 | |||
26 | build = { | ||
27 | type = "builtin", | ||
28 | modules = { | ||
29 | cjson = { | ||
30 | sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" }, | ||
31 | defines = { | ||
32 | -- LuaRocks does not support platform specific configuration for Solaris. | ||
33 | -- Uncomment the line below on Solaris platforms if required. | ||
34 | -- "USE_INTERNAL_ISINF" | ||
35 | } | ||
36 | } | ||
37 | }, | ||
38 | -- Override default build options (per platform) | ||
39 | platforms = { | ||
40 | win32 = { modules = { cjson = { defines = { | ||
41 | "DISABLE_INVALID_NUMBERS" | ||
42 | } } } } | ||
43 | }, | ||
44 | copy_directories = { "tests" } | ||
45 | } | ||
46 | |||
47 | -- vi:ai et sw=4 ts=4: | ||
diff --git a/lua-cjson.spec b/lua-cjson.spec new file mode 100644 index 0000000..4c949e5 --- /dev/null +++ b/lua-cjson.spec | |||
@@ -0,0 +1,64 @@ | |||
1 | %define luaver 5.1 | ||
2 | %define lualibdir %{_libdir}/lua/%{luaver} | ||
3 | |||
4 | Name: lua-cjson | ||
5 | Version: 1.0devel | ||
6 | Release: 1%{?dist} | ||
7 | Summary: A fast JSON encoding/parsing library for Lua | ||
8 | |||
9 | Group: Development/Libraries | ||
10 | License: MIT | ||
11 | URL: http://www.kyne.com.au/~mark/software/lua-cjson/ | ||
12 | Source0: http://www.kyne.com.au/~mark/software/lua-cjson/lua-cjson-%{version}.tar.gz | ||
13 | BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) | ||
14 | |||
15 | BuildRequires: lua >= %{luaver}, lua-devel >= %{luaver} | ||
16 | Requires: lua >= %{luaver} | ||
17 | |||
18 | %description | ||
19 | The Lua CJSON library provides JSON support for Lua. It features: | ||
20 | - Fast, standards compliant encoding/parsing routines | ||
21 | - Full support for JSON with UTF-8, including decoding surrogate pairs | ||
22 | - Optional run-time support for common exceptions to the JSON specification | ||
23 | (NaN, Infinity,..) | ||
24 | - No external dependencies | ||
25 | |||
26 | |||
27 | %prep | ||
28 | %setup -q | ||
29 | |||
30 | |||
31 | %build | ||
32 | make %{?_smp_mflags} CFLAGS="%{optflags}" LUA_INCLUDE_DIR="%{_includedir}" | ||
33 | |||
34 | |||
35 | %install | ||
36 | rm -rf "$RPM_BUILD_ROOT" | ||
37 | make install DESTDIR="$RPM_BUILD_ROOT" LUA_MODULE_DIR="%{lualibdir}" | ||
38 | |||
39 | |||
40 | %clean | ||
41 | rm -rf "$RPM_BUILD_ROOT" | ||
42 | |||
43 | |||
44 | %files | ||
45 | %defattr(-,root,root,-) | ||
46 | %doc LICENSE NEWS performance.txt manual.html manual.txt rfc4627.txt tests THANKS | ||
47 | %{lualibdir}/* | ||
48 | |||
49 | |||
50 | %changelog | ||
51 | * Wed Nov 27 2011 Mark Pulford <mark@kyne.com.au> - 1.0.4-1 | ||
52 | - Updated for 1.0.4 | ||
53 | |||
54 | * Wed Sep 15 2011 Mark Pulford <mark@kyne.com.au> - 1.0.3-1 | ||
55 | - Updated for 1.0.3 | ||
56 | |||
57 | * Sun May 29 2011 Mark Pulford <mark@kyne.com.au> - 1.0.2-1 | ||
58 | - Updated for 1.0.2 | ||
59 | |||
60 | * Sun May 10 2011 Mark Pulford <mark@kyne.com.au> - 1.0.1-1 | ||
61 | - Updated for 1.0.1 | ||
62 | |||
63 | * Sun May 1 2011 Mark Pulford <mark@kyne.com.au> - 1.0-1 | ||
64 | - Initial package | ||
diff --git a/lua_cjson.c b/lua_cjson.c new file mode 100644 index 0000000..708e695 --- /dev/null +++ b/lua_cjson.c | |||
@@ -0,0 +1,1344 @@ | |||
1 | /* CJSON - JSON support for Lua | ||
2 | * | ||
3 | * Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au> | ||
4 | * | ||
5 | * Permission is hereby granted, free of charge, to any person obtaining | ||
6 | * a copy of this software and associated documentation files (the | ||
7 | * "Software"), to deal in the Software without restriction, including | ||
8 | * without limitation the rights to use, copy, modify, merge, publish, | ||
9 | * distribute, sublicense, and/or sell copies of the Software, and to | ||
10 | * permit persons to whom the Software is furnished to do so, subject to | ||
11 | * the following conditions: | ||
12 | * | ||
13 | * The above copyright notice and this permission notice shall be | ||
14 | * included in all copies or substantial portions of the Software. | ||
15 | * | ||
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
23 | */ | ||
24 | |||
25 | /* Caveats: | ||
26 | * - JSON "null" values are represented as lightuserdata since Lua | ||
27 | * tables cannot contain "nil". Compare with cjson.null. | ||
28 | * - Invalid UTF-8 characters are not detected and will be passed | ||
29 | * untouched. If required, UTF-8 error checking should be done | ||
30 | * outside this library. | ||
31 | * - Javascript comments are not part of the JSON spec, and are not | ||
32 | * currently supported. | ||
33 | * | ||
34 | * Note: Decoding is slower than encoding. Lua spends significant | ||
35 | * time (30%) managing tables when parsing JSON since it is | ||
36 | * difficult to know object/array sizes ahead of time. | ||
37 | */ | ||
38 | |||
39 | #include <assert.h> | ||
40 | #include <string.h> | ||
41 | #include <math.h> | ||
42 | #include <lua.h> | ||
43 | #include <lauxlib.h> | ||
44 | |||
45 | #include "strbuf.h" | ||
46 | #include "fpconv.h" | ||
47 | |||
48 | #ifndef CJSON_MODNAME | ||
49 | #define CJSON_MODNAME "cjson" | ||
50 | #endif | ||
51 | |||
52 | #ifndef CJSON_VERSION | ||
53 | #define CJSON_VERSION "1.0devel" | ||
54 | #endif | ||
55 | |||
56 | /* Workaround for Solaris platforms missing isinf() */ | ||
57 | #if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF)) | ||
58 | #define isinf(x) (!isnan(x) && isnan((x) - (x))) | ||
59 | #endif | ||
60 | |||
61 | #define DEFAULT_SPARSE_CONVERT 0 | ||
62 | #define DEFAULT_SPARSE_RATIO 2 | ||
63 | #define DEFAULT_SPARSE_SAFE 10 | ||
64 | #define DEFAULT_MAX_DEPTH 20 | ||
65 | #define DEFAULT_ENCODE_REFUSE_BADNUM 1 | ||
66 | #define DEFAULT_DECODE_REFUSE_BADNUM 0 | ||
67 | #define DEFAULT_ENCODE_KEEP_BUFFER 1 | ||
68 | #define DEFAULT_ENCODE_NUMBER_PRECISION 14 | ||
69 | |||
70 | #ifdef DISABLE_INVALID_NUMBERS | ||
71 | #undef DEFAULT_DECODE_REFUSE_BADNUM | ||
72 | #define DEFAULT_DECODE_REFUSE_BADNUM 1 | ||
73 | #endif | ||
74 | |||
75 | typedef enum { | ||
76 | T_OBJ_BEGIN, | ||
77 | T_OBJ_END, | ||
78 | T_ARR_BEGIN, | ||
79 | T_ARR_END, | ||
80 | T_STRING, | ||
81 | T_NUMBER, | ||
82 | T_BOOLEAN, | ||
83 | T_NULL, | ||
84 | T_COLON, | ||
85 | T_COMMA, | ||
86 | T_END, | ||
87 | T_WHITESPACE, | ||
88 | T_ERROR, | ||
89 | T_UNKNOWN | ||
90 | } json_token_type_t; | ||
91 | |||
92 | static const char *json_token_type_name[] = { | ||
93 | "T_OBJ_BEGIN", | ||
94 | "T_OBJ_END", | ||
95 | "T_ARR_BEGIN", | ||
96 | "T_ARR_END", | ||
97 | "T_STRING", | ||
98 | "T_NUMBER", | ||
99 | "T_BOOLEAN", | ||
100 | "T_NULL", | ||
101 | "T_COLON", | ||
102 | "T_COMMA", | ||
103 | "T_END", | ||
104 | "T_WHITESPACE", | ||
105 | "T_ERROR", | ||
106 | "T_UNKNOWN", | ||
107 | NULL | ||
108 | }; | ||
109 | |||
110 | typedef struct { | ||
111 | json_token_type_t ch2token[256]; | ||
112 | char escape2char[256]; /* Decoding */ | ||
113 | |||
114 | /* encode_buf is only allocated and used when | ||
115 | * encode_keep_buffer is set */ | ||
116 | strbuf_t encode_buf; | ||
117 | |||
118 | int encode_sparse_convert; | ||
119 | int encode_sparse_ratio; | ||
120 | int encode_sparse_safe; | ||
121 | int encode_max_depth; | ||
122 | int encode_refuse_badnum; | ||
123 | int decode_refuse_badnum; | ||
124 | int encode_keep_buffer; | ||
125 | int encode_number_precision; | ||
126 | } json_config_t; | ||
127 | |||
128 | typedef struct { | ||
129 | const char *data; | ||
130 | int index; | ||
131 | strbuf_t *tmp; /* Temporary storage for strings */ | ||
132 | json_config_t *cfg; | ||
133 | } json_parse_t; | ||
134 | |||
135 | typedef struct { | ||
136 | json_token_type_t type; | ||
137 | int index; | ||
138 | union { | ||
139 | const char *string; | ||
140 | double number; | ||
141 | int boolean; | ||
142 | } value; | ||
143 | int string_len; | ||
144 | } json_token_t; | ||
145 | |||
146 | static const char *char2escape[256] = { | ||
147 | "\\u0000", "\\u0001", "\\u0002", "\\u0003", | ||
148 | "\\u0004", "\\u0005", "\\u0006", "\\u0007", | ||
149 | "\\b", "\\t", "\\n", "\\u000b", | ||
150 | "\\f", "\\r", "\\u000e", "\\u000f", | ||
151 | "\\u0010", "\\u0011", "\\u0012", "\\u0013", | ||
152 | "\\u0014", "\\u0015", "\\u0016", "\\u0017", | ||
153 | "\\u0018", "\\u0019", "\\u001a", "\\u001b", | ||
154 | "\\u001c", "\\u001d", "\\u001e", "\\u001f", | ||
155 | NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL, | ||
156 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/", | ||
157 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
158 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
159 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
160 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
161 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
162 | NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL, | ||
163 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
164 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
165 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
166 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f", | ||
167 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
168 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
169 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
170 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
171 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
172 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
173 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
174 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
175 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
176 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
177 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
178 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
179 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
180 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
181 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
182 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
183 | }; | ||
184 | |||
185 | /* ===== CONFIGURATION ===== */ | ||
186 | |||
187 | static json_config_t *json_fetch_config(lua_State *l) | ||
188 | { | ||
189 | json_config_t *cfg; | ||
190 | |||
191 | cfg = lua_touserdata(l, lua_upvalueindex(1)); | ||
192 | if (!cfg) | ||
193 | luaL_error(l, "BUG: Unable to fetch CJSON configuration"); | ||
194 | |||
195 | return cfg; | ||
196 | } | ||
197 | |||
198 | static void json_verify_arg_count(lua_State *l, int args) | ||
199 | { | ||
200 | luaL_argcheck(l, lua_gettop(l) <= args, args + 1, | ||
201 | "found too many arguments"); | ||
202 | } | ||
203 | |||
204 | /* Configures handling of extremely sparse arrays: | ||
205 | * convert: Convert extremely sparse arrays into objects? Otherwise error. | ||
206 | * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio | ||
207 | * safe: Always use an array when the max index <= safe */ | ||
208 | static int json_cfg_encode_sparse_array(lua_State *l) | ||
209 | { | ||
210 | json_config_t *cfg; | ||
211 | int val; | ||
212 | |||
213 | json_verify_arg_count(l, 3); | ||
214 | cfg = json_fetch_config(l); | ||
215 | |||
216 | switch (lua_gettop(l)) { | ||
217 | case 3: | ||
218 | val = luaL_checkinteger(l, 3); | ||
219 | luaL_argcheck(l, val >= 0, 3, "expected integer >= 0"); | ||
220 | cfg->encode_sparse_safe = val; | ||
221 | case 2: | ||
222 | val = luaL_checkinteger(l, 2); | ||
223 | luaL_argcheck(l, val >= 0, 2, "expected integer >= 0"); | ||
224 | cfg->encode_sparse_ratio = val; | ||
225 | case 1: | ||
226 | luaL_argcheck(l, lua_isboolean(l, 1), 1, "expected boolean"); | ||
227 | cfg->encode_sparse_convert = lua_toboolean(l, 1); | ||
228 | } | ||
229 | |||
230 | lua_pushboolean(l, cfg->encode_sparse_convert); | ||
231 | lua_pushinteger(l, cfg->encode_sparse_ratio); | ||
232 | lua_pushinteger(l, cfg->encode_sparse_safe); | ||
233 | |||
234 | return 3; | ||
235 | } | ||
236 | |||
237 | /* Configures the maximum number of nested arrays/objects allowed when | ||
238 | * encoding */ | ||
239 | static int json_cfg_encode_max_depth(lua_State *l) | ||
240 | { | ||
241 | json_config_t *cfg; | ||
242 | int depth; | ||
243 | |||
244 | json_verify_arg_count(l, 1); | ||
245 | cfg = json_fetch_config(l); | ||
246 | |||
247 | if (lua_gettop(l)) { | ||
248 | depth = luaL_checkinteger(l, 1); | ||
249 | luaL_argcheck(l, depth > 0, 1, "expected positive integer"); | ||
250 | cfg->encode_max_depth = depth; | ||
251 | } | ||
252 | |||
253 | lua_pushinteger(l, cfg->encode_max_depth); | ||
254 | |||
255 | return 1; | ||
256 | } | ||
257 | |||
258 | /* Configures number precision when converting doubles to text */ | ||
259 | static int json_cfg_encode_number_precision(lua_State *l) | ||
260 | { | ||
261 | json_config_t *cfg; | ||
262 | int precision; | ||
263 | |||
264 | json_verify_arg_count(l, 1); | ||
265 | cfg = json_fetch_config(l); | ||
266 | |||
267 | if (lua_gettop(l)) { | ||
268 | precision = luaL_checkinteger(l, 1); | ||
269 | luaL_argcheck(l, 1 <= precision && precision <= 14, 1, | ||
270 | "expected integer between 1 and 14"); | ||
271 | cfg->encode_number_precision = precision; | ||
272 | } | ||
273 | |||
274 | lua_pushinteger(l, cfg->encode_number_precision); | ||
275 | |||
276 | return 1; | ||
277 | } | ||
278 | |||
279 | /* Configures JSON encoding buffer persistence */ | ||
280 | static int json_cfg_encode_keep_buffer(lua_State *l) | ||
281 | { | ||
282 | json_config_t *cfg; | ||
283 | int old_value; | ||
284 | |||
285 | json_verify_arg_count(l, 1); | ||
286 | cfg = json_fetch_config(l); | ||
287 | |||
288 | old_value = cfg->encode_keep_buffer; | ||
289 | |||
290 | if (lua_gettop(l)) { | ||
291 | luaL_checktype(l, 1, LUA_TBOOLEAN); | ||
292 | cfg->encode_keep_buffer = lua_toboolean(l, 1); | ||
293 | } | ||
294 | |||
295 | /* Init / free the buffer if the setting has changed */ | ||
296 | if (old_value ^ cfg->encode_keep_buffer) { | ||
297 | if (cfg->encode_keep_buffer) | ||
298 | strbuf_init(&cfg->encode_buf, 0); | ||
299 | else | ||
300 | strbuf_free(&cfg->encode_buf); | ||
301 | } | ||
302 | |||
303 | lua_pushboolean(l, cfg->encode_keep_buffer); | ||
304 | |||
305 | return 1; | ||
306 | } | ||
307 | |||
308 | /* On argument: decode enum and set config variables | ||
309 | * **options must point to a NULL terminated array of 4 enums | ||
310 | * Returns: current enum value */ | ||
311 | static void json_enum_option(lua_State *l, const char **options, | ||
312 | int *opt1, int *opt2) | ||
313 | { | ||
314 | int setting; | ||
315 | |||
316 | if (lua_gettop(l)) { | ||
317 | if (lua_isboolean(l, 1)) | ||
318 | setting = lua_toboolean(l, 1) * 3; | ||
319 | else | ||
320 | setting = luaL_checkoption(l, 1, NULL, options); | ||
321 | |||
322 | *opt1 = setting & 1 ? 1 : 0; | ||
323 | *opt2 = setting & 2 ? 1 : 0; | ||
324 | } else { | ||
325 | setting = *opt1 | (*opt2 << 1); | ||
326 | } | ||
327 | |||
328 | if (setting) | ||
329 | lua_pushstring(l, options[setting]); | ||
330 | else | ||
331 | lua_pushboolean(l, 0); | ||
332 | } | ||
333 | |||
334 | |||
335 | /* When enabled, rejects: NaN, Infinity, hexadecimal numbers */ | ||
336 | static int json_cfg_refuse_invalid_numbers(lua_State *l) | ||
337 | { | ||
338 | static const char *options_enc_dec[] = { "none", "encode", "decode", | ||
339 | "both", NULL }; | ||
340 | json_config_t *cfg; | ||
341 | |||
342 | json_verify_arg_count(l, 1); | ||
343 | cfg = json_fetch_config(l); | ||
344 | |||
345 | json_enum_option(l, options_enc_dec, | ||
346 | &cfg->encode_refuse_badnum, | ||
347 | &cfg->decode_refuse_badnum); | ||
348 | |||
349 | #if DISABLE_INVALID_NUMBERS | ||
350 | /* Some non-POSIX platforms don't handle double <-> string translations | ||
351 | * for Infinity/NaN/hexadecimal properly. Throw an error if the | ||
352 | * user attempts to enable them. */ | ||
353 | if (!cfg->encode_refuse_badnum || !cfg->decode_refuse_badnum) { | ||
354 | cfg->encode_refuse_badnum = cfg->decode_refuse_badnum = 1; | ||
355 | luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported."); | ||
356 | } | ||
357 | #endif | ||
358 | |||
359 | return 1; | ||
360 | } | ||
361 | |||
362 | static int json_destroy_config(lua_State *l) | ||
363 | { | ||
364 | json_config_t *cfg; | ||
365 | |||
366 | cfg = lua_touserdata(l, 1); | ||
367 | if (cfg) | ||
368 | strbuf_free(&cfg->encode_buf); | ||
369 | cfg = NULL; | ||
370 | |||
371 | return 0; | ||
372 | } | ||
373 | |||
374 | static void json_create_config(lua_State *l) | ||
375 | { | ||
376 | json_config_t *cfg; | ||
377 | int i; | ||
378 | |||
379 | cfg = lua_newuserdata(l, sizeof(*cfg)); | ||
380 | |||
381 | /* Create GC method to clean up strbuf */ | ||
382 | lua_newtable(l); | ||
383 | lua_pushcfunction(l, json_destroy_config); | ||
384 | lua_setfield(l, -2, "__gc"); | ||
385 | lua_setmetatable(l, -2); | ||
386 | |||
387 | cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT; | ||
388 | cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO; | ||
389 | cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; | ||
390 | cfg->encode_max_depth = DEFAULT_MAX_DEPTH; | ||
391 | cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM; | ||
392 | cfg->decode_refuse_badnum = DEFAULT_DECODE_REFUSE_BADNUM; | ||
393 | cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; | ||
394 | cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; | ||
395 | |||
396 | #if DEFAULT_ENCODE_KEEP_BUFFER > 0 | ||
397 | strbuf_init(&cfg->encode_buf, 0); | ||
398 | #endif | ||
399 | |||
400 | /* Decoding init */ | ||
401 | |||
402 | /* Tag all characters as an error */ | ||
403 | for (i = 0; i < 256; i++) | ||
404 | cfg->ch2token[i] = T_ERROR; | ||
405 | |||
406 | /* Set tokens that require no further processing */ | ||
407 | cfg->ch2token['{'] = T_OBJ_BEGIN; | ||
408 | cfg->ch2token['}'] = T_OBJ_END; | ||
409 | cfg->ch2token['['] = T_ARR_BEGIN; | ||
410 | cfg->ch2token[']'] = T_ARR_END; | ||
411 | cfg->ch2token[','] = T_COMMA; | ||
412 | cfg->ch2token[':'] = T_COLON; | ||
413 | cfg->ch2token['\0'] = T_END; | ||
414 | cfg->ch2token[' '] = T_WHITESPACE; | ||
415 | cfg->ch2token['\t'] = T_WHITESPACE; | ||
416 | cfg->ch2token['\n'] = T_WHITESPACE; | ||
417 | cfg->ch2token['\r'] = T_WHITESPACE; | ||
418 | |||
419 | /* Update characters that require further processing */ | ||
420 | cfg->ch2token['f'] = T_UNKNOWN; /* false? */ | ||
421 | cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */ | ||
422 | cfg->ch2token['I'] = T_UNKNOWN; | ||
423 | cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */ | ||
424 | cfg->ch2token['N'] = T_UNKNOWN; | ||
425 | cfg->ch2token['t'] = T_UNKNOWN; /* true? */ | ||
426 | cfg->ch2token['"'] = T_UNKNOWN; /* string? */ | ||
427 | cfg->ch2token['+'] = T_UNKNOWN; /* number? */ | ||
428 | cfg->ch2token['-'] = T_UNKNOWN; | ||
429 | for (i = 0; i < 10; i++) | ||
430 | cfg->ch2token['0' + i] = T_UNKNOWN; | ||
431 | |||
432 | /* Lookup table for parsing escape characters */ | ||
433 | for (i = 0; i < 256; i++) | ||
434 | cfg->escape2char[i] = 0; /* String error */ | ||
435 | cfg->escape2char['"'] = '"'; | ||
436 | cfg->escape2char['\\'] = '\\'; | ||
437 | cfg->escape2char['/'] = '/'; | ||
438 | cfg->escape2char['b'] = '\b'; | ||
439 | cfg->escape2char['t'] = '\t'; | ||
440 | cfg->escape2char['n'] = '\n'; | ||
441 | cfg->escape2char['f'] = '\f'; | ||
442 | cfg->escape2char['r'] = '\r'; | ||
443 | cfg->escape2char['u'] = 'u'; /* Unicode parsing required */ | ||
444 | } | ||
445 | |||
446 | /* ===== ENCODING ===== */ | ||
447 | |||
448 | static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex, | ||
449 | const char *reason) | ||
450 | { | ||
451 | if (!cfg->encode_keep_buffer) | ||
452 | strbuf_free(json); | ||
453 | luaL_error(l, "Cannot serialise %s: %s", | ||
454 | lua_typename(l, lua_type(l, lindex)), reason); | ||
455 | } | ||
456 | |||
457 | /* json_append_string args: | ||
458 | * - lua_State | ||
459 | * - JSON strbuf | ||
460 | * - String (Lua stack index) | ||
461 | * | ||
462 | * Returns nothing. Doesn't remove string from Lua stack */ | ||
463 | static void json_append_string(lua_State *l, strbuf_t *json, int lindex) | ||
464 | { | ||
465 | const char *escstr; | ||
466 | int i; | ||
467 | const char *str; | ||
468 | size_t len; | ||
469 | |||
470 | str = lua_tolstring(l, lindex, &len); | ||
471 | |||
472 | /* Worst case is len * 6 (all unicode escapes). | ||
473 | * This buffer is reused constantly for small strings | ||
474 | * If there are any excess pages, they won't be hit anyway. | ||
475 | * This gains ~5% speedup. */ | ||
476 | strbuf_ensure_empty_length(json, len * 6 + 2); | ||
477 | |||
478 | strbuf_append_char_unsafe(json, '\"'); | ||
479 | for (i = 0; i < len; i++) { | ||
480 | escstr = char2escape[(unsigned char)str[i]]; | ||
481 | if (escstr) | ||
482 | strbuf_append_string(json, escstr); | ||
483 | else | ||
484 | strbuf_append_char_unsafe(json, str[i]); | ||
485 | } | ||
486 | strbuf_append_char_unsafe(json, '\"'); | ||
487 | } | ||
488 | |||
489 | /* Find the size of the array on the top of the Lua stack | ||
490 | * -1 object (not a pure array) | ||
491 | * >=0 elements in array | ||
492 | */ | ||
493 | static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json) | ||
494 | { | ||
495 | double k; | ||
496 | int max; | ||
497 | int items; | ||
498 | |||
499 | max = 0; | ||
500 | items = 0; | ||
501 | |||
502 | lua_pushnil(l); | ||
503 | /* table, startkey */ | ||
504 | while (lua_next(l, -2) != 0) { | ||
505 | /* table, key, value */ | ||
506 | if (lua_type(l, -2) == LUA_TNUMBER && | ||
507 | (k = lua_tonumber(l, -2))) { | ||
508 | /* Integer >= 1 ? */ | ||
509 | if (floor(k) == k && k >= 1) { | ||
510 | if (k > max) | ||
511 | max = k; | ||
512 | items++; | ||
513 | lua_pop(l, 1); | ||
514 | continue; | ||
515 | } | ||
516 | } | ||
517 | |||
518 | /* Must not be an array (non integer key) */ | ||
519 | lua_pop(l, 2); | ||
520 | return -1; | ||
521 | } | ||
522 | |||
523 | /* Encode excessively sparse arrays as objects (if enabled) */ | ||
524 | if (cfg->encode_sparse_ratio > 0 && | ||
525 | max > items * cfg->encode_sparse_ratio && | ||
526 | max > cfg->encode_sparse_safe) { | ||
527 | if (!cfg->encode_sparse_convert) | ||
528 | json_encode_exception(l, cfg, json, -1, "excessively sparse array"); | ||
529 | |||
530 | return -1; | ||
531 | } | ||
532 | |||
533 | return max; | ||
534 | } | ||
535 | |||
536 | static void json_check_encode_depth(lua_State *l, json_config_t *cfg, | ||
537 | int current_depth, strbuf_t *json) | ||
538 | { | ||
539 | if (current_depth > cfg->encode_max_depth) { | ||
540 | if (!cfg->encode_keep_buffer) | ||
541 | strbuf_free(json); | ||
542 | luaL_error(l, "Cannot serialise, excessive nesting (%d)", | ||
543 | current_depth); | ||
544 | } | ||
545 | } | ||
546 | |||
547 | static void json_append_data(lua_State *l, json_config_t *cfg, | ||
548 | int current_depth, strbuf_t *json); | ||
549 | |||
550 | /* json_append_array args: | ||
551 | * - lua_State | ||
552 | * - JSON strbuf | ||
553 | * - Size of passwd Lua array (top of stack) */ | ||
554 | static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth, | ||
555 | strbuf_t *json, int array_length) | ||
556 | { | ||
557 | int comma, i; | ||
558 | |||
559 | strbuf_append_char(json, '['); | ||
560 | |||
561 | comma = 0; | ||
562 | for (i = 1; i <= array_length; i++) { | ||
563 | if (comma) | ||
564 | strbuf_append_char(json, ','); | ||
565 | else | ||
566 | comma = 1; | ||
567 | |||
568 | lua_rawgeti(l, -1, i); | ||
569 | json_append_data(l, cfg, current_depth, json); | ||
570 | lua_pop(l, 1); | ||
571 | } | ||
572 | |||
573 | strbuf_append_char(json, ']'); | ||
574 | } | ||
575 | |||
576 | static void json_append_number(lua_State *l, json_config_t *cfg, | ||
577 | strbuf_t *json, int lindex) | ||
578 | { | ||
579 | double num = lua_tonumber(l, lindex); | ||
580 | int len; | ||
581 | |||
582 | if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num))) | ||
583 | json_encode_exception(l, cfg, json, lindex, "must not be NaN or Inf"); | ||
584 | |||
585 | if (isnan(num)) { | ||
586 | /* Some platforms may print -nan, just hard code it */ | ||
587 | strbuf_append_mem(json, "nan", 3); | ||
588 | } else { | ||
589 | strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE); | ||
590 | len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision); | ||
591 | strbuf_extend_length(json, len); | ||
592 | } | ||
593 | } | ||
594 | |||
595 | static void json_append_object(lua_State *l, json_config_t *cfg, | ||
596 | int current_depth, strbuf_t *json) | ||
597 | { | ||
598 | int comma, keytype; | ||
599 | |||
600 | /* Object */ | ||
601 | strbuf_append_char(json, '{'); | ||
602 | |||
603 | lua_pushnil(l); | ||
604 | /* table, startkey */ | ||
605 | comma = 0; | ||
606 | while (lua_next(l, -2) != 0) { | ||
607 | if (comma) | ||
608 | strbuf_append_char(json, ','); | ||
609 | else | ||
610 | comma = 1; | ||
611 | |||
612 | /* table, key, value */ | ||
613 | keytype = lua_type(l, -2); | ||
614 | if (keytype == LUA_TNUMBER) { | ||
615 | strbuf_append_char(json, '"'); | ||
616 | json_append_number(l, cfg, json, -2); | ||
617 | strbuf_append_mem(json, "\":", 2); | ||
618 | } else if (keytype == LUA_TSTRING) { | ||
619 | json_append_string(l, json, -2); | ||
620 | strbuf_append_char(json, ':'); | ||
621 | } else { | ||
622 | json_encode_exception(l, cfg, json, -2, | ||
623 | "table key must be a number or string"); | ||
624 | /* never returns */ | ||
625 | } | ||
626 | |||
627 | /* table, key, value */ | ||
628 | json_append_data(l, cfg, current_depth, json); | ||
629 | lua_pop(l, 1); | ||
630 | /* table, key */ | ||
631 | } | ||
632 | |||
633 | strbuf_append_char(json, '}'); | ||
634 | } | ||
635 | |||
636 | /* Serialise Lua data into JSON string. */ | ||
637 | static void json_append_data(lua_State *l, json_config_t *cfg, | ||
638 | int current_depth, strbuf_t *json) | ||
639 | { | ||
640 | int len; | ||
641 | |||
642 | switch (lua_type(l, -1)) { | ||
643 | case LUA_TSTRING: | ||
644 | json_append_string(l, json, -1); | ||
645 | break; | ||
646 | case LUA_TNUMBER: | ||
647 | json_append_number(l, cfg, json, -1); | ||
648 | break; | ||
649 | case LUA_TBOOLEAN: | ||
650 | if (lua_toboolean(l, -1)) | ||
651 | strbuf_append_mem(json, "true", 4); | ||
652 | else | ||
653 | strbuf_append_mem(json, "false", 5); | ||
654 | break; | ||
655 | case LUA_TTABLE: | ||
656 | len = lua_array_length(l, cfg, json); | ||
657 | current_depth++; | ||
658 | json_check_encode_depth(l, cfg, current_depth, json); | ||
659 | if (len > 0) | ||
660 | json_append_array(l, cfg, current_depth, json, len); | ||
661 | else | ||
662 | json_append_object(l, cfg, current_depth, json); | ||
663 | break; | ||
664 | case LUA_TNIL: | ||
665 | strbuf_append_mem(json, "null", 4); | ||
666 | break; | ||
667 | case LUA_TLIGHTUSERDATA: | ||
668 | if (lua_touserdata(l, -1) == NULL) { | ||
669 | strbuf_append_mem(json, "null", 4); | ||
670 | break; | ||
671 | } | ||
672 | default: | ||
673 | /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, | ||
674 | * and LUA_TLIGHTUSERDATA) cannot be serialised */ | ||
675 | json_encode_exception(l, cfg, json, -1, "type not supported"); | ||
676 | /* never returns */ | ||
677 | } | ||
678 | } | ||
679 | |||
680 | static int json_encode(lua_State *l) | ||
681 | { | ||
682 | json_config_t *cfg; | ||
683 | strbuf_t local_encode_buf; | ||
684 | strbuf_t *encode_buf; | ||
685 | char *json; | ||
686 | int len; | ||
687 | |||
688 | /* Can't use json_verify_arg_count() since we need to ensure | ||
689 | * there is only 1 argument */ | ||
690 | luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); | ||
691 | |||
692 | cfg = json_fetch_config(l); | ||
693 | |||
694 | if (!cfg->encode_keep_buffer) { | ||
695 | /* Use private buffer */ | ||
696 | encode_buf = &local_encode_buf; | ||
697 | strbuf_init(encode_buf, 0); | ||
698 | } else { | ||
699 | /* Reuse existing buffer */ | ||
700 | encode_buf = &cfg->encode_buf; | ||
701 | strbuf_reset(encode_buf); | ||
702 | } | ||
703 | |||
704 | json_append_data(l, cfg, 0, encode_buf); | ||
705 | json = strbuf_string(encode_buf, &len); | ||
706 | |||
707 | lua_pushlstring(l, json, len); | ||
708 | |||
709 | if (!cfg->encode_keep_buffer) | ||
710 | strbuf_free(encode_buf); | ||
711 | |||
712 | return 1; | ||
713 | } | ||
714 | |||
715 | /* ===== DECODING ===== */ | ||
716 | |||
717 | static void json_process_value(lua_State *l, json_parse_t *json, | ||
718 | json_token_t *token); | ||
719 | |||
720 | static int hexdigit2int(char hex) | ||
721 | { | ||
722 | if ('0' <= hex && hex <= '9') | ||
723 | return hex - '0'; | ||
724 | |||
725 | /* Force lowercase */ | ||
726 | hex |= 0x20; | ||
727 | if ('a' <= hex && hex <= 'f') | ||
728 | return 10 + hex - 'a'; | ||
729 | |||
730 | return -1; | ||
731 | } | ||
732 | |||
733 | static int decode_hex4(const char *hex) | ||
734 | { | ||
735 | int digit[4]; | ||
736 | int i; | ||
737 | |||
738 | /* Convert ASCII hex digit to numeric digit | ||
739 | * Note: this returns an error for invalid hex digits, including | ||
740 | * NULL */ | ||
741 | for (i = 0; i < 4; i++) { | ||
742 | digit[i] = hexdigit2int(hex[i]); | ||
743 | if (digit[i] < 0) { | ||
744 | return -1; | ||
745 | } | ||
746 | } | ||
747 | |||
748 | return (digit[0] << 12) + | ||
749 | (digit[1] << 8) + | ||
750 | (digit[2] << 4) + | ||
751 | digit[3]; | ||
752 | } | ||
753 | |||
754 | /* Converts a Unicode codepoint to UTF-8. | ||
755 | * Returns UTF-8 string length, and up to 4 bytes in *utf8 */ | ||
756 | static int codepoint_to_utf8(char *utf8, int codepoint) | ||
757 | { | ||
758 | /* 0xxxxxxx */ | ||
759 | if (codepoint <= 0x7F) { | ||
760 | utf8[0] = codepoint; | ||
761 | return 1; | ||
762 | } | ||
763 | |||
764 | /* 110xxxxx 10xxxxxx */ | ||
765 | if (codepoint <= 0x7FF) { | ||
766 | utf8[0] = (codepoint >> 6) | 0xC0; | ||
767 | utf8[1] = (codepoint & 0x3F) | 0x80; | ||
768 | return 2; | ||
769 | } | ||
770 | |||
771 | /* 1110xxxx 10xxxxxx 10xxxxxx */ | ||
772 | if (codepoint <= 0xFFFF) { | ||
773 | utf8[0] = (codepoint >> 12) | 0xE0; | ||
774 | utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80; | ||
775 | utf8[2] = (codepoint & 0x3F) | 0x80; | ||
776 | return 3; | ||
777 | } | ||
778 | |||
779 | /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ | ||
780 | if (codepoint <= 0x1FFFFF) { | ||
781 | utf8[0] = (codepoint >> 18) | 0xF0; | ||
782 | utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80; | ||
783 | utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80; | ||
784 | utf8[3] = (codepoint & 0x3F) | 0x80; | ||
785 | return 4; | ||
786 | } | ||
787 | |||
788 | return 0; | ||
789 | } | ||
790 | |||
791 | |||
792 | /* Called when index pointing to beginning of UTF-16 code escape: \uXXXX | ||
793 | * \u is guaranteed to exist, but the remaining hex characters may be | ||
794 | * missing. | ||
795 | * Translate to UTF-8 and append to temporary token string. | ||
796 | * Must advance index to the next character to be processed. | ||
797 | * Returns: 0 success | ||
798 | * -1 error | ||
799 | */ | ||
800 | static int json_append_unicode_escape(json_parse_t *json) | ||
801 | { | ||
802 | char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */ | ||
803 | int codepoint; | ||
804 | int surrogate_low; | ||
805 | int len; | ||
806 | int escape_len = 6; | ||
807 | |||
808 | /* Fetch UTF-16 code unit */ | ||
809 | codepoint = decode_hex4(&json->data[json->index + 2]); | ||
810 | if (codepoint < 0) | ||
811 | return -1; | ||
812 | |||
813 | /* UTF-16 surrogate pairs take the following 2 byte form: | ||
814 | * 11011 x yyyyyyyyyy | ||
815 | * When x = 0: y is the high 10 bits of the codepoint | ||
816 | * x = 1: y is the low 10 bits of the codepoint | ||
817 | * | ||
818 | * Check for a surrogate pair (high or low) */ | ||
819 | if ((codepoint & 0xF800) == 0xD800) { | ||
820 | /* Error if the 1st surrogate is not high */ | ||
821 | if (codepoint & 0x400) | ||
822 | return -1; | ||
823 | |||
824 | /* Ensure the next code is a unicode escape */ | ||
825 | if (json->data[json->index + escape_len] != '\\' || | ||
826 | json->data[json->index + escape_len + 1] != 'u') { | ||
827 | return -1; | ||
828 | } | ||
829 | |||
830 | /* Fetch the next codepoint */ | ||
831 | surrogate_low = decode_hex4(&json->data[json->index + 2 + escape_len]); | ||
832 | if (surrogate_low < 0) | ||
833 | return -1; | ||
834 | |||
835 | /* Error if the 2nd code is not a low surrogate */ | ||
836 | if ((surrogate_low & 0xFC00) != 0xDC00) | ||
837 | return -1; | ||
838 | |||
839 | /* Calculate Unicode codepoint */ | ||
840 | codepoint = (codepoint & 0x3FF) << 10; | ||
841 | surrogate_low &= 0x3FF; | ||
842 | codepoint = (codepoint | surrogate_low) + 0x10000; | ||
843 | escape_len = 12; | ||
844 | } | ||
845 | |||
846 | /* Convert codepoint to UTF-8 */ | ||
847 | len = codepoint_to_utf8(utf8, codepoint); | ||
848 | if (!len) | ||
849 | return -1; | ||
850 | |||
851 | /* Append bytes and advance parse index */ | ||
852 | strbuf_append_mem_unsafe(json->tmp, utf8, len); | ||
853 | json->index += escape_len; | ||
854 | |||
855 | return 0; | ||
856 | } | ||
857 | |||
858 | static void json_set_token_error(json_token_t *token, json_parse_t *json, | ||
859 | const char *errtype) | ||
860 | { | ||
861 | token->type = T_ERROR; | ||
862 | token->index = json->index; | ||
863 | token->value.string = errtype; | ||
864 | } | ||
865 | |||
866 | static void json_next_string_token(json_parse_t *json, json_token_t *token) | ||
867 | { | ||
868 | char *escape2char = json->cfg->escape2char; | ||
869 | char ch; | ||
870 | |||
871 | /* Caller must ensure a string is next */ | ||
872 | assert(json->data[json->index] == '"'); | ||
873 | |||
874 | /* Skip " */ | ||
875 | json->index++; | ||
876 | |||
877 | /* json->tmp is the temporary strbuf used to accumulate the | ||
878 | * decoded string value. | ||
879 | * json->tmp is sized to handle JSON containing only a string value. | ||
880 | */ | ||
881 | strbuf_reset(json->tmp); | ||
882 | |||
883 | while ((ch = json->data[json->index]) != '"') { | ||
884 | if (!ch) { | ||
885 | /* Premature end of the string */ | ||
886 | json_set_token_error(token, json, "unexpected end of string"); | ||
887 | return; | ||
888 | } | ||
889 | |||
890 | /* Handle escapes */ | ||
891 | if (ch == '\\') { | ||
892 | /* Fetch escape character */ | ||
893 | ch = json->data[json->index + 1]; | ||
894 | |||
895 | /* Translate escape code and append to tmp string */ | ||
896 | ch = escape2char[(unsigned char)ch]; | ||
897 | if (ch == 'u') { | ||
898 | if (json_append_unicode_escape(json) == 0) | ||
899 | continue; | ||
900 | |||
901 | json_set_token_error(token, json, | ||
902 | "invalid unicode escape code"); | ||
903 | return; | ||
904 | } | ||
905 | if (!ch) { | ||
906 | json_set_token_error(token, json, "invalid escape code"); | ||
907 | return; | ||
908 | } | ||
909 | |||
910 | /* Skip '\' */ | ||
911 | json->index++; | ||
912 | } | ||
913 | /* Append normal character or translated single character | ||
914 | * Unicode escapes are handled above */ | ||
915 | strbuf_append_char_unsafe(json->tmp, ch); | ||
916 | json->index++; | ||
917 | } | ||
918 | json->index++; /* Eat final quote (") */ | ||
919 | |||
920 | strbuf_ensure_null(json->tmp); | ||
921 | |||
922 | token->type = T_STRING; | ||
923 | token->value.string = strbuf_string(json->tmp, &token->string_len); | ||
924 | } | ||
925 | |||
926 | /* JSON numbers should take the following form: | ||
927 | * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)? | ||
928 | * | ||
929 | * json_next_number_token() uses strtod() which allows other forms: | ||
930 | * - numbers starting with '+' | ||
931 | * - NaN, -NaN, infinity, -infinity | ||
932 | * - hexadecimal numbers | ||
933 | * - numbers with leading zeros | ||
934 | * | ||
935 | * json_is_invalid_number() detects "numbers" which may pass strtod()'s | ||
936 | * error checking, but should not be allowed with strict JSON. | ||
937 | * | ||
938 | * json_is_invalid_number() may pass numbers which cause strtod() | ||
939 | * to generate an error. | ||
940 | */ | ||
941 | static int json_is_invalid_number(json_parse_t *json) | ||
942 | { | ||
943 | int i = json->index; | ||
944 | |||
945 | /* Reject numbers starting with + */ | ||
946 | if (json->data[i] == '+') | ||
947 | return 1; | ||
948 | |||
949 | /* Skip minus sign if it exists */ | ||
950 | if (json->data[i] == '-') | ||
951 | i++; | ||
952 | |||
953 | /* Reject numbers starting with 0x, or leading zeros */ | ||
954 | if (json->data[i] == '0') { | ||
955 | int ch2 = json->data[i + 1]; | ||
956 | |||
957 | if ((ch2 | 0x20) == 'x' || /* Hex */ | ||
958 | ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ | ||
959 | return 1; | ||
960 | |||
961 | return 0; | ||
962 | } else if (json->data[i] <= '9') { | ||
963 | return 0; /* Ordinary number */ | ||
964 | } | ||
965 | |||
966 | /* Reject inf/nan */ | ||
967 | if (!strncasecmp(&json->data[i], "inf", 3)) | ||
968 | return 1; | ||
969 | if (!strncasecmp(&json->data[i], "nan", 3)) | ||
970 | return 1; | ||
971 | |||
972 | /* Pass all other numbers which may still be invalid, but | ||
973 | * strtod() will catch them. */ | ||
974 | return 0; | ||
975 | } | ||
976 | |||
977 | static void json_next_number_token(json_parse_t *json, json_token_t *token) | ||
978 | { | ||
979 | const char *startptr; | ||
980 | char *endptr; | ||
981 | |||
982 | token->type = T_NUMBER; | ||
983 | startptr = &json->data[json->index]; | ||
984 | token->value.number = fpconv_strtod(&json->data[json->index], &endptr); | ||
985 | if (startptr == endptr) | ||
986 | json_set_token_error(token, json, "invalid number"); | ||
987 | else | ||
988 | json->index += endptr - startptr; /* Skip the processed number */ | ||
989 | |||
990 | return; | ||
991 | } | ||
992 | |||
993 | /* Fills in the token struct. | ||
994 | * T_STRING will return a pointer to the json_parse_t temporary string | ||
995 | * T_ERROR will leave the json->index pointer at the error. | ||
996 | */ | ||
997 | static void json_next_token(json_parse_t *json, json_token_t *token) | ||
998 | { | ||
999 | json_token_type_t *ch2token = json->cfg->ch2token; | ||
1000 | int ch; | ||
1001 | |||
1002 | /* Eat whitespace. FIXME: UGLY */ | ||
1003 | token->type = ch2token[(unsigned char)json->data[json->index]]; | ||
1004 | while (token->type == T_WHITESPACE) | ||
1005 | token->type = ch2token[(unsigned char)json->data[++json->index]]; | ||
1006 | |||
1007 | token->index = json->index; | ||
1008 | |||
1009 | /* Don't advance the pointer for an error or the end */ | ||
1010 | if (token->type == T_ERROR) { | ||
1011 | json_set_token_error(token, json, "invalid token"); | ||
1012 | return; | ||
1013 | } | ||
1014 | |||
1015 | if (token->type == T_END) { | ||
1016 | return; | ||
1017 | } | ||
1018 | |||
1019 | /* Found a known single character token, advance index and return */ | ||
1020 | if (token->type != T_UNKNOWN) { | ||
1021 | json->index++; | ||
1022 | return; | ||
1023 | } | ||
1024 | |||
1025 | /* Process characters which triggered T_UNKNOWN */ | ||
1026 | ch = json->data[json->index]; | ||
1027 | |||
1028 | /* Must use strncmp() to match the front of the JSON string. | ||
1029 | * JSON identifier must be lowercase. | ||
1030 | * When strict_numbers if disabled, either case is allowed for | ||
1031 | * Infinity/NaN (since we are no longer following the spec..) */ | ||
1032 | if (ch == '"') { | ||
1033 | json_next_string_token(json, token); | ||
1034 | return; | ||
1035 | } else if (ch == '-' || ('0' <= ch && ch <= '9')) { | ||
1036 | if (json->cfg->decode_refuse_badnum && json_is_invalid_number(json)) { | ||
1037 | json_set_token_error(token, json, "invalid number"); | ||
1038 | return; | ||
1039 | } | ||
1040 | json_next_number_token(json, token); | ||
1041 | return; | ||
1042 | } else if (!strncmp(&json->data[json->index], "true", 4)) { | ||
1043 | token->type = T_BOOLEAN; | ||
1044 | token->value.boolean = 1; | ||
1045 | json->index += 4; | ||
1046 | return; | ||
1047 | } else if (!strncmp(&json->data[json->index], "false", 5)) { | ||
1048 | token->type = T_BOOLEAN; | ||
1049 | token->value.boolean = 0; | ||
1050 | json->index += 5; | ||
1051 | return; | ||
1052 | } else if (!strncmp(&json->data[json->index], "null", 4)) { | ||
1053 | token->type = T_NULL; | ||
1054 | json->index += 4; | ||
1055 | return; | ||
1056 | } else if (!json->cfg->decode_refuse_badnum && | ||
1057 | json_is_invalid_number(json)) { | ||
1058 | /* When refuse_badnum is disabled, only attempt to process | ||
1059 | * numbers we know are invalid JSON (Inf, NaN, hex) | ||
1060 | * This is required to generate an appropriate token error, | ||
1061 | * otherwise all bad tokens will register as "invalid number" | ||
1062 | */ | ||
1063 | json_next_number_token(json, token); | ||
1064 | return; | ||
1065 | } | ||
1066 | |||
1067 | /* Token starts with t/f/n but isn't recognised above. */ | ||
1068 | json_set_token_error(token, json, "invalid token"); | ||
1069 | } | ||
1070 | |||
1071 | /* This function does not return. | ||
1072 | * DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED. | ||
1073 | * The only supported exception is the temporary parser string | ||
1074 | * json->tmp struct. | ||
1075 | * json and token should exist on the stack somewhere. | ||
1076 | * luaL_error() will long_jmp and release the stack */ | ||
1077 | static void json_throw_parse_error(lua_State *l, json_parse_t *json, | ||
1078 | const char *exp, json_token_t *token) | ||
1079 | { | ||
1080 | const char *found; | ||
1081 | |||
1082 | strbuf_free(json->tmp); | ||
1083 | |||
1084 | if (token->type == T_ERROR) | ||
1085 | found = token->value.string; | ||
1086 | else | ||
1087 | found = json_token_type_name[token->type]; | ||
1088 | |||
1089 | /* Note: token->index is 0 based, display starting from 1 */ | ||
1090 | luaL_error(l, "Expected %s but found %s at character %d", | ||
1091 | exp, found, token->index + 1); | ||
1092 | } | ||
1093 | |||
1094 | static void json_decode_checkstack(lua_State *l, json_parse_t *json, int n) | ||
1095 | { | ||
1096 | if (lua_checkstack(l, n)) | ||
1097 | return; | ||
1098 | |||
1099 | strbuf_free(json->tmp); | ||
1100 | luaL_error(l, "Too many nested data structures"); | ||
1101 | } | ||
1102 | |||
1103 | static void json_parse_object_context(lua_State *l, json_parse_t *json) | ||
1104 | { | ||
1105 | json_token_t token; | ||
1106 | |||
1107 | /* 3 slots required: | ||
1108 | * .., table, key, value */ | ||
1109 | json_decode_checkstack(l, json, 3); | ||
1110 | |||
1111 | lua_newtable(l); | ||
1112 | |||
1113 | json_next_token(json, &token); | ||
1114 | |||
1115 | /* Handle empty objects */ | ||
1116 | if (token.type == T_OBJ_END) { | ||
1117 | return; | ||
1118 | } | ||
1119 | |||
1120 | while (1) { | ||
1121 | if (token.type != T_STRING) | ||
1122 | json_throw_parse_error(l, json, "object key string", &token); | ||
1123 | |||
1124 | /* Push key */ | ||
1125 | lua_pushlstring(l, token.value.string, token.string_len); | ||
1126 | |||
1127 | json_next_token(json, &token); | ||
1128 | if (token.type != T_COLON) | ||
1129 | json_throw_parse_error(l, json, "colon", &token); | ||
1130 | |||
1131 | /* Fetch value */ | ||
1132 | json_next_token(json, &token); | ||
1133 | json_process_value(l, json, &token); | ||
1134 | |||
1135 | /* Set key = value */ | ||
1136 | lua_rawset(l, -3); | ||
1137 | |||
1138 | json_next_token(json, &token); | ||
1139 | |||
1140 | if (token.type == T_OBJ_END) | ||
1141 | return; | ||
1142 | |||
1143 | if (token.type != T_COMMA) | ||
1144 | json_throw_parse_error(l, json, "comma or object end", &token); | ||
1145 | |||
1146 | json_next_token(json, &token); | ||
1147 | } | ||
1148 | } | ||
1149 | |||
1150 | /* Handle the array context */ | ||
1151 | static void json_parse_array_context(lua_State *l, json_parse_t *json) | ||
1152 | { | ||
1153 | json_token_t token; | ||
1154 | int i; | ||
1155 | |||
1156 | /* 2 slots required: | ||
1157 | * .., table, value */ | ||
1158 | json_decode_checkstack(l, json, 2); | ||
1159 | |||
1160 | lua_newtable(l); | ||
1161 | |||
1162 | json_next_token(json, &token); | ||
1163 | |||
1164 | /* Handle empty arrays */ | ||
1165 | if (token.type == T_ARR_END) | ||
1166 | return; | ||
1167 | |||
1168 | for (i = 1; ; i++) { | ||
1169 | json_process_value(l, json, &token); | ||
1170 | lua_rawseti(l, -2, i); /* arr[i] = value */ | ||
1171 | |||
1172 | json_next_token(json, &token); | ||
1173 | |||
1174 | if (token.type == T_ARR_END) | ||
1175 | return; | ||
1176 | |||
1177 | if (token.type != T_COMMA) | ||
1178 | json_throw_parse_error(l, json, "comma or array end", &token); | ||
1179 | |||
1180 | json_next_token(json, &token); | ||
1181 | } | ||
1182 | } | ||
1183 | |||
1184 | /* Handle the "value" context */ | ||
1185 | static void json_process_value(lua_State *l, json_parse_t *json, | ||
1186 | json_token_t *token) | ||
1187 | { | ||
1188 | switch (token->type) { | ||
1189 | case T_STRING: | ||
1190 | lua_pushlstring(l, token->value.string, token->string_len); | ||
1191 | break;; | ||
1192 | case T_NUMBER: | ||
1193 | lua_pushnumber(l, token->value.number); | ||
1194 | break;; | ||
1195 | case T_BOOLEAN: | ||
1196 | lua_pushboolean(l, token->value.boolean); | ||
1197 | break;; | ||
1198 | case T_OBJ_BEGIN: | ||
1199 | json_parse_object_context(l, json); | ||
1200 | break;; | ||
1201 | case T_ARR_BEGIN: | ||
1202 | json_parse_array_context(l, json); | ||
1203 | break;; | ||
1204 | case T_NULL: | ||
1205 | /* In Lua, setting "t[k] = nil" will delete k from the table. | ||
1206 | * Hence a NULL pointer lightuserdata object is used instead */ | ||
1207 | lua_pushlightuserdata(l, NULL); | ||
1208 | break;; | ||
1209 | default: | ||
1210 | json_throw_parse_error(l, json, "value", token); | ||
1211 | } | ||
1212 | } | ||
1213 | |||
1214 | /* json_text must be null terminated string */ | ||
1215 | static void lua_json_decode(lua_State *l, const char *json_text, int json_len) | ||
1216 | { | ||
1217 | json_parse_t json; | ||
1218 | json_token_t token; | ||
1219 | |||
1220 | json.cfg = json_fetch_config(l); | ||
1221 | json.data = json_text; | ||
1222 | json.index = 0; | ||
1223 | |||
1224 | /* Ensure the temporary buffer can hold the entire string. | ||
1225 | * This means we no longer need to do length checks since the decoded | ||
1226 | * string must be smaller than the entire json string */ | ||
1227 | json.tmp = strbuf_new(json_len); | ||
1228 | |||
1229 | json_next_token(&json, &token); | ||
1230 | json_process_value(l, &json, &token); | ||
1231 | |||
1232 | /* Ensure there is no more input left */ | ||
1233 | json_next_token(&json, &token); | ||
1234 | |||
1235 | if (token.type != T_END) | ||
1236 | json_throw_parse_error(l, &json, "the end", &token); | ||
1237 | |||
1238 | strbuf_free(json.tmp); | ||
1239 | } | ||
1240 | |||
1241 | static int json_decode(lua_State *l) | ||
1242 | { | ||
1243 | const char *json; | ||
1244 | size_t len; | ||
1245 | |||
1246 | json_verify_arg_count(l, 1); | ||
1247 | |||
1248 | json = luaL_checklstring(l, 1, &len); | ||
1249 | |||
1250 | /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) | ||
1251 | * | ||
1252 | * CJSON can support any simple data type, hence only the first | ||
1253 | * character is guaranteed to be ASCII (at worst: '"'). This is | ||
1254 | * still enough to detect whether the wrong encoding is in use. */ | ||
1255 | if (len >= 2 && (!json[0] || !json[1])) | ||
1256 | luaL_error(l, "JSON parser does not support UTF-16 or UTF-32"); | ||
1257 | |||
1258 | lua_json_decode(l, json, len); | ||
1259 | |||
1260 | return 1; | ||
1261 | } | ||
1262 | |||
1263 | /* ===== INITIALISATION ===== */ | ||
1264 | |||
1265 | #if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502 | ||
1266 | /* Compatibility for Lua 5.1. | ||
1267 | * | ||
1268 | * luaL_setfuncs() is used to create a module table where the functions have | ||
1269 | * json_config_t as their first upvalue. Code borrowed from Lua 5.2 source. */ | ||
1270 | static void luaL_setfuncs (lua_State *l, const luaL_Reg *reg, int nup) | ||
1271 | { | ||
1272 | int i; | ||
1273 | |||
1274 | luaL_checkstack(l, nup, "too many upvalues"); | ||
1275 | for (; reg->name != NULL; reg++) { /* fill the table with given functions */ | ||
1276 | for (i = 0; i < nup; i++) /* copy upvalues to the top */ | ||
1277 | lua_pushvalue(l, -nup); | ||
1278 | lua_pushcclosure(l, reg->func, nup); /* closure with those upvalues */ | ||
1279 | lua_setfield(l, -(nup + 2), reg->name); | ||
1280 | } | ||
1281 | lua_pop(l, nup); /* remove upvalues */ | ||
1282 | } | ||
1283 | #endif | ||
1284 | |||
1285 | static int lua_cjson_new(lua_State *l) | ||
1286 | { | ||
1287 | luaL_Reg reg[] = { | ||
1288 | { "encode", json_encode }, | ||
1289 | { "decode", json_decode }, | ||
1290 | { "encode_sparse_array", json_cfg_encode_sparse_array }, | ||
1291 | { "encode_max_depth", json_cfg_encode_max_depth }, | ||
1292 | { "encode_number_precision", json_cfg_encode_number_precision }, | ||
1293 | { "encode_keep_buffer", json_cfg_encode_keep_buffer }, | ||
1294 | { "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers }, | ||
1295 | { "new", lua_cjson_new }, | ||
1296 | { NULL, NULL } | ||
1297 | }; | ||
1298 | |||
1299 | /* Initialise number conversions */ | ||
1300 | fpconv_init(); | ||
1301 | |||
1302 | /* cjson module table */ | ||
1303 | lua_newtable(l); | ||
1304 | |||
1305 | /* Register functions with config data as upvalue */ | ||
1306 | json_create_config(l); | ||
1307 | luaL_setfuncs(l, reg, 1); | ||
1308 | |||
1309 | /* Set cjson.null */ | ||
1310 | lua_pushlightuserdata(l, NULL); | ||
1311 | lua_setfield(l, -2, "null"); | ||
1312 | |||
1313 | /* Set module name / version fields */ | ||
1314 | lua_pushliteral(l, CJSON_MODNAME); | ||
1315 | lua_setfield(l, -2, "_NAME"); | ||
1316 | lua_pushliteral(l, CJSON_VERSION); | ||
1317 | lua_setfield(l, -2, "_VERSION"); | ||
1318 | lua_pushliteral(l, CJSON_VERSION); | ||
1319 | lua_setfield(l, -2, "version"); | ||
1320 | |||
1321 | return 1; | ||
1322 | } | ||
1323 | |||
1324 | int luaopen_cjson(lua_State *l) | ||
1325 | { | ||
1326 | lua_cjson_new(l); | ||
1327 | |||
1328 | #if !defined(DISABLE_CJSON_GLOBAL) && (!defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502) | ||
1329 | /* Register a global "cjson" table to maintain compatibility with earlier | ||
1330 | * versions of Lua CJSON which used luaL_register() under Lua 5.1. | ||
1331 | * | ||
1332 | * From Lua 5.2 onwards, Lua CJSON does not automatically register a global | ||
1333 | * table. | ||
1334 | */ | ||
1335 | lua_pushvalue(l, -1); | ||
1336 | lua_setglobal(l, CJSON_MODNAME); | ||
1337 | #endif | ||
1338 | |||
1339 | /* Return cjson table */ | ||
1340 | return 1; | ||
1341 | } | ||
1342 | |||
1343 | /* vi:ai et sw=4 ts=4: | ||
1344 | */ | ||
diff --git a/manual.txt b/manual.txt new file mode 100644 index 0000000..72dd196 --- /dev/null +++ b/manual.txt | |||
@@ -0,0 +1,497 @@ | |||
1 | = Lua CJSON 1.0devel Manual = | ||
2 | Mark Pulford <mark@kyne.com.au> | ||
3 | :revdate: November 30, 2011 | ||
4 | |||
5 | Overview | ||
6 | -------- | ||
7 | |||
8 | The Lua CJSON library provides JSON support for Lua. | ||
9 | |||
10 | .Features | ||
11 | - Fast, standards compliant encoding/parsing routines | ||
12 | - Full support for JSON with UTF-8, including decoding surrogate | ||
13 | pairs | ||
14 | - Optional run-time support for common exceptions to the JSON | ||
15 | specification (NaN, Infinity,..) | ||
16 | - No external dependencies | ||
17 | |||
18 | .Caveats | ||
19 | - UTF-16 and UTF-32 are not supported. | ||
20 | |||
21 | Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for | ||
22 | details. | ||
23 | |||
24 | The latest version of this software is available from the | ||
25 | http://www.kyne.com.au/~mark/software/lua-cjson.php[Lua CJSON website]. | ||
26 | |||
27 | Feel free to email me if you have any patches, suggestions, or | ||
28 | comments. | ||
29 | |||
30 | |||
31 | Installation Methods | ||
32 | -------------------- | ||
33 | |||
34 | Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, or | ||
35 | http://www.luajit.org[LuaJIT] to build. | ||
36 | |||
37 | There are 4 build methods available: | ||
38 | |||
39 | [horizontal] | ||
40 | Make:: Unix (including Linux, BSD, Mac OSX & Solaris) | ||
41 | CMake:: Unix, Windows | ||
42 | RPM:: Linux | ||
43 | LuaRocks:: Unix, Windows | ||
44 | |||
45 | |||
46 | Make | ||
47 | ~~~~ | ||
48 | |||
49 | Review and update the included Makefile to suit your platform. Next, | ||
50 | build and install the module: | ||
51 | |||
52 | [source,sh] | ||
53 | make install | ||
54 | |||
55 | Or install manually: | ||
56 | |||
57 | [source,sh] | ||
58 | make | ||
59 | cp cjson.so $your_lua_module_directory | ||
60 | |||
61 | |||
62 | CMake | ||
63 | ~~~~~ | ||
64 | |||
65 | http://www.cmake.org[CMake] can generate build configuration for many different | ||
66 | platforms (including Unix and Windows). | ||
67 | |||
68 | [source,sh] | ||
69 | mkdir build | ||
70 | cd build | ||
71 | cmake .. | ||
72 | make | ||
73 | cp cjson.so $your_lua_module_directory | ||
74 | |||
75 | Review the http://www.cmake.org/cmake/help/documentation.html[CMake documentation] | ||
76 | for further details. | ||
77 | |||
78 | |||
79 | RPM | ||
80 | ~~~ | ||
81 | |||
82 | Linux distributions using RPM should be able to create a package via | ||
83 | the included RPM spec file. Install the +rpm-build+ package (or | ||
84 | similar) then: | ||
85 | |||
86 | [source,sh] | ||
87 | rpmbuild -tb lua-cjson-1.0devel.tar.gz | ||
88 | rpm -Uvh $newly_built_lua_cjson_rpm | ||
89 | |||
90 | |||
91 | LuaRocks | ||
92 | ~~~~~~~~ | ||
93 | |||
94 | http://luarocks.org[LuaRocks] can be used to install and manage Lua | ||
95 | modules on a wide range of platforms (including Windows). | ||
96 | |||
97 | Extract the Lua CJSON source package into a directory and run: | ||
98 | |||
99 | [source,sh] | ||
100 | cd lua-cjson-1.0devel | ||
101 | luarocks make | ||
102 | |||
103 | [NOTE] | ||
104 | LuaRocks does not support platform specific configuration for Solaris. | ||
105 | On Solaris, you may need to manually uncomment +USE_INTERNAL_ISINF+ in | ||
106 | the rockspec before building this module. | ||
107 | |||
108 | Review the http://luarocks.org/en/Documentation[LuaRocks documentation] | ||
109 | for further details. | ||
110 | |||
111 | |||
112 | Build Options (#define) | ||
113 | ~~~~~~~~~~~~~~~~~~~~~~~ | ||
114 | |||
115 | [horizontal] | ||
116 | USE_INTERNAL_ISINF:: Workaround for Solaris platforms missing ++isinf++(3). | ||
117 | DISABLE_CJSON_GLOBAL:: Do not store module table in global "cjson" | ||
118 | variable. Redundant from Lua 5.2 onwards. | ||
119 | DISABLE_INVALID_NUMBERS:: Recommended on platforms where ++strtod++(3) / | ||
120 | ++sprintf++(3) are not POSIX compliant (Eg, Windows MinGW). Restricts | ||
121 | the +cjson.refuse_invalid_numbers+ runtime configuration to +true+. | ||
122 | |||
123 | |||
124 | API (Functions) | ||
125 | --------------- | ||
126 | |||
127 | Synopsis | ||
128 | ~~~~~~~~ | ||
129 | |||
130 | [source,lua] | ||
131 | ------------ | ||
132 | -- Module initalisation methods | ||
133 | local cjson = require "cjson" | ||
134 | local cjson2 = cjson.new() | ||
135 | |||
136 | -- Translate Lua value to/from JSON | ||
137 | text = cjson.encode(value) | ||
138 | value = cjson.decode(text) | ||
139 | |||
140 | -- Get and/or set Lua CJSON configuration | ||
141 | setting = cjson.refuse_invalid_numbers([setting]) | ||
142 | depth = cjson.encode_max_depth([depth]) | ||
143 | convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) | ||
144 | keep = cjson.encode_keep_buffer([keep]) | ||
145 | ------------ | ||
146 | |||
147 | |||
148 | Module Instantiation | ||
149 | ~~~~~~~~~~~~~~~~~~~~ | ||
150 | |||
151 | [source,lua] | ||
152 | ------------ | ||
153 | local cjson = require "cjson" | ||
154 | local cjson2 = cjson.new() | ||
155 | ------------ | ||
156 | |||
157 | Lua CJSON can be loaded via the Lua +require+ function. A global | ||
158 | +cjson+ module table is registered under Lua 5.1 to maintain backward | ||
159 | compatibility. Lua CJSON does not register a global table under Lua | ||
160 | 5.2 since this practice is discouraged. | ||
161 | |||
162 | +cjson.new+ can be used to instantiate an independent copy of the Lua | ||
163 | CJSON module. The new module has a separate persistent encoding | ||
164 | buffer, and default settings. | ||
165 | |||
166 | Lua CJSON can support Lua implementations using multiple pre-emptive | ||
167 | threads within a single Lua state provided the persistent encoding | ||
168 | buffer is not shared. This can be achieved by one of the following | ||
169 | methods: | ||
170 | |||
171 | - Disabling the persistent encoding buffer with | ||
172 | +cjson.encode_keep_buffer+ | ||
173 | - Ensuring each thread calls +cjson.encode+ at a time | ||
174 | - Using a separate +cjson+ module table per pre-emptive thread | ||
175 | (+cjson.new+) | ||
176 | |||
177 | [NOTE] | ||
178 | Lua CJSON uses ++strtod++(3) and ++snprintf++(3) to perform numeric | ||
179 | conversion as they are usually well supported, fast and bug free. | ||
180 | However, these functions require a workaround for JSON | ||
181 | encoding/parsing under locales using a comma decimal separator. Lua | ||
182 | CJSON detects the current locale during instantiation to determine | ||
183 | whether a workaround is required. CJSON should be reinitialised via | ||
184 | +cjson.new+ if the locale of the current process changes. Different | ||
185 | locales per thread are not supported. | ||
186 | |||
187 | |||
188 | decode | ||
189 | ~~~~~~ | ||
190 | |||
191 | [source,lua] | ||
192 | ------------ | ||
193 | value = cjson.decode(json_text) | ||
194 | ------------ | ||
195 | |||
196 | +cjson.decode+ will deserialise any UTF-8 JSON string into a Lua value | ||
197 | or table. It may return any of the types that +cjson.encode+ supports. | ||
198 | |||
199 | UTF-16 and UTF-32 JSON strings are not supported. | ||
200 | |||
201 | +cjson.decode+ requires that any NULL (ASCII 0) and double quote | ||
202 | (ASCII 34) characters are escaped within strings. All escape codes | ||
203 | will be decoded and other characters will be passed transparently. | ||
204 | UTF-8 characters are not validated during decoding and should be | ||
205 | checked elsewhere if required. | ||
206 | |||
207 | JSON +null+ will be converted to a NULL +lightuserdata+ value. This | ||
208 | can be compared with +cjson.null+ for convenience. | ||
209 | |||
210 | By default, numbers incompatible with the JSON specification (NaN, | ||
211 | Infinity, Hexadecimal) can be decoded. This default can be changed | ||
212 | with +cjson.refuse_invalid_numbers+. | ||
213 | |||
214 | .Example: Decoding | ||
215 | [source,lua] | ||
216 | json_text = '[ true, { "foo": "bar" } ]' | ||
217 | value = cjson.decode(json_text) | ||
218 | -- Returns: { true, { foo = "bar" } } | ||
219 | |||
220 | [CAUTION] | ||
221 | Care must be taken when after decoding JSON objects with numeric keys. Each | ||
222 | numeric key will be stored as a Lua +string+. Any code assuming type | ||
223 | +number+ may break. | ||
224 | |||
225 | |||
226 | encode | ||
227 | ~~~~~~ | ||
228 | |||
229 | [source,lua] | ||
230 | ------------ | ||
231 | json_text = cjson.encode(value) | ||
232 | ------------ | ||
233 | |||
234 | +cjson.encode+ will serialise a Lua value into a string containing the | ||
235 | JSON representation. | ||
236 | |||
237 | +cjson.encode+ supports the following types: | ||
238 | |||
239 | - +boolean+ | ||
240 | - +lightuserdata+ (NULL value only) | ||
241 | - +nil+ | ||
242 | - +number+ | ||
243 | - +string+ | ||
244 | - +table+ | ||
245 | |||
246 | The remaining Lua types will generate an error: | ||
247 | |||
248 | - +function+ | ||
249 | - +lightuserdata+ (non-NULL values) | ||
250 | - +thread+ | ||
251 | - +userdata+ | ||
252 | |||
253 | By default, numbers are encoded using the standard Lua number | ||
254 | +printf+(3) format (+%.14g+). | ||
255 | |||
256 | Lua CJSON will escape the following characters within each UTF-8 | ||
257 | string: | ||
258 | |||
259 | - Control characters (ASCII 0 - 31) | ||
260 | - Double quote (ASCII 34) | ||
261 | - Forward slash (ASCII 47) | ||
262 | - Blackslash (ASCII 92) | ||
263 | - Delete (ASCII 127) | ||
264 | |||
265 | All other characters are passed transparently. | ||
266 | |||
267 | [CAUTION] | ||
268 | ========= | ||
269 | Lua CJSON will successfully encode/decode binary strings, but this is | ||
270 | technically not supported by JSON and may not be compatible with other | ||
271 | JSON libraries. Applications should ensure all Lua strings passed to | ||
272 | +cjson.encode+ are valid UTF-8 to ensure the output is valid JSON. | ||
273 | |||
274 | Base64 is a common way to transport binary data through JSON. Lua | ||
275 | Base64 routines can be found in the | ||
276 | http://w3.impa.br/~diego/software/luasocket/[LuaSocket] and | ||
277 | http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lbase64[lbase64] packages. | ||
278 | ========= | ||
279 | |||
280 | Lua CJSON uses a heuristic to determine whether to encode a Lua table | ||
281 | as a JSON array or an object. A Lua table with only positive integers | ||
282 | keys of type +number+ will be encoded as a JSON array. All other | ||
283 | tables will be encoded as a JSON object. | ||
284 | |||
285 | Refer to <<encode_sparse_array,+cjson.encode_sparse_array+>> for details | ||
286 | on sparse array handling. | ||
287 | |||
288 | Lua CJSON does not use metamethods when serialising tables. | ||
289 | |||
290 | - +rawget+ is used to iterate over Lua arrays | ||
291 | - +next+ is used to iterate over Lua objects | ||
292 | |||
293 | JSON object keys are always strings. +cjson.encode+ can only handle | ||
294 | table keys which are type +number+ or +string+. All other types will | ||
295 | generate an error. | ||
296 | |||
297 | [NOTE] | ||
298 | Standards compliant JSON must be encapsulated in either an object | ||
299 | (+{}+) or an array (+[]+). Hence a table must be passed to | ||
300 | +cjson.encode+ to generate standards compliant JSON output. | ||
301 | This may not be required by some applications. | ||
302 | |||
303 | By default, the following Lua values will generate errors: | ||
304 | |||
305 | - Numbers incompatible with the JSON specification (NaN, Infinity, Hexadecimal) | ||
306 | - Tables nested more than 20 levels deep | ||
307 | - Excessively sparse Lua arrays | ||
308 | |||
309 | These defaults can be changed with: | ||
310 | |||
311 | - <<encode_max_depth,+cjson.encode_max_depth+>> | ||
312 | - <<encode_sparse_array,+cjson.encode_sparse_array+>> | ||
313 | - <<refuse_invalid_numbers,+cjson.refuse_invalid_numbers+>> | ||
314 | |||
315 | .Example: Encoding | ||
316 | [source,lua] | ||
317 | value = { true, { foo = "bar" } } | ||
318 | json_text = cjson.encode(value) | ||
319 | -- Returns: '[true,{"foo":"bar"}]' | ||
320 | |||
321 | |||
322 | encode_keep_buffer | ||
323 | ~~~~~~~~~~~~~~~~~~ | ||
324 | |||
325 | [source,lua] | ||
326 | ------------ | ||
327 | keep = cjson.encode_keep_buffer([keep]) | ||
328 | -- "keep" must be a boolean | ||
329 | ------------ | ||
330 | |||
331 | By default, Lua CJSON will reuse the JSON encoding buffer to improve | ||
332 | performance. The buffer will grow to the largest size required and is | ||
333 | not freed until the Lua CJSON module is garbage collected. Setting this | ||
334 | option to +false+ will cause the buffer to be freed after each call to | ||
335 | +cjson.encode+. | ||
336 | |||
337 | This setting is only changed when an argument is provided. The current | ||
338 | setting is always returned. | ||
339 | |||
340 | |||
341 | [[encode_max_depth]] | ||
342 | encode_max_depth | ||
343 | ~~~~~~~~~~~~~~~~ | ||
344 | |||
345 | [source,lua] | ||
346 | ------------ | ||
347 | depth = cjson.encode_max_depth([depth]) | ||
348 | -- "depth" must be a positive integer | ||
349 | ------------ | ||
350 | |||
351 | By default, Lua CJSON will reject data structure with more than 20 nested | ||
352 | tables. | ||
353 | |||
354 | This setting is only changed when an argument is provided. The current | ||
355 | setting is always returned. | ||
356 | |||
357 | This check prevents a deeply nested or recursive data structure from | ||
358 | crashing the application. | ||
359 | |||
360 | .Example: Recursive Lua tables | ||
361 | [source,lua] | ||
362 | a = {}; b = { a }; a[1] = b | ||
363 | |||
364 | Once the maximum table depth has been reached Lua CJSON will throw an error. | ||
365 | |||
366 | |||
367 | encode_number_precision | ||
368 | ~~~~~~~~~~~~~~~~~~~~~~~ | ||
369 | |||
370 | [source,lua] | ||
371 | ------------ | ||
372 | precision = cjson.encode_number_precision([precision]) | ||
373 | -- "precision" must be between 1 and 14 (inclusive) | ||
374 | ------------ | ||
375 | |||
376 | By default, Lua CJSON will output 14 significant digits when | ||
377 | converting a number to text. | ||
378 | |||
379 | This setting is only changed when an argument is provided. The current | ||
380 | setting is always returned. | ||
381 | |||
382 | Reducing number precision to _3_ can improve performance of number | ||
383 | heavy JSON conversions by up to 50%. | ||
384 | |||
385 | |||
386 | [[encode_sparse_array]] | ||
387 | encode_sparse_array | ||
388 | ~~~~~~~~~~~~~~~~~~~ | ||
389 | |||
390 | [source,lua] | ||
391 | ------------ | ||
392 | convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) | ||
393 | -- "convert" must be a boolean. Default: false. | ||
394 | -- "ratio" must be a positive integer. Default: 2 | ||
395 | -- "safe" must be a positive integer. Default: 10 | ||
396 | ------------ | ||
397 | |||
398 | Lua CJSON classifies Lua tables into 3 types when encoding as a JSON | ||
399 | array. This is determined by the number of values missing for keys up | ||
400 | to the maximum index: | ||
401 | |||
402 | Each particular setting is only changed when that argument is | ||
403 | provided. The current settings are always returned. | ||
404 | |||
405 | [horizontal] | ||
406 | Normal:: All values are available. | ||
407 | Sparse:: At least 1 value is missing. | ||
408 | Excessively sparse:: The number of values missing exceed the configured ratio. | ||
409 | |||
410 | Lua CJSON encodes sparse Lua arrays by as JSON arrays using JSON +null+. | ||
411 | |||
412 | .Example: Encoding a sparse array | ||
413 | [source,lua] | ||
414 | cjson.encode({ [3] = "data" }) | ||
415 | -- Returns: '[null,null,"data"]' | ||
416 | |||
417 | Lua CJSON checks for excessively sparse arrays when the maximum index | ||
418 | is greater an the +safe+ limit and +ratio+ is greater than +0+. An | ||
419 | array is _excessively sparse_ when: | ||
420 | |||
421 | _maximum_index_ > _item_count_ * +ratio+ | ||
422 | |||
423 | By default, attempting to encode excessively sparse arrays will | ||
424 | generate an error. If +convert+ is set to +true+, excessively sparse | ||
425 | arrays will be converted to a JSON object: | ||
426 | |||
427 | .Example: Enabling conversion to a JSON object | ||
428 | [source,lua] | ||
429 | cjson.encode_sparse_array(true) | ||
430 | cjson.encode({ [1000] = "excessively sparse" }) | ||
431 | -- Returns: '{"1000":"excessively sparse"}' | ||
432 | |||
433 | |||
434 | [[refuse_invalid_numbers]] | ||
435 | refuse_invalid_numbers | ||
436 | ~~~~~~~~~~~~~~~~~~~~~~ | ||
437 | |||
438 | [source,lua] | ||
439 | ------------ | ||
440 | setting = cjson.refuse_invalid_numbers([setting]) | ||
441 | -- "setting" must be on of: | ||
442 | -- false, "encode", "decode", "both", true | ||
443 | ------------ | ||
444 | |||
445 | Lua CJSON can throw an error for numbers outside of the JSON | ||
446 | specification (_invalid numbers_): | ||
447 | |||
448 | - Infinity | ||
449 | - NaN | ||
450 | - Hexadecimal | ||
451 | |||
452 | By default Lua CJSON will decode _invalid numbers_, but will refuse to | ||
453 | encode them. | ||
454 | |||
455 | This setting is only changed when an argument is provided. The current | ||
456 | setting is always returned. | ||
457 | |||
458 | This setting can be configured separately for encoding and/or | ||
459 | decoding: | ||
460 | |||
461 | [horizontal] | ||
462 | Enabled:: An error will be generated if an _invalid number_ is found. | ||
463 | Disabled for encoding:: NaN and Infinity can be encoded. | ||
464 | Disabled for decoding:: All numbers supported by +strtod+(3) will be parsed. | ||
465 | |||
466 | |||
467 | API (Variables) | ||
468 | --------------- | ||
469 | |||
470 | _NAME | ||
471 | ~~~~~ | ||
472 | |||
473 | The name of the Lua CJSON module (+"cjson"+). | ||
474 | |||
475 | |||
476 | _VERSION | ||
477 | ~~~~~~~~ | ||
478 | |||
479 | The version number of the Lua CJSON module (Eg, +"1.0devel"+). | ||
480 | |||
481 | |||
482 | null | ||
483 | ~~~~ | ||
484 | |||
485 | Lua CJSON decodes JSON +null+ as a Lua +lightuserdata+ NULL pointer. | ||
486 | +cjson.null+ is provided for comparison. | ||
487 | |||
488 | |||
489 | [sect1] | ||
490 | References | ||
491 | ---------- | ||
492 | |||
493 | - http://tools.ietf.org/html/rfc4627[RFC 4627] | ||
494 | - http://www.json.org/[JSON website] | ||
495 | |||
496 | |||
497 | // vi:ft=asciidoc tw=70: | ||
diff --git a/performance.txt b/performance.txt new file mode 100644 index 0000000..51c61ec --- /dev/null +++ b/performance.txt | |||
@@ -0,0 +1,51 @@ | |||
1 | JSON Performance Comparison under Lua | ||
2 | ------------------------------------- | ||
3 | |||
4 | The following JSON packages for Lua were tested: | ||
5 | |||
6 | - DKJSON 1.0: One of the fastest pure Lua JSON implementations. | ||
7 | - LuaJSON 1.0: A mixed Lua/C JSON implementation using LPeg. | ||
8 | - Lua YAJL 2.0: A Lua wrapper for the YAJL JSON library. | ||
9 | - CSJON 1.0.2: Pure C. | ||
10 | |||
11 | LuaJSON 1.2.2 appeared to be slower during initial testing, so 1.0 was | ||
12 | used instead. | ||
13 | |||
14 | The following Lua implementations were used for this comparison: | ||
15 | - Lua 5.1.4 | ||
16 | - LuaJIT 2.0.0-beta7 | ||
17 | |||
18 | The example JSON files used were taken from http://json.org/ and | ||
19 | RFC 4627. | ||
20 | |||
21 | DKJSON 1.0 LuaJSON 1.0 LuaYAJL 2.0 CJSON 1.0.2 | ||
22 | == Decoding == Lua LuaJIT Lua LuaJIT Lua LuaJIT Lua LuaJIT | ||
23 | example1 1.0x 2.0x 3.4x 4.0x 7.1x 10.1x 13.2x 19.4x | ||
24 | example2 1.0x 2.1x 3.5x 4.5x 6.6x 9.8x 12.7x 20.0x | ||
25 | example3 1.0x 2.0x 3.9x 4.7x 7.0x 9.4x 13.2x 19.3x | ||
26 | example4 1.0x 1.9x 3.7x 4.4x 7.4x 10.6x 11.8x 18.0x | ||
27 | example5 1.0x 2.1x 4.0x 4.7x 7.7x 11.4x 14.7x 22.3x | ||
28 | numbers 1.0x 2.1x 2.1x 3.4x 4.6x 5.7x 8.6x 10.4x | ||
29 | rfc-example1 1.0x 2.0x 3.2x 4.2x 5.8x 8.2x 11.8x 17.7x | ||
30 | rfc-example2 1.0x 2.0x 3.6x 4.5x 7.0x 9.3x 14.5x 20.5x | ||
31 | types 1.0x 2.1x 2.3x 3.5x 4.9x 7.6x 10.7x 17.2x | ||
32 | == Average ==> 1.0x 2.0x 3.3x 4.2x 6.4x 9.1x 12.4x 18.3x | ||
33 | |||
34 | == Encoding == | ||
35 | example1 1.0x 1.9x 0.6x 1.4x 3.5x 5.6x 23.1x 29.1x | ||
36 | example2 1.0x 2.0x 0.5x 1.2x 3.0x 4.9x 23.4x 28.5x | ||
37 | example3 1.0x 1.8x 0.6x 1.3x 3.0x 4.7x 13.3x 14.9x | ||
38 | example4 1.0x 1.7x 0.7x 1.5x 4.2x 6.6x 15.4x 18.5x | ||
39 | example5 1.0x 2.0x 0.6x 1.4x 3.4x 5.5x 22.7x 25.5x | ||
40 | numbers 1.0x 2.4x 0.4x 0.9x 1.4x 2.1x 4.3x 4.6x | ||
41 | rfc-example1 1.0x 1.9x 0.5x 1.2x 2.3x 3.6x 8.8x 9.6x | ||
42 | rfc-example2 1.0x 1.9x 0.6x 1.3x 2.8x 4.3x 10.7x 10.7x | ||
43 | types 1.0x 2.4x 0.3x 0.7x 1.4x 2.3x 11.7x 11.3x | ||
44 | == Average ==> 1.0x 2.0x 0.6x 1.2x 2.8x 4.4x 14.8x 17.0x | ||
45 | |||
46 | |||
47 | Number conversion is a relatively expensive operation. Number heavy | ||
48 | JSON will show less performance difference between libraries. | ||
49 | |||
50 | Performance can vary widely between platforms and data sets. These | ||
51 | results should only be considered as a rough guide. | ||
diff --git a/rfc4627.txt b/rfc4627.txt new file mode 100644 index 0000000..67b8909 --- /dev/null +++ b/rfc4627.txt | |||
@@ -0,0 +1,563 @@ | |||
1 | |||
2 | |||
3 | |||
4 | |||
5 | |||
6 | |||
7 | Network Working Group D. Crockford | ||
8 | Request for Comments: 4627 JSON.org | ||
9 | Category: Informational July 2006 | ||
10 | |||
11 | |||
12 | The application/json Media Type for JavaScript Object Notation (JSON) | ||
13 | |||
14 | Status of This Memo | ||
15 | |||
16 | This memo provides information for the Internet community. It does | ||
17 | not specify an Internet standard of any kind. Distribution of this | ||
18 | memo is unlimited. | ||
19 | |||
20 | Copyright Notice | ||
21 | |||
22 | Copyright (C) The Internet Society (2006). | ||
23 | |||
24 | Abstract | ||
25 | |||
26 | JavaScript Object Notation (JSON) is a lightweight, text-based, | ||
27 | language-independent data interchange format. It was derived from | ||
28 | the ECMAScript Programming Language Standard. JSON defines a small | ||
29 | set of formatting rules for the portable representation of structured | ||
30 | data. | ||
31 | |||
32 | 1. Introduction | ||
33 | |||
34 | JavaScript Object Notation (JSON) is a text format for the | ||
35 | serialization of structured data. It is derived from the object | ||
36 | literals of JavaScript, as defined in the ECMAScript Programming | ||
37 | Language Standard, Third Edition [ECMA]. | ||
38 | |||
39 | JSON can represent four primitive types (strings, numbers, booleans, | ||
40 | and null) and two structured types (objects and arrays). | ||
41 | |||
42 | A string is a sequence of zero or more Unicode characters [UNICODE]. | ||
43 | |||
44 | An object is an unordered collection of zero or more name/value | ||
45 | pairs, where a name is a string and a value is a string, number, | ||
46 | boolean, null, object, or array. | ||
47 | |||
48 | An array is an ordered sequence of zero or more values. | ||
49 | |||
50 | The terms "object" and "array" come from the conventions of | ||
51 | JavaScript. | ||
52 | |||
53 | JSON's design goals were for it to be minimal, portable, textual, and | ||
54 | a subset of JavaScript. | ||
55 | |||
56 | |||
57 | |||
58 | Crockford Informational [Page 1] | ||
59 | |||
60 | RFC 4627 JSON July 2006 | ||
61 | |||
62 | |||
63 | 1.1. Conventions Used in This Document | ||
64 | |||
65 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", | ||
66 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this | ||
67 | document are to be interpreted as described in [RFC2119]. | ||
68 | |||
69 | The grammatical rules in this document are to be interpreted as | ||
70 | described in [RFC4234]. | ||
71 | |||
72 | 2. JSON Grammar | ||
73 | |||
74 | A JSON text is a sequence of tokens. The set of tokens includes six | ||
75 | structural characters, strings, numbers, and three literal names. | ||
76 | |||
77 | A JSON text is a serialized object or array. | ||
78 | |||
79 | JSON-text = object / array | ||
80 | |||
81 | These are the six structural characters: | ||
82 | |||
83 | begin-array = ws %x5B ws ; [ left square bracket | ||
84 | |||
85 | begin-object = ws %x7B ws ; { left curly bracket | ||
86 | |||
87 | end-array = ws %x5D ws ; ] right square bracket | ||
88 | |||
89 | end-object = ws %x7D ws ; } right curly bracket | ||
90 | |||
91 | name-separator = ws %x3A ws ; : colon | ||
92 | |||
93 | value-separator = ws %x2C ws ; , comma | ||
94 | |||
95 | Insignificant whitespace is allowed before or after any of the six | ||
96 | structural characters. | ||
97 | |||
98 | ws = *( | ||
99 | %x20 / ; Space | ||
100 | %x09 / ; Horizontal tab | ||
101 | %x0A / ; Line feed or New line | ||
102 | %x0D ; Carriage return | ||
103 | ) | ||
104 | |||
105 | 2.1. Values | ||
106 | |||
107 | A JSON value MUST be an object, array, number, or string, or one of | ||
108 | the following three literal names: | ||
109 | |||
110 | false null true | ||
111 | |||
112 | |||
113 | |||
114 | Crockford Informational [Page 2] | ||
115 | |||
116 | RFC 4627 JSON July 2006 | ||
117 | |||
118 | |||
119 | The literal names MUST be lowercase. No other literal names are | ||
120 | allowed. | ||
121 | |||
122 | value = false / null / true / object / array / number / string | ||
123 | |||
124 | false = %x66.61.6c.73.65 ; false | ||
125 | |||
126 | null = %x6e.75.6c.6c ; null | ||
127 | |||
128 | true = %x74.72.75.65 ; true | ||
129 | |||
130 | 2.2. Objects | ||
131 | |||
132 | An object structure is represented as a pair of curly brackets | ||
133 | surrounding zero or more name/value pairs (or members). A name is a | ||
134 | string. A single colon comes after each name, separating the name | ||
135 | from the value. A single comma separates a value from a following | ||
136 | name. The names within an object SHOULD be unique. | ||
137 | |||
138 | object = begin-object [ member *( value-separator member ) ] | ||
139 | end-object | ||
140 | |||
141 | member = string name-separator value | ||
142 | |||
143 | 2.3. Arrays | ||
144 | |||
145 | An array structure is represented as square brackets surrounding zero | ||
146 | or more values (or elements). Elements are separated by commas. | ||
147 | |||
148 | array = begin-array [ value *( value-separator value ) ] end-array | ||
149 | |||
150 | 2.4. Numbers | ||
151 | |||
152 | The representation of numbers is similar to that used in most | ||
153 | programming languages. A number contains an integer component that | ||
154 | may be prefixed with an optional minus sign, which may be followed by | ||
155 | a fraction part and/or an exponent part. | ||
156 | |||
157 | Octal and hex forms are not allowed. Leading zeros are not allowed. | ||
158 | |||
159 | A fraction part is a decimal point followed by one or more digits. | ||
160 | |||
161 | An exponent part begins with the letter E in upper or lowercase, | ||
162 | which may be followed by a plus or minus sign. The E and optional | ||
163 | sign are followed by one or more digits. | ||
164 | |||
165 | Numeric values that cannot be represented as sequences of digits | ||
166 | (such as Infinity and NaN) are not permitted. | ||
167 | |||
168 | |||
169 | |||
170 | Crockford Informational [Page 3] | ||
171 | |||
172 | RFC 4627 JSON July 2006 | ||
173 | |||
174 | |||
175 | number = [ minus ] int [ frac ] [ exp ] | ||
176 | |||
177 | decimal-point = %x2E ; . | ||
178 | |||
179 | digit1-9 = %x31-39 ; 1-9 | ||
180 | |||
181 | e = %x65 / %x45 ; e E | ||
182 | |||
183 | exp = e [ minus / plus ] 1*DIGIT | ||
184 | |||
185 | frac = decimal-point 1*DIGIT | ||
186 | |||
187 | int = zero / ( digit1-9 *DIGIT ) | ||
188 | |||
189 | minus = %x2D ; - | ||
190 | |||
191 | plus = %x2B ; + | ||
192 | |||
193 | zero = %x30 ; 0 | ||
194 | |||
195 | 2.5. Strings | ||
196 | |||
197 | The representation of strings is similar to conventions used in the C | ||
198 | family of programming languages. A string begins and ends with | ||
199 | quotation marks. All Unicode characters may be placed within the | ||
200 | quotation marks except for the characters that must be escaped: | ||
201 | quotation mark, reverse solidus, and the control characters (U+0000 | ||
202 | through U+001F). | ||
203 | |||
204 | Any character may be escaped. If the character is in the Basic | ||
205 | Multilingual Plane (U+0000 through U+FFFF), then it may be | ||
206 | represented as a six-character sequence: a reverse solidus, followed | ||
207 | by the lowercase letter u, followed by four hexadecimal digits that | ||
208 | encode the character's code point. The hexadecimal letters A though | ||
209 | F can be upper or lowercase. So, for example, a string containing | ||
210 | only a single reverse solidus character may be represented as | ||
211 | "\u005C". | ||
212 | |||
213 | Alternatively, there are two-character sequence escape | ||
214 | representations of some popular characters. So, for example, a | ||
215 | string containing only a single reverse solidus character may be | ||
216 | represented more compactly as "\\". | ||
217 | |||
218 | To escape an extended character that is not in the Basic Multilingual | ||
219 | Plane, the character is represented as a twelve-character sequence, | ||
220 | encoding the UTF-16 surrogate pair. So, for example, a string | ||
221 | containing only the G clef character (U+1D11E) may be represented as | ||
222 | "\uD834\uDD1E". | ||
223 | |||
224 | |||
225 | |||
226 | Crockford Informational [Page 4] | ||
227 | |||
228 | RFC 4627 JSON July 2006 | ||
229 | |||
230 | |||
231 | string = quotation-mark *char quotation-mark | ||
232 | |||
233 | char = unescaped / | ||
234 | escape ( | ||
235 | %x22 / ; " quotation mark U+0022 | ||
236 | %x5C / ; \ reverse solidus U+005C | ||
237 | %x2F / ; / solidus U+002F | ||
238 | %x62 / ; b backspace U+0008 | ||
239 | %x66 / ; f form feed U+000C | ||
240 | %x6E / ; n line feed U+000A | ||
241 | %x72 / ; r carriage return U+000D | ||
242 | %x74 / ; t tab U+0009 | ||
243 | %x75 4HEXDIG ) ; uXXXX U+XXXX | ||
244 | |||
245 | escape = %x5C ; \ | ||
246 | |||
247 | quotation-mark = %x22 ; " | ||
248 | |||
249 | unescaped = %x20-21 / %x23-5B / %x5D-10FFFF | ||
250 | |||
251 | 3. Encoding | ||
252 | |||
253 | JSON text SHALL be encoded in Unicode. The default encoding is | ||
254 | UTF-8. | ||
255 | |||
256 | Since the first two characters of a JSON text will always be ASCII | ||
257 | characters [RFC0020], it is possible to determine whether an octet | ||
258 | stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking | ||
259 | at the pattern of nulls in the first four octets. | ||
260 | |||
261 | 00 00 00 xx UTF-32BE | ||
262 | 00 xx 00 xx UTF-16BE | ||
263 | xx 00 00 00 UTF-32LE | ||
264 | xx 00 xx 00 UTF-16LE | ||
265 | xx xx xx xx UTF-8 | ||
266 | |||
267 | 4. Parsers | ||
268 | |||
269 | A JSON parser transforms a JSON text into another representation. A | ||
270 | JSON parser MUST accept all texts that conform to the JSON grammar. | ||
271 | A JSON parser MAY accept non-JSON forms or extensions. | ||
272 | |||
273 | An implementation may set limits on the size of texts that it | ||
274 | accepts. An implementation may set limits on the maximum depth of | ||
275 | nesting. An implementation may set limits on the range of numbers. | ||
276 | An implementation may set limits on the length and character contents | ||
277 | of strings. | ||
278 | |||
279 | |||
280 | |||
281 | |||
282 | Crockford Informational [Page 5] | ||
283 | |||
284 | RFC 4627 JSON July 2006 | ||
285 | |||
286 | |||
287 | 5. Generators | ||
288 | |||
289 | A JSON generator produces JSON text. The resulting text MUST | ||
290 | strictly conform to the JSON grammar. | ||
291 | |||
292 | 6. IANA Considerations | ||
293 | |||
294 | The MIME media type for JSON text is application/json. | ||
295 | |||
296 | Type name: application | ||
297 | |||
298 | Subtype name: json | ||
299 | |||
300 | Required parameters: n/a | ||
301 | |||
302 | Optional parameters: n/a | ||
303 | |||
304 | Encoding considerations: 8bit if UTF-8; binary if UTF-16 or UTF-32 | ||
305 | |||
306 | JSON may be represented using UTF-8, UTF-16, or UTF-32. When JSON | ||
307 | is written in UTF-8, JSON is 8bit compatible. When JSON is | ||
308 | written in UTF-16 or UTF-32, the binary content-transfer-encoding | ||
309 | must be used. | ||
310 | |||
311 | Security considerations: | ||
312 | |||
313 | Generally there are security issues with scripting languages. JSON | ||
314 | is a subset of JavaScript, but it is a safe subset that excludes | ||
315 | assignment and invocation. | ||
316 | |||
317 | A JSON text can be safely passed into JavaScript's eval() function | ||
318 | (which compiles and executes a string) if all the characters not | ||
319 | enclosed in strings are in the set of characters that form JSON | ||
320 | tokens. This can be quickly determined in JavaScript with two | ||
321 | regular expressions and calls to the test and replace methods. | ||
322 | |||
323 | var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test( | ||
324 | text.replace(/"(\\.|[^"\\])*"/g, ''))) && | ||
325 | eval('(' + text + ')'); | ||
326 | |||
327 | Interoperability considerations: n/a | ||
328 | |||
329 | Published specification: RFC 4627 | ||
330 | |||
331 | |||
332 | |||
333 | |||
334 | |||
335 | |||
336 | |||
337 | |||
338 | Crockford Informational [Page 6] | ||
339 | |||
340 | RFC 4627 JSON July 2006 | ||
341 | |||
342 | |||
343 | Applications that use this media type: | ||
344 | |||
345 | JSON has been used to exchange data between applications written | ||
346 | in all of these programming languages: ActionScript, C, C#, | ||
347 | ColdFusion, Common Lisp, E, Erlang, Java, JavaScript, Lua, | ||
348 | Objective CAML, Perl, PHP, Python, Rebol, Ruby, and Scheme. | ||
349 | |||
350 | Additional information: | ||
351 | |||
352 | Magic number(s): n/a | ||
353 | File extension(s): .json | ||
354 | Macintosh file type code(s): TEXT | ||
355 | |||
356 | Person & email address to contact for further information: | ||
357 | Douglas Crockford | ||
358 | douglas@crockford.com | ||
359 | |||
360 | Intended usage: COMMON | ||
361 | |||
362 | Restrictions on usage: none | ||
363 | |||
364 | Author: | ||
365 | Douglas Crockford | ||
366 | douglas@crockford.com | ||
367 | |||
368 | Change controller: | ||
369 | Douglas Crockford | ||
370 | douglas@crockford.com | ||
371 | |||
372 | 7. Security Considerations | ||
373 | |||
374 | See Security Considerations in Section 6. | ||
375 | |||
376 | 8. Examples | ||
377 | |||
378 | This is a JSON object: | ||
379 | |||
380 | { | ||
381 | "Image": { | ||
382 | "Width": 800, | ||
383 | "Height": 600, | ||
384 | "Title": "View from 15th Floor", | ||
385 | "Thumbnail": { | ||
386 | "Url": "http://www.example.com/image/481989943", | ||
387 | "Height": 125, | ||
388 | "Width": "100" | ||
389 | }, | ||
390 | "IDs": [116, 943, 234, 38793] | ||
391 | |||
392 | |||
393 | |||
394 | Crockford Informational [Page 7] | ||
395 | |||
396 | RFC 4627 JSON July 2006 | ||
397 | |||
398 | |||
399 | } | ||
400 | } | ||
401 | |||
402 | Its Image member is an object whose Thumbnail member is an object | ||
403 | and whose IDs member is an array of numbers. | ||
404 | |||
405 | This is a JSON array containing two objects: | ||
406 | |||
407 | [ | ||
408 | { | ||
409 | "precision": "zip", | ||
410 | "Latitude": 37.7668, | ||
411 | "Longitude": -122.3959, | ||
412 | "Address": "", | ||
413 | "City": "SAN FRANCISCO", | ||
414 | "State": "CA", | ||
415 | "Zip": "94107", | ||
416 | "Country": "US" | ||
417 | }, | ||
418 | { | ||
419 | "precision": "zip", | ||
420 | "Latitude": 37.371991, | ||
421 | "Longitude": -122.026020, | ||
422 | "Address": "", | ||
423 | "City": "SUNNYVALE", | ||
424 | "State": "CA", | ||
425 | "Zip": "94085", | ||
426 | "Country": "US" | ||
427 | } | ||
428 | ] | ||
429 | |||
430 | 9. References | ||
431 | |||
432 | 9.1. Normative References | ||
433 | |||
434 | [ECMA] European Computer Manufacturers Association, "ECMAScript | ||
435 | Language Specification 3rd Edition", December 1999, | ||
436 | <http://www.ecma-international.org/publications/files/ | ||
437 | ecma-st/ECMA-262.pdf>. | ||
438 | |||
439 | [RFC0020] Cerf, V., "ASCII format for network interchange", RFC 20, | ||
440 | October 1969. | ||
441 | |||
442 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate | ||
443 | Requirement Levels", BCP 14, RFC 2119, March 1997. | ||
444 | |||
445 | [RFC4234] Crocker, D. and P. Overell, "Augmented BNF for Syntax | ||
446 | Specifications: ABNF", RFC 4234, October 2005. | ||
447 | |||
448 | |||
449 | |||
450 | Crockford Informational [Page 8] | ||
451 | |||
452 | RFC 4627 JSON July 2006 | ||
453 | |||
454 | |||
455 | [UNICODE] The Unicode Consortium, "The Unicode Standard Version 4.0", | ||
456 | 2003, <http://www.unicode.org/versions/Unicode4.1.0/>. | ||
457 | |||
458 | Author's Address | ||
459 | |||
460 | Douglas Crockford | ||
461 | JSON.org | ||
462 | EMail: douglas@crockford.com | ||
463 | |||
464 | |||
465 | |||
466 | |||
467 | |||
468 | |||
469 | |||
470 | |||
471 | |||
472 | |||
473 | |||
474 | |||
475 | |||
476 | |||
477 | |||
478 | |||
479 | |||
480 | |||
481 | |||
482 | |||
483 | |||
484 | |||
485 | |||
486 | |||
487 | |||
488 | |||
489 | |||
490 | |||
491 | |||
492 | |||
493 | |||
494 | |||
495 | |||
496 | |||
497 | |||
498 | |||
499 | |||
500 | |||
501 | |||
502 | |||
503 | |||
504 | |||
505 | |||
506 | Crockford Informational [Page 9] | ||
507 | |||
508 | RFC 4627 JSON July 2006 | ||
509 | |||
510 | |||
511 | Full Copyright Statement | ||
512 | |||
513 | Copyright (C) The Internet Society (2006). | ||
514 | |||
515 | This document is subject to the rights, licenses and restrictions | ||
516 | contained in BCP 78, and except as set forth therein, the authors | ||
517 | retain all their rights. | ||
518 | |||
519 | This document and the information contained herein are provided on an | ||
520 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS | ||
521 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET | ||
522 | ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, | ||
523 | INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE | ||
524 | INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED | ||
525 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. | ||
526 | |||
527 | Intellectual Property | ||
528 | |||
529 | The IETF takes no position regarding the validity or scope of any | ||
530 | Intellectual Property Rights or other rights that might be claimed to | ||
531 | pertain to the implementation or use of the technology described in | ||
532 | this document or the extent to which any license under such rights | ||
533 | might or might not be available; nor does it represent that it has | ||
534 | made any independent effort to identify any such rights. Information | ||
535 | on the procedures with respect to rights in RFC documents can be | ||
536 | found in BCP 78 and BCP 79. | ||
537 | |||
538 | Copies of IPR disclosures made to the IETF Secretariat and any | ||
539 | assurances of licenses to be made available, or the result of an | ||
540 | attempt made to obtain a general license or permission for the use of | ||
541 | such proprietary rights by implementers or users of this | ||
542 | specification can be obtained from the IETF on-line IPR repository at | ||
543 | http://www.ietf.org/ipr. | ||
544 | |||
545 | The IETF invites any interested party to bring to its attention any | ||
546 | copyrights, patents or patent applications, or other proprietary | ||
547 | rights that may cover technology that may be required to implement | ||
548 | this standard. Please address the information to the IETF at | ||
549 | ietf-ipr@ietf.org. | ||
550 | |||
551 | Acknowledgement | ||
552 | |||
553 | Funding for the RFC Editor function is provided by the IETF | ||
554 | Administrative Support Activity (IASA). | ||
555 | |||
556 | |||
557 | |||
558 | |||
559 | |||
560 | |||
561 | |||
562 | Crockford Informational [Page 10] | ||
563 | |||
diff --git a/runtests.sh b/runtests.sh new file mode 100755 index 0000000..d313db3 --- /dev/null +++ b/runtests.sh | |||
@@ -0,0 +1,91 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | PLATFORM="`uname -s`" | ||
4 | [ "$1" ] && VERSION="$1" || VERSION="1.0devel" | ||
5 | |||
6 | set -e | ||
7 | |||
8 | # Portable "ggrep -A" replacement | ||
9 | # contextgrep PATTERN POST_MATCH_LINES | ||
10 | contextgrep() { | ||
11 | awk "/$1/ { count = ($2 + 1) } count { count--; print }" | ||
12 | } | ||
13 | |||
14 | do_tests() { | ||
15 | echo | ||
16 | cd tests | ||
17 | lua -e 'require "cjson"; print("Testing Lua CJSON version " .. cjson.version)' | ||
18 | ./test.lua | contextgrep 'FAIL|Summary' 3 | grep -v PASS | cut -c -70 | ||
19 | cd .. | ||
20 | } | ||
21 | |||
22 | echo "===== Setting LuaRocks PATH =====" | ||
23 | eval "`luarocks path`" | ||
24 | |||
25 | echo "===== Building UTF-8 test data =====" | ||
26 | ( cd tests && ./genutf8.pl; ) | ||
27 | |||
28 | echo "===== Cleaning old build data =====" | ||
29 | make clean | ||
30 | rm -f tests/cjson.so | ||
31 | |||
32 | echo "===== Verifying cjson.so is not installed =====" | ||
33 | |||
34 | cd tests | ||
35 | if lua -e 'require "cjson"' 2>/dev/null | ||
36 | then | ||
37 | cat <<EOT | ||
38 | Please ensure you do not have the Lua CJSON module installed before | ||
39 | running these tests. | ||
40 | EOT | ||
41 | exit | ||
42 | fi | ||
43 | cd .. | ||
44 | |||
45 | echo "===== Testing LuaRocks build =====" | ||
46 | luarocks make --local | ||
47 | do_tests | ||
48 | luarocks remove --local lua-cjson | ||
49 | make clean | ||
50 | |||
51 | echo "===== Testing Makefile build =====" | ||
52 | make | ||
53 | cp cjson.so tests | ||
54 | do_tests | ||
55 | make clean | ||
56 | rm -f tests/cjson.so | ||
57 | |||
58 | echo "===== Testing Cmake build =====" | ||
59 | mkdir build | ||
60 | cd build | ||
61 | cmake .. | ||
62 | make | ||
63 | cd .. | ||
64 | cp build/cjson.so tests | ||
65 | do_tests | ||
66 | rm -rf build tests/cjson.so | ||
67 | |||
68 | if [ "$PLATFORM" = "Linux" ] | ||
69 | then | ||
70 | echo "===== Testing RPM build =====" | ||
71 | SRCTGZ="" | ||
72 | TGZ=lua-cjson-$VERSION.tar.gz | ||
73 | for D in .. packages . | ||
74 | do | ||
75 | [ -r "$D/$TGZ" ] && SRCTGZ="$D/$TGZ" | ||
76 | done | ||
77 | if [ "$SRCTGZ" ] | ||
78 | then | ||
79 | LOG=/tmp/build.$$ | ||
80 | rpmbuild -tb "$SRCTGZ" > "$LOG" | ||
81 | RPM="`awk '/^Wrote: / && ! /debuginfo/ { print $2}' < "$LOG"`" | ||
82 | sudo -- rpm -Uvh \"$RPM\" | ||
83 | do_tests | ||
84 | sudo -- rpm -e lua-cjson | ||
85 | rm -f "$LOG" | ||
86 | else | ||
87 | echo "==> skipping, $TGZ not found" | ||
88 | fi | ||
89 | fi | ||
90 | |||
91 | # vi:ai et sw=4 ts=4: | ||
diff --git a/strbuf.c b/strbuf.c new file mode 100644 index 0000000..ab00f0a --- /dev/null +++ b/strbuf.c | |||
@@ -0,0 +1,251 @@ | |||
1 | /* strbuf - string buffer routines | ||
2 | * | ||
3 | * Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au> | ||
4 | * | ||
5 | * Permission is hereby granted, free of charge, to any person obtaining | ||
6 | * a copy of this software and associated documentation files (the | ||
7 | * "Software"), to deal in the Software without restriction, including | ||
8 | * without limitation the rights to use, copy, modify, merge, publish, | ||
9 | * distribute, sublicense, and/or sell copies of the Software, and to | ||
10 | * permit persons to whom the Software is furnished to do so, subject to | ||
11 | * the following conditions: | ||
12 | * | ||
13 | * The above copyright notice and this permission notice shall be | ||
14 | * included in all copies or substantial portions of the Software. | ||
15 | * | ||
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
23 | */ | ||
24 | |||
25 | #include <stdio.h> | ||
26 | #include <stdlib.h> | ||
27 | #include <stdarg.h> | ||
28 | #include <string.h> | ||
29 | |||
30 | #include "strbuf.h" | ||
31 | |||
32 | static void die(const char *fmt, ...) | ||
33 | { | ||
34 | va_list arg; | ||
35 | |||
36 | va_start(arg, fmt); | ||
37 | vfprintf(stderr, fmt, arg); | ||
38 | va_end(arg); | ||
39 | fprintf(stderr, "\n"); | ||
40 | |||
41 | exit(-1); | ||
42 | } | ||
43 | |||
44 | void strbuf_init(strbuf_t *s, int len) | ||
45 | { | ||
46 | int size; | ||
47 | |||
48 | if (len <= 0) | ||
49 | size = STRBUF_DEFAULT_SIZE; | ||
50 | else | ||
51 | size = len + 1; /* \0 terminator */ | ||
52 | |||
53 | s->buf = NULL; | ||
54 | s->size = size; | ||
55 | s->length = 0; | ||
56 | s->increment = STRBUF_DEFAULT_INCREMENT; | ||
57 | s->dynamic = 0; | ||
58 | s->reallocs = 0; | ||
59 | s->debug = 0; | ||
60 | |||
61 | s->buf = malloc(size); | ||
62 | if (!s->buf) | ||
63 | die("Out of memory"); | ||
64 | |||
65 | strbuf_ensure_null(s); | ||
66 | } | ||
67 | |||
68 | strbuf_t *strbuf_new(int len) | ||
69 | { | ||
70 | strbuf_t *s; | ||
71 | |||
72 | s = malloc(sizeof(strbuf_t)); | ||
73 | if (!s) | ||
74 | die("Out of memory"); | ||
75 | |||
76 | strbuf_init(s, len); | ||
77 | |||
78 | /* Dynamic strbuf allocation / deallocation */ | ||
79 | s->dynamic = 1; | ||
80 | |||
81 | return s; | ||
82 | } | ||
83 | |||
84 | void strbuf_set_increment(strbuf_t *s, int increment) | ||
85 | { | ||
86 | /* Increment > 0: Linear buffer growth rate | ||
87 | * Increment < -1: Exponential buffer growth rate */ | ||
88 | if (increment == 0 || increment == -1) | ||
89 | die("BUG: Invalid string increment"); | ||
90 | |||
91 | s->increment = increment; | ||
92 | } | ||
93 | |||
94 | static inline void debug_stats(strbuf_t *s) | ||
95 | { | ||
96 | if (s->debug) { | ||
97 | fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n", | ||
98 | (long)s, s->reallocs, s->length, s->size); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | /* If strbuf_t has not been dynamically allocated, strbuf_free() can | ||
103 | * be called any number of times strbuf_init() */ | ||
104 | void strbuf_free(strbuf_t *s) | ||
105 | { | ||
106 | debug_stats(s); | ||
107 | |||
108 | if (s->buf) { | ||
109 | free(s->buf); | ||
110 | s->buf = NULL; | ||
111 | } | ||
112 | if (s->dynamic) | ||
113 | free(s); | ||
114 | } | ||
115 | |||
116 | char *strbuf_free_to_string(strbuf_t *s, int *len) | ||
117 | { | ||
118 | char *buf; | ||
119 | |||
120 | debug_stats(s); | ||
121 | |||
122 | strbuf_ensure_null(s); | ||
123 | |||
124 | buf = s->buf; | ||
125 | if (len) | ||
126 | *len = s->length; | ||
127 | |||
128 | if (s->dynamic) | ||
129 | free(s); | ||
130 | |||
131 | return buf; | ||
132 | } | ||
133 | |||
134 | static int calculate_new_size(strbuf_t *s, int len) | ||
135 | { | ||
136 | int reqsize, newsize; | ||
137 | |||
138 | if (len <= 0) | ||
139 | die("BUG: Invalid strbuf length requested"); | ||
140 | |||
141 | /* Ensure there is room for optional NULL termination */ | ||
142 | reqsize = len + 1; | ||
143 | |||
144 | /* If the user has requested to shrink the buffer, do it exactly */ | ||
145 | if (s->size > reqsize) | ||
146 | return reqsize; | ||
147 | |||
148 | newsize = s->size; | ||
149 | if (s->increment < 0) { | ||
150 | /* Exponential sizing */ | ||
151 | while (newsize < reqsize) | ||
152 | newsize *= -s->increment; | ||
153 | } else { | ||
154 | /* Linear sizing */ | ||
155 | newsize = ((newsize + s->increment - 1) / s->increment) * s->increment; | ||
156 | } | ||
157 | |||
158 | return newsize; | ||
159 | } | ||
160 | |||
161 | |||
162 | /* Ensure strbuf can handle a string length bytes long (ignoring NULL | ||
163 | * optional termination). */ | ||
164 | void strbuf_resize(strbuf_t *s, int len) | ||
165 | { | ||
166 | int newsize; | ||
167 | |||
168 | newsize = calculate_new_size(s, len); | ||
169 | |||
170 | if (s->debug > 1) { | ||
171 | fprintf(stderr, "strbuf(%lx) resize: %d => %d\n", | ||
172 | (long)s, s->size, newsize); | ||
173 | } | ||
174 | |||
175 | s->size = newsize; | ||
176 | s->buf = realloc(s->buf, s->size); | ||
177 | if (!s->buf) | ||
178 | die("Out of memory"); | ||
179 | s->reallocs++; | ||
180 | } | ||
181 | |||
182 | void strbuf_append_string(strbuf_t *s, const char *str) | ||
183 | { | ||
184 | int space, i; | ||
185 | |||
186 | space = strbuf_empty_length(s); | ||
187 | |||
188 | for (i = 0; str[i]; i++) { | ||
189 | if (space < 1) { | ||
190 | strbuf_resize(s, s->length + 1); | ||
191 | space = strbuf_empty_length(s); | ||
192 | } | ||
193 | |||
194 | s->buf[s->length] = str[i]; | ||
195 | s->length++; | ||
196 | space--; | ||
197 | } | ||
198 | } | ||
199 | |||
200 | /* strbuf_append_fmt() should only be used when an upper bound | ||
201 | * is known for the output string. */ | ||
202 | void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...) | ||
203 | { | ||
204 | va_list arg; | ||
205 | int fmt_len; | ||
206 | |||
207 | strbuf_ensure_empty_length(s, len); | ||
208 | |||
209 | va_start(arg, fmt); | ||
210 | fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg); | ||
211 | va_end(arg); | ||
212 | |||
213 | if (fmt_len < 0) | ||
214 | die("BUG: Unable to convert number"); /* This should never happen.. */ | ||
215 | |||
216 | s->length += fmt_len; | ||
217 | } | ||
218 | |||
219 | /* strbuf_append_fmt_retry() can be used when the there is no known | ||
220 | * upper bound for the output string. */ | ||
221 | void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...) | ||
222 | { | ||
223 | va_list arg; | ||
224 | int fmt_len, try; | ||
225 | int empty_len; | ||
226 | |||
227 | /* If the first attempt to append fails, resize the buffer appropriately | ||
228 | * and try again */ | ||
229 | for (try = 0; ; try++) { | ||
230 | va_start(arg, fmt); | ||
231 | /* Append the new formatted string */ | ||
232 | /* fmt_len is the length of the string required, excluding the | ||
233 | * trailing NULL */ | ||
234 | empty_len = strbuf_empty_length(s); | ||
235 | /* Add 1 since there is also space to store the terminating NULL. */ | ||
236 | fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg); | ||
237 | va_end(arg); | ||
238 | |||
239 | if (fmt_len <= empty_len) | ||
240 | break; /* SUCCESS */ | ||
241 | if (try > 0) | ||
242 | die("BUG: length of formatted string changed"); | ||
243 | |||
244 | strbuf_resize(s, s->length + fmt_len); | ||
245 | } | ||
246 | |||
247 | s->length += fmt_len; | ||
248 | } | ||
249 | |||
250 | /* vi:ai et sw=4 ts=4: | ||
251 | */ | ||
diff --git a/strbuf.h b/strbuf.h new file mode 100644 index 0000000..fbc8651 --- /dev/null +++ b/strbuf.h | |||
@@ -0,0 +1,154 @@ | |||
1 | /* strbuf - String buffer routines | ||
2 | * | ||
3 | * Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au> | ||
4 | * | ||
5 | * Permission is hereby granted, free of charge, to any person obtaining | ||
6 | * a copy of this software and associated documentation files (the | ||
7 | * "Software"), to deal in the Software without restriction, including | ||
8 | * without limitation the rights to use, copy, modify, merge, publish, | ||
9 | * distribute, sublicense, and/or sell copies of the Software, and to | ||
10 | * permit persons to whom the Software is furnished to do so, subject to | ||
11 | * the following conditions: | ||
12 | * | ||
13 | * The above copyright notice and this permission notice shall be | ||
14 | * included in all copies or substantial portions of the Software. | ||
15 | * | ||
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
23 | */ | ||
24 | |||
25 | #include <stdlib.h> | ||
26 | #include <stdarg.h> | ||
27 | |||
28 | /* Size: Total bytes allocated to *buf | ||
29 | * Length: String length, excluding optional NULL terminator. | ||
30 | * Increment: Allocation increments when resizing the string buffer. | ||
31 | * Dynamic: True if created via strbuf_new() | ||
32 | */ | ||
33 | |||
34 | typedef struct { | ||
35 | char *buf; | ||
36 | int size; | ||
37 | int length; | ||
38 | int increment; | ||
39 | int dynamic; | ||
40 | int reallocs; | ||
41 | int debug; | ||
42 | } strbuf_t; | ||
43 | |||
44 | #ifndef STRBUF_DEFAULT_SIZE | ||
45 | #define STRBUF_DEFAULT_SIZE 1023 | ||
46 | #endif | ||
47 | #ifndef STRBUF_DEFAULT_INCREMENT | ||
48 | #define STRBUF_DEFAULT_INCREMENT -2 | ||
49 | #endif | ||
50 | |||
51 | /* Initialise */ | ||
52 | extern strbuf_t *strbuf_new(int len); | ||
53 | extern void strbuf_init(strbuf_t *s, int len); | ||
54 | extern void strbuf_set_increment(strbuf_t *s, int increment); | ||
55 | |||
56 | /* Release */ | ||
57 | extern void strbuf_free(strbuf_t *s); | ||
58 | extern char *strbuf_free_to_string(strbuf_t *s, int *len); | ||
59 | |||
60 | /* Management */ | ||
61 | extern void strbuf_resize(strbuf_t *s, int len); | ||
62 | static int strbuf_empty_length(strbuf_t *s); | ||
63 | static int strbuf_length(strbuf_t *s); | ||
64 | static char *strbuf_string(strbuf_t *s, int *len); | ||
65 | static void strbuf_ensure_empty_length(strbuf_t *s, int len); | ||
66 | static char *strbuf_empty_ptr(strbuf_t *s); | ||
67 | static void strbuf_extend_length(strbuf_t *s, int len); | ||
68 | |||
69 | /* Update */ | ||
70 | extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); | ||
71 | extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...); | ||
72 | static void strbuf_append_mem(strbuf_t *s, const char *c, int len); | ||
73 | extern void strbuf_append_string(strbuf_t *s, const char *str); | ||
74 | static void strbuf_append_char(strbuf_t *s, const char c); | ||
75 | static void strbuf_ensure_null(strbuf_t *s); | ||
76 | |||
77 | /* Reset string for before use */ | ||
78 | static inline void strbuf_reset(strbuf_t *s) | ||
79 | { | ||
80 | s->length = 0; | ||
81 | } | ||
82 | |||
83 | static inline int strbuf_allocated(strbuf_t *s) | ||
84 | { | ||
85 | return s->buf != NULL; | ||
86 | } | ||
87 | |||
88 | /* Return bytes remaining in the string buffer | ||
89 | * Ensure there is space for a NULL terminator. */ | ||
90 | static inline int strbuf_empty_length(strbuf_t *s) | ||
91 | { | ||
92 | return s->size - s->length - 1; | ||
93 | } | ||
94 | |||
95 | static inline void strbuf_ensure_empty_length(strbuf_t *s, int len) | ||
96 | { | ||
97 | if (len > strbuf_empty_length(s)) | ||
98 | strbuf_resize(s, s->length + len); | ||
99 | } | ||
100 | |||
101 | static inline char *strbuf_empty_ptr(strbuf_t *s) | ||
102 | { | ||
103 | return s->buf + s->length; | ||
104 | } | ||
105 | |||
106 | static inline void strbuf_extend_length(strbuf_t *s, int len) | ||
107 | { | ||
108 | s->length += len; | ||
109 | } | ||
110 | |||
111 | static inline int strbuf_length(strbuf_t *s) | ||
112 | { | ||
113 | return s->length; | ||
114 | } | ||
115 | |||
116 | static inline void strbuf_append_char(strbuf_t *s, const char c) | ||
117 | { | ||
118 | strbuf_ensure_empty_length(s, 1); | ||
119 | s->buf[s->length++] = c; | ||
120 | } | ||
121 | |||
122 | static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c) | ||
123 | { | ||
124 | s->buf[s->length++] = c; | ||
125 | } | ||
126 | |||
127 | static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len) | ||
128 | { | ||
129 | strbuf_ensure_empty_length(s, len); | ||
130 | memcpy(s->buf + s->length, c, len); | ||
131 | s->length += len; | ||
132 | } | ||
133 | |||
134 | static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len) | ||
135 | { | ||
136 | memcpy(s->buf + s->length, c, len); | ||
137 | s->length += len; | ||
138 | } | ||
139 | |||
140 | static inline void strbuf_ensure_null(strbuf_t *s) | ||
141 | { | ||
142 | s->buf[s->length] = 0; | ||
143 | } | ||
144 | |||
145 | static inline char *strbuf_string(strbuf_t *s, int *len) | ||
146 | { | ||
147 | if (len) | ||
148 | *len = s->length; | ||
149 | |||
150 | return s->buf; | ||
151 | } | ||
152 | |||
153 | /* vi:ai et sw=4 ts=4: | ||
154 | */ | ||
diff --git a/tests/README b/tests/README new file mode 100644 index 0000000..39e8bd4 --- /dev/null +++ b/tests/README | |||
@@ -0,0 +1,4 @@ | |||
1 | These JSON examples were taken from the JSON website | ||
2 | (http://json.org/example.html) and RFC 4627. | ||
3 | |||
4 | Used with permission. | ||
diff --git a/tests/bench.lua b/tests/bench.lua new file mode 100755 index 0000000..2b5177b --- /dev/null +++ b/tests/bench.lua | |||
@@ -0,0 +1,83 @@ | |||
1 | #!/usr/bin/env lua | ||
2 | |||
3 | -- Simple JSON benchmark. | ||
4 | -- | ||
5 | -- Your Mileage May Vary. | ||
6 | -- | ||
7 | -- Mark Pulford <mark@kyne.com.au> | ||
8 | |||
9 | require "socket" | ||
10 | local json = require "cjson" | ||
11 | local misc = require "cjson-misc" | ||
12 | |||
13 | function benchmark(tests, seconds, rep) | ||
14 | local function bench(func, iter) | ||
15 | -- collectgarbage("stop") | ||
16 | collectgarbage("collect") | ||
17 | local t = socket.gettime() | ||
18 | for i = 1, iter do | ||
19 | func(i) | ||
20 | end | ||
21 | t = socket.gettime() - t | ||
22 | -- collectgarbage("restart") | ||
23 | return (iter / t) | ||
24 | end | ||
25 | |||
26 | -- Roughly calculate the number of interations required | ||
27 | -- to obtain a particular time period. | ||
28 | local function calc_iter(func, seconds) | ||
29 | local base_iter = 10 | ||
30 | local rate = (bench(func, base_iter) + bench(func, base_iter)) / 2 | ||
31 | return math.ceil(seconds * rate) | ||
32 | end | ||
33 | |||
34 | local test_results = {} | ||
35 | for name, func in pairs(tests) do | ||
36 | -- k(number), v(string) | ||
37 | -- k(string), v(function) | ||
38 | -- k(number), v(function) | ||
39 | if type(func) == "string" then | ||
40 | name = func | ||
41 | func = _G[name] | ||
42 | end | ||
43 | local iter = calc_iter(func, seconds) | ||
44 | local result = {} | ||
45 | for i = 1, rep do | ||
46 | result[i] = bench(func, iter) | ||
47 | end | ||
48 | table.sort(result) | ||
49 | test_results[name] = result[rep] | ||
50 | end | ||
51 | |||
52 | return test_results | ||
53 | end | ||
54 | |||
55 | function bench_file(filename) | ||
56 | local data_json = misc.file_load(filename) | ||
57 | local data_obj = json.decode(data_json) | ||
58 | |||
59 | local function test_encode () | ||
60 | json.encode(data_obj) | ||
61 | end | ||
62 | local function test_decode () | ||
63 | json.decode(data_json) | ||
64 | end | ||
65 | |||
66 | local tests = { | ||
67 | encode = test_encode, | ||
68 | decode = test_decode | ||
69 | } | ||
70 | |||
71 | return benchmark(tests, 0.1, 5) | ||
72 | end | ||
73 | |||
74 | json.encode_keep_buffer(true) | ||
75 | |||
76 | for i = 1, #arg do | ||
77 | local results = bench_file(arg[i]) | ||
78 | for k, v in pairs(results) do | ||
79 | print(string.format("%s: %s: %d", arg[i], k, v)) | ||
80 | end | ||
81 | end | ||
82 | |||
83 | -- vi:ai et sw=4 ts=4: | ||
diff --git a/tests/cjson-misc.lua b/tests/cjson-misc.lua new file mode 100644 index 0000000..d8bfe5e --- /dev/null +++ b/tests/cjson-misc.lua | |||
@@ -0,0 +1,253 @@ | |||
1 | local json = require "cjson" | ||
2 | |||
3 | -- Misc routines to assist with CJSON testing | ||
4 | -- | ||
5 | -- Mark Pulford <mark@kyne.com.au> | ||
6 | |||
7 | -- Determine with a Lua table can be treated as an array. | ||
8 | -- Explicitly returns "not an array" for very sparse arrays. | ||
9 | -- Returns: | ||
10 | -- -1 Not an array | ||
11 | -- 0 Empty table | ||
12 | -- >0 Highest index in the array | ||
13 | local function is_array(table) | ||
14 | local max = 0 | ||
15 | local count = 0 | ||
16 | for k, v in pairs(table) do | ||
17 | if type(k) == "number" then | ||
18 | if k > max then max = k end | ||
19 | count = count + 1 | ||
20 | else | ||
21 | return -1 | ||
22 | end | ||
23 | end | ||
24 | if max > count * 2 then | ||
25 | return -1 | ||
26 | end | ||
27 | |||
28 | return max | ||
29 | end | ||
30 | |||
31 | local serialise_value | ||
32 | |||
33 | local function serialise_table(value, indent, depth) | ||
34 | local spacing, spacing2, indent2 | ||
35 | if indent then | ||
36 | spacing = "\n" .. indent | ||
37 | spacing2 = spacing .. " " | ||
38 | indent2 = indent .. " " | ||
39 | else | ||
40 | spacing, spacing2, indent2 = " ", " ", false | ||
41 | end | ||
42 | depth = depth + 1 | ||
43 | if depth > 50 then | ||
44 | return "Cannot serialise any further: too many nested tables" | ||
45 | end | ||
46 | |||
47 | local max = is_array(value) | ||
48 | |||
49 | local comma = false | ||
50 | local fragment = { "{" .. spacing2 } | ||
51 | if max > 0 then | ||
52 | -- Serialise array | ||
53 | for i = 1, max do | ||
54 | if comma then | ||
55 | table.insert(fragment, "," .. spacing2) | ||
56 | end | ||
57 | table.insert(fragment, serialise_value(value[i], indent2, depth)) | ||
58 | comma = true | ||
59 | end | ||
60 | elseif max < 0 then | ||
61 | -- Serialise table | ||
62 | for k, v in pairs(value) do | ||
63 | if comma then | ||
64 | table.insert(fragment, "," .. spacing2) | ||
65 | end | ||
66 | table.insert(fragment, string.format( | ||
67 | "[%s] = %s", serialise_value(k, indent2, depth), | ||
68 | serialise_value(v, indent2, depth)) | ||
69 | ) | ||
70 | comma = true | ||
71 | end | ||
72 | end | ||
73 | table.insert(fragment, spacing .. "}") | ||
74 | |||
75 | return table.concat(fragment) | ||
76 | end | ||
77 | |||
78 | function serialise_value(value, indent, depth) | ||
79 | if indent == nil then indent = "" end | ||
80 | if depth == nil then depth = 0 end | ||
81 | |||
82 | if value == json.null then | ||
83 | return "json.null" | ||
84 | elseif type(value) == "string" then | ||
85 | return string.format("%q", value) | ||
86 | elseif type(value) == "nil" or type(value) == "number" or | ||
87 | type(value) == "boolean" then | ||
88 | return tostring(value) | ||
89 | elseif type(value) == "table" then | ||
90 | return serialise_table(value, indent, depth) | ||
91 | else | ||
92 | return "\"<" .. type(value) .. ">\"" | ||
93 | end | ||
94 | end | ||
95 | |||
96 | local function file_load(filename) | ||
97 | local file | ||
98 | if filename == nil then | ||
99 | file = io.stdin | ||
100 | else | ||
101 | local err | ||
102 | file, err = io.open(filename) | ||
103 | if file == nil then | ||
104 | error(string.format("Unable to read '%s': %s", filename, err)) | ||
105 | end | ||
106 | end | ||
107 | local data = file:read("*a") | ||
108 | |||
109 | if filename ~= nil then | ||
110 | file:close() | ||
111 | end | ||
112 | |||
113 | if data == nil then | ||
114 | error("Failed to read " .. filename) | ||
115 | end | ||
116 | |||
117 | return data | ||
118 | end | ||
119 | |||
120 | local function file_save(filename, data) | ||
121 | local file | ||
122 | if filename == nil then | ||
123 | file = io.stdout | ||
124 | else | ||
125 | local err | ||
126 | file, err = io.open(filename, "w") | ||
127 | if file == nil then | ||
128 | error(string.format("Unable to write '%s': %s", filename, err)) | ||
129 | end | ||
130 | end | ||
131 | file:write(data) | ||
132 | if filename ~= nil then | ||
133 | file:close() | ||
134 | end | ||
135 | end | ||
136 | |||
137 | local function compare_values(val1, val2) | ||
138 | local type1 = type(val1) | ||
139 | local type2 = type(val2) | ||
140 | if type1 ~= type2 then | ||
141 | return false | ||
142 | end | ||
143 | |||
144 | -- Check for NaN | ||
145 | if type1 == "number" and val1 ~= val1 and val2 ~= val2 then | ||
146 | return true | ||
147 | end | ||
148 | |||
149 | if type1 ~= "table" then | ||
150 | return val1 == val2 | ||
151 | end | ||
152 | |||
153 | -- check_keys stores all the keys that must be checked in val2 | ||
154 | local check_keys = {} | ||
155 | for k, _ in pairs(val1) do | ||
156 | check_keys[k] = true | ||
157 | end | ||
158 | |||
159 | for k, v in pairs(val2) do | ||
160 | if not check_keys[k] then | ||
161 | return false | ||
162 | end | ||
163 | |||
164 | if not compare_values(val1[k], val2[k]) then | ||
165 | return false | ||
166 | end | ||
167 | |||
168 | check_keys[k] = nil | ||
169 | end | ||
170 | for k, _ in pairs(check_keys) do | ||
171 | -- Not the same if any keys from val1 were not found in val2 | ||
172 | return false | ||
173 | end | ||
174 | return true | ||
175 | end | ||
176 | |||
177 | local test_count_pass = 0 | ||
178 | local test_count_total = 0 | ||
179 | |||
180 | local function run_test_summary() | ||
181 | return test_count_pass, test_count_total | ||
182 | end | ||
183 | |||
184 | local function run_test(testname, func, input, should_work, output) | ||
185 | local function status_line(name, status, value) | ||
186 | local statusmap = { [true] = ":success", [false] = ":error" } | ||
187 | if status ~= nil then | ||
188 | name = name .. statusmap[status] | ||
189 | end | ||
190 | print(string.format("[%s] %s", name, serialise_value(value, false))) | ||
191 | end | ||
192 | |||
193 | local result = { pcall(func, unpack(input)) } | ||
194 | local success = table.remove(result, 1) | ||
195 | |||
196 | local correct = false | ||
197 | if success == should_work and compare_values(result, output) then | ||
198 | correct = true | ||
199 | test_count_pass = test_count_pass + 1 | ||
200 | end | ||
201 | test_count_total = test_count_total + 1 | ||
202 | |||
203 | local teststatus = { [true] = "PASS", [false] = "FAIL" } | ||
204 | print(string.format("==> Test[%d] / %s: %s", | ||
205 | test_count_total, testname, teststatus[correct])) | ||
206 | |||
207 | status_line("Input", nil, input) | ||
208 | if not correct then | ||
209 | status_line("Expected", should_work, output) | ||
210 | end | ||
211 | status_line("Received", success, result) | ||
212 | print() | ||
213 | |||
214 | return correct, result | ||
215 | end | ||
216 | |||
217 | local function run_test_group(testgroup, tests) | ||
218 | local function run_config(configname, func) | ||
219 | local success, msg = pcall(func) | ||
220 | if msg then | ||
221 | print(string.format("==> Config %s: %s", configname, msg)) | ||
222 | end | ||
223 | print() | ||
224 | end | ||
225 | |||
226 | local function test_id(group, id) | ||
227 | return string.format("%s[%d]", group, id) | ||
228 | end | ||
229 | |||
230 | for k, v in ipairs(tests) do | ||
231 | if type(v) == "function" then | ||
232 | -- Useful for changing configuration during a batch | ||
233 | run_config(test_id(testgroup, k), v) | ||
234 | elseif type(v) == "table" then | ||
235 | run_test(test_id(testgroup, k), unpack(v)) | ||
236 | else | ||
237 | error("Testgroup can only contain functions and tables") | ||
238 | end | ||
239 | end | ||
240 | end | ||
241 | |||
242 | -- Export functions | ||
243 | return { | ||
244 | serialise_value = serialise_value, | ||
245 | file_load = file_load, | ||
246 | file_save = file_save, | ||
247 | compare_values = compare_values, | ||
248 | run_test_summary = run_test_summary, | ||
249 | run_test = run_test, | ||
250 | run_test_group = run_test_group | ||
251 | } | ||
252 | |||
253 | -- vi:ai et sw=4 ts=4: | ||
diff --git a/tests/example1.json b/tests/example1.json new file mode 100644 index 0000000..42486ce --- /dev/null +++ b/tests/example1.json | |||
@@ -0,0 +1,22 @@ | |||
1 | { | ||
2 | "glossary": { | ||
3 | "title": "example glossary", | ||
4 | "GlossDiv": { | ||
5 | "title": "S", | ||
6 | "GlossList": { | ||
7 | "GlossEntry": { | ||
8 | "ID": "SGML", | ||
9 | "SortAs": "SGML", | ||
10 | "GlossTerm": "Standard Generalized Mark up Language", | ||
11 | "Acronym": "SGML", | ||
12 | "Abbrev": "ISO 8879:1986", | ||
13 | "GlossDef": { | ||
14 | "para": "A meta-markup language, used to create markup languages such as DocBook.", | ||
15 | "GlossSeeAlso": ["GML", "XML"] | ||
16 | }, | ||
17 | "GlossSee": "markup" | ||
18 | } | ||
19 | } | ||
20 | } | ||
21 | } | ||
22 | } | ||
diff --git a/tests/example2.json b/tests/example2.json new file mode 100644 index 0000000..5600991 --- /dev/null +++ b/tests/example2.json | |||
@@ -0,0 +1,11 @@ | |||
1 | {"menu": { | ||
2 | "id": "file", | ||
3 | "value": "File", | ||
4 | "popup": { | ||
5 | "menuitem": [ | ||
6 | {"value": "New", "onclick": "CreateNewDoc()"}, | ||
7 | {"value": "Open", "onclick": "OpenDoc()"}, | ||
8 | {"value": "Close", "onclick": "CloseDoc()"} | ||
9 | ] | ||
10 | } | ||
11 | }} | ||
diff --git a/tests/example3.json b/tests/example3.json new file mode 100644 index 0000000..d7237a5 --- /dev/null +++ b/tests/example3.json | |||
@@ -0,0 +1,26 @@ | |||
1 | {"widget": { | ||
2 | "debug": "on", | ||
3 | "window": { | ||
4 | "title": "Sample Konfabulator Widget", | ||
5 | "name": "main_window", | ||
6 | "width": 500, | ||
7 | "height": 500 | ||
8 | }, | ||
9 | "image": { | ||
10 | "src": "Images/Sun.png", | ||
11 | "name": "sun1", | ||
12 | "hOffset": 250, | ||
13 | "vOffset": 250, | ||
14 | "alignment": "center" | ||
15 | }, | ||
16 | "text": { | ||
17 | "data": "Click Here", | ||
18 | "size": 36, | ||
19 | "style": "bold", | ||
20 | "name": "text1", | ||
21 | "hOffset": 250, | ||
22 | "vOffset": 100, | ||
23 | "alignment": "center", | ||
24 | "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" | ||
25 | } | ||
26 | }} | ||
diff --git a/tests/example4.json b/tests/example4.json new file mode 100644 index 0000000..d31a395 --- /dev/null +++ b/tests/example4.json | |||
@@ -0,0 +1,88 @@ | |||
1 | {"web-app": { | ||
2 | "servlet": [ | ||
3 | { | ||
4 | "servlet-name": "cofaxCDS", | ||
5 | "servlet-class": "org.cofax.cds.CDSServlet", | ||
6 | "init-param": { | ||
7 | "configGlossary:installationAt": "Philadelphia, PA", | ||
8 | "configGlossary:adminEmail": "ksm@pobox.com", | ||
9 | "configGlossary:poweredBy": "Cofax", | ||
10 | "configGlossary:poweredByIcon": "/images/cofax.gif", | ||
11 | "configGlossary:staticPath": "/content/static", | ||
12 | "templateProcessorClass": "org.cofax.WysiwygTemplate", | ||
13 | "templateLoaderClass": "org.cofax.FilesTemplateLoader", | ||
14 | "templatePath": "templates", | ||
15 | "templateOverridePath": "", | ||
16 | "defaultListTemplate": "listTemplate.htm", | ||
17 | "defaultFileTemplate": "articleTemplate.htm", | ||
18 | "useJSP": false, | ||
19 | "jspListTemplate": "listTemplate.jsp", | ||
20 | "jspFileTemplate": "articleTemplate.jsp", | ||
21 | "cachePackageTagsTrack": 200, | ||
22 | "cachePackageTagsStore": 200, | ||
23 | "cachePackageTagsRefresh": 60, | ||
24 | "cacheTemplatesTrack": 100, | ||
25 | "cacheTemplatesStore": 50, | ||
26 | "cacheTemplatesRefresh": 15, | ||
27 | "cachePagesTrack": 200, | ||
28 | "cachePagesStore": 100, | ||
29 | "cachePagesRefresh": 10, | ||
30 | "cachePagesDirtyRead": 10, | ||
31 | "searchEngineListTemplate": "forSearchEnginesList.htm", | ||
32 | "searchEngineFileTemplate": "forSearchEngines.htm", | ||
33 | "searchEngineRobotsDb": "WEB-INF/robots.db", | ||
34 | "useDataStore": true, | ||
35 | "dataStoreClass": "org.cofax.SqlDataStore", | ||
36 | "redirectionClass": "org.cofax.SqlRedirection", | ||
37 | "dataStoreName": "cofax", | ||
38 | "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", | ||
39 | "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", | ||
40 | "dataStoreUser": "sa", | ||
41 | "dataStorePassword": "dataStoreTestQuery", | ||
42 | "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", | ||
43 | "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", | ||
44 | "dataStoreInitConns": 10, | ||
45 | "dataStoreMaxConns": 100, | ||
46 | "dataStoreConnUsageLimit": 100, | ||
47 | "dataStoreLogLevel": "debug", | ||
48 | "maxUrlLength": 500}}, | ||
49 | { | ||
50 | "servlet-name": "cofaxEmail", | ||
51 | "servlet-class": "org.cofax.cds.EmailServlet", | ||
52 | "init-param": { | ||
53 | "mailHost": "mail1", | ||
54 | "mailHostOverride": "mail2"}}, | ||
55 | { | ||
56 | "servlet-name": "cofaxAdmin", | ||
57 | "servlet-class": "org.cofax.cds.AdminServlet"}, | ||
58 | |||
59 | { | ||
60 | "servlet-name": "fileServlet", | ||
61 | "servlet-class": "org.cofax.cds.FileServlet"}, | ||
62 | { | ||
63 | "servlet-name": "cofaxTools", | ||
64 | "servlet-class": "org.cofax.cms.CofaxToolsServlet", | ||
65 | "init-param": { | ||
66 | "templatePath": "toolstemplates/", | ||
67 | "log": 1, | ||
68 | "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", | ||
69 | "logMaxSize": "", | ||
70 | "dataLog": 1, | ||
71 | "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", | ||
72 | "dataLogMaxSize": "", | ||
73 | "removePageCache": "/content/admin/remove?cache=pages&id=", | ||
74 | "removeTemplateCache": "/content/admin/remove?cache=templates&id=", | ||
75 | "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", | ||
76 | "lookInContext": 1, | ||
77 | "adminGroupID": 4, | ||
78 | "betaServer": true}}], | ||
79 | "servlet-mapping": { | ||
80 | "cofaxCDS": "/", | ||
81 | "cofaxEmail": "/cofaxutil/aemail/*", | ||
82 | "cofaxAdmin": "/admin/*", | ||
83 | "fileServlet": "/static/*", | ||
84 | "cofaxTools": "/tools/*"}, | ||
85 | |||
86 | "taglib": { | ||
87 | "taglib-uri": "cofax.tld", | ||
88 | "taglib-location": "/WEB-INF/tlds/cofax.tld"}}} | ||
diff --git a/tests/example5.json b/tests/example5.json new file mode 100644 index 0000000..49980ca --- /dev/null +++ b/tests/example5.json | |||
@@ -0,0 +1,27 @@ | |||
1 | {"menu": { | ||
2 | "header": "SVG Viewer", | ||
3 | "items": [ | ||
4 | {"id": "Open"}, | ||
5 | {"id": "OpenNew", "label": "Open New"}, | ||
6 | null, | ||
7 | {"id": "ZoomIn", "label": "Zoom In"}, | ||
8 | {"id": "ZoomOut", "label": "Zoom Out"}, | ||
9 | {"id": "OriginalView", "label": "Original View"}, | ||
10 | null, | ||
11 | {"id": "Quality"}, | ||
12 | {"id": "Pause"}, | ||
13 | {"id": "Mute"}, | ||
14 | null, | ||
15 | {"id": "Find", "label": "Find..."}, | ||
16 | {"id": "FindAgain", "label": "Find Again"}, | ||
17 | {"id": "Copy"}, | ||
18 | {"id": "CopyAgain", "label": "Copy Again"}, | ||
19 | {"id": "CopySVG", "label": "Copy SVG"}, | ||
20 | {"id": "ViewSVG", "label": "View SVG"}, | ||
21 | {"id": "ViewSource", "label": "View Source"}, | ||
22 | {"id": "SaveAs", "label": "Save As"}, | ||
23 | null, | ||
24 | {"id": "Help"}, | ||
25 | {"id": "About", "label": "About Adobe CVG Viewer..."} | ||
26 | ] | ||
27 | }} | ||
diff --git a/tests/genutf8.pl b/tests/genutf8.pl new file mode 100755 index 0000000..db661a1 --- /dev/null +++ b/tests/genutf8.pl | |||
@@ -0,0 +1,23 @@ | |||
1 | #!/usr/bin/env perl | ||
2 | |||
3 | # Create test comparison data using a different UTF-8 implementation. | ||
4 | |||
5 | # The generated utf8.dat file must have the following MD5 sum: | ||
6 | # cff03b039d850f370a7362f3313e5268 | ||
7 | |||
8 | use strict; | ||
9 | |||
10 | # 0xD800 - 0xDFFF are used to encode supplementary codepoints | ||
11 | # 0x10000 - 0x10FFFF are supplementary codepoints | ||
12 | my (@codepoints) = (0 .. 0xD7FF, 0xE000 .. 0x10FFFF); | ||
13 | |||
14 | my $utf8 = pack("U*", @codepoints); | ||
15 | defined($utf8) or die "Unable create UTF-8 string\n"; | ||
16 | |||
17 | open(FH, ">:utf8", "utf8.dat") | ||
18 | or die "Unable to open utf8.dat: $!\n"; | ||
19 | print FH $utf8 | ||
20 | or die "Unable to write utf8.dat\n"; | ||
21 | close(FH); | ||
22 | |||
23 | # vi:ai et sw=4 ts=4: | ||
diff --git a/tests/json2lua.lua b/tests/json2lua.lua new file mode 100755 index 0000000..050b89d --- /dev/null +++ b/tests/json2lua.lua | |||
@@ -0,0 +1,14 @@ | |||
1 | #!/usr/bin/env lua | ||
2 | |||
3 | -- usage: json2lua.lua [json_file] | ||
4 | -- | ||
5 | -- Eg: | ||
6 | -- echo '[ "testing" ]' | ./json2lua.lua | ||
7 | -- ./json2lua.lua test.json | ||
8 | |||
9 | local json = require "cjson" | ||
10 | local misc = require "cjson-misc" | ||
11 | |||
12 | local json_text = misc.file_load(arg[1]) | ||
13 | local t = json.decode(json_text) | ||
14 | print(misc.serialise_value(t)) | ||
diff --git a/tests/lua2json.lua b/tests/lua2json.lua new file mode 100755 index 0000000..ebe7380 --- /dev/null +++ b/tests/lua2json.lua | |||
@@ -0,0 +1,42 @@ | |||
1 | #!/usr/bin/env lua | ||
2 | |||
3 | -- usage: lua2json.lua [lua_file] | ||
4 | -- | ||
5 | -- Eg: | ||
6 | -- echo '{ "testing" }' | ./lua2json.lua | ||
7 | -- ./lua2json.lua test.lua | ||
8 | |||
9 | local json = require "cjson" | ||
10 | local misc = require "cjson-misc" | ||
11 | |||
12 | function get_lua_table(s) | ||
13 | local env = {} | ||
14 | local func | ||
15 | |||
16 | env.json = {} | ||
17 | env.json.null = json.null | ||
18 | env.null = json.null | ||
19 | s = "data = " .. s | ||
20 | |||
21 | -- Use setfenv() if it exists, otherwise assume Lua 5.2 load() exists | ||
22 | if _G.setfenv then | ||
23 | func = loadstring(s) | ||
24 | if func then | ||
25 | setfenv(func, env) | ||
26 | end | ||
27 | else | ||
28 | func = load(s, nil, nil, env) | ||
29 | end | ||
30 | |||
31 | if func == nil then | ||
32 | error("Invalid syntax. Failed to parse Lua table.") | ||
33 | end | ||
34 | func() | ||
35 | |||
36 | return env.data | ||
37 | end | ||
38 | |||
39 | local t = get_lua_table(misc.file_load(arg[1])) | ||
40 | print(json.encode(t)) | ||
41 | |||
42 | -- vi:ai et sw=4 ts=4: | ||
diff --git a/tests/numbers.json b/tests/numbers.json new file mode 100644 index 0000000..ef11a26 --- /dev/null +++ b/tests/numbers.json | |||
@@ -0,0 +1,7 @@ | |||
1 | [ 0.110001000000000000000001, | ||
2 | 0.12345678910111213141516, | ||
3 | 0.412454033640, | ||
4 | 2.6651441426902251886502972498731, | ||
5 | 2.718281828459045235360287471352, | ||
6 | 3.141592653589793238462643383279, | ||
7 | 2.14069263277926 ] | ||
diff --git a/tests/octets-escaped.dat b/tests/octets-escaped.dat new file mode 100644 index 0000000..ee99a6b --- /dev/null +++ b/tests/octets-escaped.dat | |||
@@ -0,0 +1 @@ | |||
"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-.\/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ" \ No newline at end of file | |||
diff --git a/tests/rfc-example1.json b/tests/rfc-example1.json new file mode 100644 index 0000000..73532fa --- /dev/null +++ b/tests/rfc-example1.json | |||
@@ -0,0 +1,13 @@ | |||
1 | { | ||
2 | "Image": { | ||
3 | "Width": 800, | ||
4 | "Height": 600, | ||
5 | "Title": "View from 15th Floor", | ||
6 | "Thumbnail": { | ||
7 | "Url": "http://www.example.com/image/481989943", | ||
8 | "Height": 125, | ||
9 | "Width": "100" | ||
10 | }, | ||
11 | "IDs": [116, 943, 234, 38793] | ||
12 | } | ||
13 | } | ||
diff --git a/tests/rfc-example2.json b/tests/rfc-example2.json new file mode 100644 index 0000000..2a0cb68 --- /dev/null +++ b/tests/rfc-example2.json | |||
@@ -0,0 +1,22 @@ | |||
1 | [ | ||
2 | { | ||
3 | "precision": "zip", | ||
4 | "Latitude": 37.7668, | ||
5 | "Longitude": -122.3959, | ||
6 | "Address": "", | ||
7 | "City": "SAN FRANCISCO", | ||
8 | "State": "CA", | ||
9 | "Zip": "94107", | ||
10 | "Country": "US" | ||
11 | }, | ||
12 | { | ||
13 | "precision": "zip", | ||
14 | "Latitude": 37.371991, | ||
15 | "Longitude": -122.026020, | ||
16 | "Address": "", | ||
17 | "City": "SUNNYVALE", | ||
18 | "State": "CA", | ||
19 | "Zip": "94085", | ||
20 | "Country": "US" | ||
21 | } | ||
22 | ] | ||
diff --git a/tests/test.lua b/tests/test.lua new file mode 100755 index 0000000..c860878 --- /dev/null +++ b/tests/test.lua | |||
@@ -0,0 +1,252 @@ | |||
1 | #!/usr/bin/env lua | ||
2 | |||
3 | -- CJSON tests | ||
4 | -- | ||
5 | -- Mark Pulford <mark@kyne.com.au> | ||
6 | -- | ||
7 | -- Note: The output of this script is easier to read with "less -S" | ||
8 | |||
9 | local json = require "cjson" | ||
10 | local misc = require "cjson-misc" | ||
11 | |||
12 | local function gen_ascii() | ||
13 | local chars = {} | ||
14 | for i = 0, 255 do chars[i + 1] = string.char(i) end | ||
15 | return table.concat(chars) | ||
16 | end | ||
17 | |||
18 | -- Generate every UTF-16 codepoint, including supplementary codes | ||
19 | local function gen_utf16_escaped() | ||
20 | -- Create raw table escapes | ||
21 | local utf16_escaped = {} | ||
22 | local count = 0 | ||
23 | |||
24 | local function append_escape(code) | ||
25 | local esc = string.format('\\u%04X', code) | ||
26 | table.insert(utf16_escaped, esc) | ||
27 | end | ||
28 | |||
29 | table.insert(utf16_escaped, '"') | ||
30 | for i = 0, 0xD7FF do | ||
31 | append_escape(i) | ||
32 | end | ||
33 | -- Skip 0xD800 - 0xDFFF since they are used to encode supplementary | ||
34 | -- codepoints | ||
35 | for i = 0xE000, 0xFFFF do | ||
36 | append_escape(i) | ||
37 | end | ||
38 | -- Append surrogate pair for each supplementary codepoint | ||
39 | for high = 0xD800, 0xDBFF do | ||
40 | for low = 0xDC00, 0xDFFF do | ||
41 | append_escape(high) | ||
42 | append_escape(low) | ||
43 | end | ||
44 | end | ||
45 | table.insert(utf16_escaped, '"') | ||
46 | |||
47 | return table.concat(utf16_escaped) | ||
48 | end | ||
49 | |||
50 | function test_decode_cycle(filename) | ||
51 | local obj1 = json.decode(file_load(filename)) | ||
52 | local obj2 = json.decode(json.encode(obj1)) | ||
53 | return misc.compare_values(obj1, obj2) | ||
54 | end | ||
55 | |||
56 | local Inf = math.huge; | ||
57 | local NaN = math.huge * 0; | ||
58 | local octets_raw = gen_ascii() | ||
59 | local octets_escaped = misc.file_load("octets-escaped.dat") | ||
60 | local utf8_loaded, utf8_raw = pcall(misc.file_load, "utf8.dat") | ||
61 | if not utf8_loaded then | ||
62 | utf8_raw = "Failed to load utf8.dat" | ||
63 | end | ||
64 | local utf16_escaped = gen_utf16_escaped() | ||
65 | local nested5 = {{{{{ "nested" }}}}} | ||
66 | local table_cycle = {} | ||
67 | local table_cycle2 = { table_cycle } | ||
68 | table_cycle[1] = table_cycle2 | ||
69 | |||
70 | local decode_simple_tests = { | ||
71 | { json.decode, { '"test string"' }, true, { "test string" } }, | ||
72 | { json.decode, { '-5e3' }, true, { -5000 } }, | ||
73 | { json.decode, { 'null' }, true, { json.null } }, | ||
74 | { json.decode, { 'true' }, true, { true } }, | ||
75 | { json.decode, { 'false' }, true, { false } }, | ||
76 | { json.decode, { '{ "1": "one", "3": "three" }' }, | ||
77 | true, { { ["1"] = "one", ["3"] = "three" } } }, | ||
78 | { json.decode, { '[ "one", null, "three" ]' }, | ||
79 | true, { { "one", json.null, "three" } } } | ||
80 | } | ||
81 | |||
82 | local encode_simple_tests = { | ||
83 | { json.encode, { json.null }, true, { 'null' } }, | ||
84 | { json.encode, { true }, true, { 'true' } }, | ||
85 | { json.encode, { false }, true, { 'false' } }, | ||
86 | { json.encode, { { } }, true, { '{}' } }, | ||
87 | { json.encode, { 10 }, true, { '10' } }, | ||
88 | { json.encode, { NaN }, | ||
89 | false, { "Cannot serialise number: must not be NaN or Inf" } }, | ||
90 | { json.encode, { Inf }, | ||
91 | false, { "Cannot serialise number: must not be NaN or Inf" } }, | ||
92 | { json.encode, { "hello" }, true, { '"hello"' } }, | ||
93 | } | ||
94 | |||
95 | local decode_numeric_tests = { | ||
96 | { json.decode, { '[ 0.0, -1, 0.3e-3, 1023.2 ]' }, | ||
97 | true, { { 0.0, -1, 0.0003, 1023.2 } } }, | ||
98 | { json.decode, { '00123' }, true, { 123 } }, | ||
99 | { json.decode, { '05.2' }, true, { 5.2 } }, | ||
100 | { json.decode, { '0e10' }, true, { 0 } }, | ||
101 | { json.decode, { '0x6' }, true, { 6 } }, | ||
102 | { json.decode, { '[ +Inf, Inf, -Inf ]' }, true, { { Inf, Inf, -Inf } } }, | ||
103 | { json.decode, { '[ +Infinity, Infinity, -Infinity ]' }, | ||
104 | true, { { Inf, Inf, -Inf } } }, | ||
105 | { json.decode, { '[ +NaN, NaN, -NaN ]' }, true, { { NaN, NaN, NaN } } }, | ||
106 | { json.decode, { 'Infrared' }, | ||
107 | false, { "Expected the end but found invalid token at character 4" } }, | ||
108 | { json.decode, { 'Noodle' }, | ||
109 | false, { "Expected value but found invalid token at character 1" } }, | ||
110 | } | ||
111 | |||
112 | local encode_table_tests = { | ||
113 | function() | ||
114 | json.encode_sparse_array(true, 2, 3) | ||
115 | json.encode_max_depth(5) | ||
116 | return "Setting sparse array (true, 2, 3) / max depth (5)" | ||
117 | end, | ||
118 | { json.encode, { { [3] = "sparse test" } }, | ||
119 | true, { '[null,null,"sparse test"]' } }, | ||
120 | { json.encode, { { [1] = "one", [4] = "sparse test" } }, | ||
121 | true, { '["one",null,null,"sparse test"]' } }, | ||
122 | { json.encode, { { [1] = "one", [5] = "sparse test" } }, | ||
123 | true, { '{"1":"one","5":"sparse test"}' } }, | ||
124 | |||
125 | { json.encode, { { ["2"] = "numeric string key test" } }, | ||
126 | true, { '{"2":"numeric string key test"}' } }, | ||
127 | |||
128 | { json.encode, { nested5 }, true, { '[[[[["nested"]]]]]' } }, | ||
129 | { json.encode, { { nested5 } }, | ||
130 | false, { "Cannot serialise, excessive nesting (6)" } }, | ||
131 | { json.encode, { table_cycle }, | ||
132 | false, { "Cannot serialise, excessive nesting (6)" } } | ||
133 | } | ||
134 | |||
135 | local encode_error_tests = { | ||
136 | { json.encode, { { [false] = "wrong" } }, | ||
137 | false, { "Cannot serialise boolean: table key must be a number or string" } }, | ||
138 | { json.encode, { function () end }, | ||
139 | false, { "Cannot serialise function: type not supported" } }, | ||
140 | function () | ||
141 | json.refuse_invalid_numbers("encode") | ||
142 | return 'Setting refuse_invalid_numbers("encode")' | ||
143 | end, | ||
144 | { json.encode, { NaN }, | ||
145 | false, { "Cannot serialise number: must not be NaN or Inf" } }, | ||
146 | { json.encode, { Inf }, | ||
147 | false, { "Cannot serialise number: must not be NaN or Inf" } }, | ||
148 | function () | ||
149 | json.refuse_invalid_numbers(false) | ||
150 | return 'Setting refuse_invalid_numbers(false).' | ||
151 | end, | ||
152 | { json.encode, { NaN }, true, { "nan" } }, | ||
153 | { json.encode, { Inf }, true, { "inf" } }, | ||
154 | function () | ||
155 | json.refuse_invalid_numbers("encode") | ||
156 | return 'Setting refuse_invalid_numbers("encode")' | ||
157 | end, | ||
158 | } | ||
159 | |||
160 | local json_nested = string.rep("[", 100000) .. string.rep("]", 100000) | ||
161 | |||
162 | local decode_error_tests = { | ||
163 | { json.decode, { '\0"\0"' }, | ||
164 | false, { "JSON parser does not support UTF-16 or UTF-32" } }, | ||
165 | { json.decode, { '"\0"\0' }, | ||
166 | false, { "JSON parser does not support UTF-16 or UTF-32" } }, | ||
167 | { json.decode, { '{ "unexpected eof": ' }, | ||
168 | false, { "Expected value but found T_END at character 21" } }, | ||
169 | { json.decode, { '{ "extra data": true }, false' }, | ||
170 | false, { "Expected the end but found T_COMMA at character 23" } }, | ||
171 | { json.decode, { ' { "bad escape \\q code" } ' }, | ||
172 | false, { "Expected object key string but found invalid escape code at character 16" } }, | ||
173 | { json.decode, { ' { "bad unicode \\u0f6 escape" } ' }, | ||
174 | false, { "Expected object key string but found invalid unicode escape code at character 17" } }, | ||
175 | { json.decode, { ' [ "bad barewood", test ] ' }, | ||
176 | false, { "Expected value but found invalid token at character 20" } }, | ||
177 | { json.decode, { '[ -+12 ]' }, | ||
178 | false, { "Expected value but found invalid number at character 3" } }, | ||
179 | { json.decode, { '-v' }, | ||
180 | false, { "Expected value but found invalid number at character 1" } }, | ||
181 | { json.decode, { '[ 0.4eg10 ]' }, | ||
182 | false, { "Expected comma or array end but found invalid token at character 6" } }, | ||
183 | { json.decode, { json_nested }, | ||
184 | false, { "Too many nested data structures" } } | ||
185 | } | ||
186 | |||
187 | local escape_tests = { | ||
188 | -- Test 8bit clean | ||
189 | { json.encode, { octets_raw }, true, { octets_escaped } }, | ||
190 | { json.decode, { octets_escaped }, true, { octets_raw } }, | ||
191 | -- Ensure high bits are removed from surrogate codes | ||
192 | { json.decode, { '"\\uF800"' }, true, { "\239\160\128" } }, | ||
193 | -- Test inverted surrogate pairs | ||
194 | { json.decode, { '"\\uDB00\\uD800"' }, | ||
195 | false, { "Expected value but found invalid unicode escape code at character 2" } }, | ||
196 | -- Test 2x high surrogate code units | ||
197 | { json.decode, { '"\\uDB00\\uDB00"' }, | ||
198 | false, { "Expected value but found invalid unicode escape code at character 2" } }, | ||
199 | -- Test invalid 2nd escape | ||
200 | { json.decode, { '"\\uDB00\\"' }, | ||
201 | false, { "Expected value but found invalid unicode escape code at character 2" } }, | ||
202 | { json.decode, { '"\\uDB00\\uD"' }, | ||
203 | false, { "Expected value but found invalid unicode escape code at character 2" } }, | ||
204 | -- Test decoding of all UTF-16 escapes | ||
205 | { json.decode, { utf16_escaped }, true, { utf8_raw } } | ||
206 | } | ||
207 | |||
208 | -- The standard Lua interpreter is ANSI C online doesn't support locales | ||
209 | -- by default. Force a known problematic locale to test strtod()/sprintf(). | ||
210 | local locale_tests = { | ||
211 | function () | ||
212 | os.setlocale("cs_CZ") | ||
213 | json.new() | ||
214 | return "Setting locale to cs_CZ (comma separator)" | ||
215 | end, | ||
216 | { json.encode, { 1.5 }, true, { '1.5' } }, | ||
217 | { json.decode, { "[ 10, \"test\" ]" }, true, { { 10, "test" } } }, | ||
218 | function () | ||
219 | os.setlocale("C") | ||
220 | json.new() | ||
221 | return "Reverting locale to POSIX" | ||
222 | end | ||
223 | } | ||
224 | |||
225 | print(string.format("Testing Lua CJSON version %s\n", json.version)) | ||
226 | |||
227 | misc.run_test_group("decode simple value", decode_simple_tests) | ||
228 | misc.run_test_group("encode simple value", encode_simple_tests) | ||
229 | misc.run_test_group("decode numeric", decode_numeric_tests) | ||
230 | misc.run_test_group("encode table", encode_table_tests) | ||
231 | misc.run_test_group("decode error", decode_error_tests) | ||
232 | misc.run_test_group("encode error", encode_error_tests) | ||
233 | misc.run_test_group("escape", escape_tests) | ||
234 | misc.run_test_group("locale", locale_tests) | ||
235 | |||
236 | json.refuse_invalid_numbers(false) | ||
237 | json.encode_max_depth(20) | ||
238 | for i = 1, #arg do | ||
239 | misc.run_test("decode cycle " .. arg[i], test_decode_cycle, { arg[i] }, | ||
240 | true, { true }) | ||
241 | end | ||
242 | |||
243 | local pass, total = misc.run_test_summary() | ||
244 | |||
245 | if pass == total then | ||
246 | print("==> Summary: all tests succeeded") | ||
247 | else | ||
248 | print(string.format("==> Summary: %d/%d tests failed", total - pass, total)) | ||
249 | os.exit(1) | ||
250 | end | ||
251 | |||
252 | -- vi:ai et sw=4 ts=4: | ||
diff --git a/tests/types.json b/tests/types.json new file mode 100644 index 0000000..c01e7d2 --- /dev/null +++ b/tests/types.json | |||
@@ -0,0 +1 @@ | |||
{ "array": [ 10, true, null ] } | |||