aboutsummaryrefslogtreecommitdiff
path: root/lua_json.c
diff options
context:
space:
mode:
authorMark Pulford <mark@kyne.com.au>2011-04-15 23:37:41 +0930
committerMark Pulford <mark@kyne.com.au>2011-04-15 23:37:41 +0930
commit9f3d6b59c5f097d66e94f987c7731d7a4113057f (patch)
tree4a3b0aa4f59a0b645b0cfb12fc89d260f1fca9b8 /lua_json.c
parenta99fc753a590d7dd1cf19d083af504cb3ec9a8d4 (diff)
downloadlua-cjson-9f3d6b59c5f097d66e94f987c7731d7a4113057f.tar.gz
lua-cjson-9f3d6b59c5f097d66e94f987c7731d7a4113057f.tar.bz2
lua-cjson-9f3d6b59c5f097d66e94f987c7731d7a4113057f.zip
Merge lua_json_encode/decode.c into lua_json.c
Diffstat (limited to 'lua_json.c')
-rw-r--r--lua_json.c668
1 files changed, 668 insertions, 0 deletions
diff --git a/lua_json.c b/lua_json.c
new file mode 100644
index 0000000..ab72dd5
--- /dev/null
+++ b/lua_json.c
@@ -0,0 +1,668 @@
1/* Lua JSON routines
2 */
3
4/* Caveats:
5 * - No unicode support
6 * - JSON "null" values are represented as lightuserdata. Compare with
7 * json.null.
8 * - Parsing comments is not support. According to json.org, this isn't
9 * part of the spec.
10 */
11
12/* FIXME:
13 * - Ensure JSON data is UTF-8. Fail otherwise.
14 * - Alternatively, dynamically support Unicode in JSON string. Return current locale.
15 * - Use lua_checkstack() to ensure there is enough stack space left to
16 * fulfill an operation. What happens if we don't, is that acceptible too?
17 * Does lua_checkstack grow the stack, or merely check if it is possible?
18 */
19
20/* FIXME:
21 * - Option to encode non-printable characters? Only \" \\ are required
22 * - Unicode?
23 */
24
25/* FIXME:
26 * - Review memory allocation handling and error returns.
27 * Ensure all memory is free. Including after exceptions.
28 */
29
30#include <assert.h>
31#include <string.h>
32#include <math.h>
33
34#include <lua.h>
35#include <lauxlib.h>
36
37#include "lua_json.h"
38#include "utils.h"
39#include "strbuf.h"
40
41/* ===== ENCODING ===== */
42
43
44/* JSON escape a character if required, or return NULL */
45static inline char *json_escape_char(int c)
46{
47 switch(c) {
48 case 0:
49 return "\\u0000";
50 case '\\':
51 return "\\\\";
52 case '"':
53 return "\\\"";
54 case '\b':
55 return "\\b";
56 case '\t':
57 return "\\t";
58 case '\n':
59 return "\\n";
60 case '\f':
61 return "\\f";
62 case '\r':
63 return "\\r";
64 }
65
66 return NULL;
67}
68
69/* json_append_string args:
70 * - lua_State
71 * - JSON strbuf
72 * - String (Lua stack index)
73 *
74 * Returns nothing. Doesn't remove string from Lua stack */
75static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
76{
77 char *p;
78 int i;
79 const char *str;
80 size_t len;
81
82 str = lua_tolstring(l, lindex, &len);
83
84 strbuf_append_char(json, '\"');
85 for (i = 0; i < len; i++) {
86 p = json_escape_char(str[i]);
87 if (p)
88 strbuf_append_string(json, p);
89 else
90 strbuf_append_char(json, str[i]);
91 }
92 strbuf_append_char(json, '\"');
93}
94
95/* Find the size of the array on the top of the Lua stack
96 * -1 object (not a pure array)
97 * >=0 elements in array
98 */
99static int lua_array_length(lua_State *l)
100{
101 double k;
102 int max;
103
104 max = 0;
105
106 lua_pushnil(l);
107 /* table, startkey */
108 while (lua_next(l, -2) != 0) {
109 /* table, key, value */
110 if ((k = lua_tonumber(l, -2))) {
111 /* Integer >= 1 ? */
112 if (floor(k) == k && k >= 1) {
113 if (k > max)
114 max = k;
115 lua_pop(l, 1);
116 continue;
117 }
118 }
119
120 /* Must not be an array (non integer key) */
121 lua_pop(l, 2);
122 return -1;
123 }
124
125 return max;
126}
127
128static void json_append_data(lua_State *l, strbuf_t *json);
129
130/* json_append_array args:
131 * - lua_State
132 * - JSON strbuf
133 * - Size of passwd Lua array (top of stack) */
134static void json_append_array(lua_State *l, strbuf_t *json, int array_length)
135{
136 int comma, i;
137
138 strbuf_append_string(json, "[ ");
139
140 comma = 0;
141 for (i = 1; i <= array_length; i++) {
142 if (comma)
143 strbuf_append_string(json, ", ");
144 else
145 comma = 1;
146
147 lua_rawgeti(l, -1, i);
148 json_append_data(l, json);
149 lua_pop(l, 1);
150 }
151
152 strbuf_append_string(json, " ]");
153}
154
155static void json_append_object(lua_State *l, strbuf_t *json)
156{
157 int comma, keytype;
158
159 /* Object */
160 strbuf_append_string(json, "{ ");
161
162 lua_pushnil(l);
163 /* table, startkey */
164 comma = 0;
165 while (lua_next(l, -2) != 0) {
166 if (comma)
167 strbuf_append_string(json, ", ");
168 else
169 comma = 1;
170
171 /* table, key, value */
172 keytype = lua_type(l, -2);
173 if (keytype == LUA_TNUMBER) {
174 strbuf_append_fmt(json, "\"" LUA_NUMBER_FMT "\": ",
175 lua_tonumber(l, -2));
176 } else if (keytype == LUA_TSTRING) {
177 json_append_string(l, json, -2);
178 strbuf_append_string(json, ": ");
179 } else {
180 die("Cannot serialise table key %s", lua_typename(l, lua_type(l, -2)));
181 }
182
183 /* table, key, value */
184 json_append_data(l, json);
185 lua_pop(l, 1);
186 /* table, key */
187 }
188
189 strbuf_append_string(json, " }");
190}
191
192/* Serialise Lua data into JSON string.
193 *
194 * FIXME:
195 * - Error handling when cannot serialise key or value (return to script)
196 */
197static void json_append_data(lua_State *l, strbuf_t *json)
198{
199 int len;
200
201 switch (lua_type(l, -1)) {
202 case LUA_TSTRING:
203 json_append_string(l, json, -1);
204 break;
205 case LUA_TNUMBER:
206 strbuf_append_fmt(json, "%lf", lua_tonumber(l, -1));
207 break;
208 case LUA_TBOOLEAN:
209 if (lua_toboolean(l, -1))
210 strbuf_append_string(json, "true");
211 else
212 strbuf_append_string(json, "false");
213 break;
214 case LUA_TTABLE:
215 len = lua_array_length(l);
216 if (len >= 0)
217 json_append_array(l, json, len);
218 else
219 json_append_object(l, json);
220 break;
221 case LUA_TNIL:
222 strbuf_append_string(json, "null");
223 break;
224 case LUA_TLIGHTUSERDATA:
225 if (lua_touserdata(l, -1) == NULL) {
226 strbuf_append_string(json, "null");
227 break;
228 }
229 default:
230 /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, and LUA_TLIGHTUSERDATA)
231 * cannot be serialised */
232 /* FIXME: return error */
233 die("Cannot serialise %s", lua_typename(l, lua_type(l, -1)));
234 }
235}
236
237char *lua_json_encode(lua_State *l, int *len)
238{
239 strbuf_t buf;
240 char *json;
241
242 strbuf_init(&buf);
243 strbuf_set_increment(&buf, 256);
244 json_append_data(l, &buf);
245 json = strbuf_free_to_string(&buf, len);
246
247 return json;
248}
249
250int lua_api_json_encode(lua_State *l)
251{
252 char *json;
253 int len;
254
255 json = lua_json_encode(l, &len);
256 lua_pushlstring(l, json, len);
257 free(json);
258
259 return 1;
260}
261
262/* ===== DECODING ===== */
263
264typedef struct {
265 const char *data;
266 int index;
267 strbuf_t *tmp; /* Temporary storage for strings */
268} json_parse_t;
269
270typedef enum {
271 T_OBJ_BEGIN,
272 T_OBJ_END,
273 T_ARR_BEGIN,
274 T_ARR_END,
275 T_STRING,
276 T_NUMBER,
277 T_BOOLEAN,
278 T_NULL,
279 T_COLON,
280 T_COMMA,
281 T_END,
282 T_WHITESPACE,
283 T_ERROR,
284 T_UNKNOWN
285} json_token_type_t;
286
287static const char *json_token_type_name[] = {
288 "T_OBJ_BEGIN",
289 "T_OBJ_END",
290 "T_ARR_BEGIN",
291 "T_ARR_END",
292 "T_STRING",
293 "T_NUMBER",
294 "T_BOOLEAN",
295 "T_NULL",
296 "T_COLON",
297 "T_COMMA",
298 "T_END",
299 "T_WHITESPACE",
300 "T_ERROR",
301 "T_UNKNOWN",
302 NULL
303};
304
305typedef struct {
306 json_token_type_t type;
307 int index;
308 union {
309 char *string;
310 double number;
311 int boolean;
312 } value;
313 int length; /* FIXME: Merge into union? Won't save memory, but more logical */
314} json_token_t;
315
316static void json_process_value(lua_State *l, json_parse_t *json, json_token_t *token);
317
318static json_token_type_t json_ch2token[256];
319static char json_ch2escape[256];
320
321static void json_init_lookup_tables()
322{
323 int i;
324
325 /* Tag all characters as an error */
326 for (i = 0; i < 256; i++)
327 json_ch2token[i] = T_ERROR;
328
329 /* Set tokens that require no further processing */
330 json_ch2token['{'] = T_OBJ_BEGIN;
331 json_ch2token['}'] = T_OBJ_END;
332 json_ch2token['['] = T_ARR_BEGIN;
333 json_ch2token[']'] = T_ARR_END;
334 json_ch2token[','] = T_COMMA;
335 json_ch2token[':'] = T_COLON;
336 json_ch2token['\0'] = T_END;
337 json_ch2token[' '] = T_WHITESPACE;
338 json_ch2token['\t'] = T_WHITESPACE;
339 json_ch2token['\n'] = T_WHITESPACE;
340 json_ch2token['\r'] = T_WHITESPACE;
341
342 /* Update characters that require further processing */
343 json_ch2token['n'] = T_UNKNOWN;
344 json_ch2token['t'] = T_UNKNOWN;
345 json_ch2token['f'] = T_UNKNOWN;
346 json_ch2token['"'] = T_UNKNOWN;
347 json_ch2token['-'] = T_UNKNOWN;
348 for (i = 0; i < 10; i++)
349 json_ch2token['0' + i] = T_UNKNOWN;
350
351 for (i = 0; i < 256; i++)
352 json_ch2escape[i] = 0; /* String error */
353
354 json_ch2escape['"'] = '"';
355 json_ch2escape['\\'] = '\\';
356 json_ch2escape['/'] = '/';
357 json_ch2escape['b'] = '\b';
358 json_ch2escape['t'] = '\t';
359 json_ch2escape['n'] = '\n';
360 json_ch2escape['f'] = '\f';
361 json_ch2escape['r'] = '\r';
362 json_ch2escape['u'] = 'u'; /* This needs to be parsed as unicode */
363}
364
365static void json_next_string_token(json_parse_t *json, json_token_t *token)
366{
367 char ch;
368
369 /* Caller must ensure a string is next */
370 assert(json->data[json->index] == '"');
371
372 /* Gobble string. FIXME, ugly */
373
374 json->tmp->length = 0;
375 while ((ch = json->data[++json->index]) != '"') {
376 /* Handle escapes */
377 if (ch == '\\') {
378 /* Translate escape code */
379 ch = json_ch2escape[(unsigned char)json->data[++json->index]];
380 if (!ch) {
381 /* Invalid escape code */
382 token->type = T_ERROR;
383 return;
384 }
385 if (ch == 'u') {
386 /* Process unicode */
387 /* FIXME: cleanup memory handling. Implement iconv(3)
388 * conversion from UCS-2 -> UTF-8
389 */
390 if (!memcmp(&json->data[json->index], "u0000", 5)) {
391 /* Handle NULL */
392 ch = 0;
393 json->index += 4;
394 } else {
395 /* Remaining codepoints unhandled */
396 token->type = T_ERROR;
397 return;
398 }
399 }
400 }
401 strbuf_append_char(json->tmp, ch);
402 }
403 json->index++; /* Eat final quote (") */
404
405 strbuf_ensure_null(json->tmp);
406
407 token->type = T_STRING;
408 token->value.string = strbuf_string(json->tmp, NULL);
409 token->length = json->tmp->length;
410}
411
412static void json_next_number_token(json_parse_t *json, json_token_t *token)
413{
414 const char *startptr;
415 char *endptr;
416
417 /* FIXME:
418 * Verify that the number takes the following form:
419 * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)?
420 * strtod() below allows other forms (Hex, infinity, NaN,..) */
421 /* i = json->index;
422 if (json->data[i] == '-')
423 i++;
424 j = i;
425 while ('0' <= json->data[i] && json->data[i] <= '9')
426 i++;
427 if (i == j)
428 return T_ERROR; */
429
430 token->type = T_NUMBER;
431 startptr = &json->data[json->index];
432 token->value.number = strtod(&json->data[json->index], &endptr);
433 if (startptr == endptr)
434 token->type = T_ERROR;
435 else
436 json->index += endptr - startptr; /* Skip the processed number */
437
438 return;
439}
440
441/* Fills in the token struct.
442 * T_STRING will return a pointer to the json_parse_t temporary string
443 * T_ERROR will leave the json->index pointer at the error.
444 */
445static void json_next_token(json_parse_t *json, json_token_t *token)
446{
447 int ch;
448
449 /* Eat whitespace. FIXME: UGLY */
450 token->type = json_ch2token[(unsigned char)json->data[json->index]];
451 while (token->type == T_WHITESPACE)
452 token->type = json_ch2token[(unsigned char)json->data[++json->index]];
453
454 token->index = json->index;
455
456 /* Don't advance the pointer for an error or the end */
457 if (token->type == T_ERROR || token->type == T_END)
458 return;
459
460 /* Found a known token, advance index and return */
461 if (token->type != T_UNKNOWN) {
462 json->index++;
463 return;
464 }
465
466 /* Process characters which triggered T_UNKNOWN */
467 ch = json->data[json->index];
468
469 if (ch == '"') {
470 json_next_string_token(json, token);
471 return;
472 } else if (ch == '-' || ('0' <= ch && ch <= '9')) {
473 json_next_number_token(json, token);
474 return;
475 } else if (!strncmp(&json->data[json->index], "true", 4)) {
476 token->type = T_BOOLEAN;
477 token->value.boolean = 1;
478 json->index += 4;
479 return;
480 } else if (!strncmp(&json->data[json->index], "false", 5)) {
481 token->type = T_BOOLEAN;
482 token->value.boolean = 0;
483 json->index += 5;
484 return;
485 } else if (!strncmp(&json->data[json->index], "null", 4)) {
486 token->type = T_NULL;
487 json->index += 4;
488 return;
489 }
490
491 token->type = T_ERROR;
492}
493
494/* This function does not return.
495 * DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED.
496 * The only allowed exception is the temporary parser string
497 * json->tmp struct.
498 * json and token should exist on the stack somewhere.
499 * luaL_error() will long_jmp and release the stack */
500static void json_throw_parse_error(lua_State *l, json_parse_t *json,
501 const char *exp, json_token_t *token)
502{
503 strbuf_free(json->tmp);
504 luaL_error(l, "Expected %s but found type <%s> at character %d",
505 exp, json_token_type_name[token->type], token->index);
506}
507
508static void json_parse_object_context(lua_State *l, json_parse_t *json)
509{
510 json_token_t token;
511
512 lua_newtable(l);
513
514 json_next_token(json, &token);
515
516 /* Handle empty objects */
517 if (token.type == T_OBJ_END)
518 return;
519
520 while (1) {
521 if (token.type != T_STRING)
522 json_throw_parse_error(l, json, "object key", &token);
523
524 lua_pushlstring(l, token.value.string, token.length); /* Push key */
525
526 json_next_token(json, &token);
527 if (token.type != T_COLON)
528 json_throw_parse_error(l, json, "colon", &token);
529
530 json_next_token(json, &token);
531 json_process_value(l, json, &token);
532 lua_rawset(l, -3); /* Set key = value */
533
534 json_next_token(json, &token);
535
536 if (token.type == T_OBJ_END)
537 return;
538
539 if (token.type != T_COMMA)
540 json_throw_parse_error(l, json, "comma or object end", &token);
541
542 json_next_token(json, &token);
543 }
544}
545
546/* Handle the array context */
547static void json_parse_array_context(lua_State *l, json_parse_t *json)
548{
549 json_token_t token;
550 int i;
551
552 lua_newtable(l);
553
554 json_next_token(json, &token);
555
556 /* Handle empty arrays */
557 if (token.type == T_ARR_END)
558 return;
559
560 i = 1;
561 while (1) {
562 json_process_value(l, json, &token);
563 lua_rawseti(l, -2, i); /* arr[i] = value */
564
565 json_next_token(json, &token);
566
567 if (token.type == T_ARR_END)
568 return;
569
570 if (token.type != T_COMMA)
571 json_throw_parse_error(l, json, "comma or array end", &token);
572
573 json_next_token(json, &token);
574 i++;
575 }
576}
577
578/* Handle the "value" context */
579static void json_process_value(lua_State *l, json_parse_t *json, json_token_t *token)
580{
581 switch (token->type) {
582 case T_STRING:
583 lua_pushlstring(l, token->value.string, token->length);
584 break;;
585 case T_NUMBER:
586 lua_pushnumber(l, token->value.number);
587 break;;
588 case T_BOOLEAN:
589 lua_pushboolean(l, token->value.boolean);
590 break;;
591 case T_OBJ_BEGIN:
592 json_parse_object_context(l, json);
593 break;;
594 case T_ARR_BEGIN:
595 json_parse_array_context(l, json);
596 break;;
597 case T_NULL:
598 /* In Lua, setting "t[k] = nil" will delete k from the table.
599 * Hence a NULL pointer lightuserdata object is used instead */
600 lua_pushlightuserdata(l, NULL);
601 break;;
602 default:
603 json_throw_parse_error(l, json, "value", token);
604 }
605}
606
607/* json_text must be null terminated string */
608void lua_json_decode(lua_State *l, const char *json_text)
609{
610 json_parse_t json;
611 json_token_t token;
612
613 json.data = json_text;
614 json.index = 0;
615 json.tmp = strbuf_new();
616 strbuf_set_increment(json.tmp, 256);
617
618 json_next_token(&json, &token);
619 json_process_value(l, &json, &token);
620
621 /* Ensure there is no more input left */
622 json_next_token(&json, &token);
623
624 if (token.type != T_END)
625 json_throw_parse_error(l, &json, "the end", &token);
626
627 strbuf_free(json.tmp);
628}
629
630static int lua_api_json_decode(lua_State *l)
631{
632 int i, n;
633
634 n = lua_gettop(l);
635
636 for (i = 1; i <= n; i++) {
637 if (lua_isstring(l, i)) {
638 lua_json_decode(l, lua_tostring(l, i));
639 } else {
640 lua_pushnil(l);
641 }
642 }
643
644 return n; /* Number of results */
645}
646
647/* ===== INITIALISATION ===== */
648
649void lua_json_init(lua_State *l)
650{
651 luaL_Reg reg[] = {
652 { "encode", lua_api_json_encode },
653 { "decode", lua_api_json_decode },
654 { NULL, NULL }
655 };
656
657 json_init_lookup_tables();
658
659 luaL_register(l, "json", reg);
660
661 /* Set json.null, and pop "json" table from the stack */
662 lua_pushlightuserdata(l, NULL);
663 lua_setfield(l, -2, "null");
664 lua_pop(l, 1);
665}
666
667/* vi:ai et sw=4 ts=4:
668 */