Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
json/src/json.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
4162 lines (3037 sloc)
85.4 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* ========================================================================== | |
| * json.c - Path Autovivifying JSON C Library | |
| * -------------------------------------------------------------------------- | |
| * Copyright (c) 2012, 2013 William Ahern | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a | |
| * copy of this software and associated documentation files (the | |
| * "Software"), to deal in the Software without restriction, including | |
| * without limitation the rights to use, copy, modify, merge, publish, | |
| * distribute, sublicense, and/or sell copies of the Software, and to permit | |
| * persons to whom the Software is furnished to do so, subject to the | |
| * following conditions: | |
| * | |
| * The above copyright notice and this permission notice shall be included | |
| * in all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
| * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN | |
| * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
| * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | |
| * USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| * ========================================================================== | |
| */ | |
| #include <limits.h> /* INT_MAX */ | |
| #include <stddef.h> /* size_t offsetof */ | |
| #include <stdarg.h> /* va_list va_start va_end va_arg */ | |
| #include <stdint.h> /* SIZE_MAX uintptr_t */ | |
| #include <stdlib.h> /* malloc(3) realloc(3) free(3) strtod(3) */ | |
| #include <stdio.h> /* EOF snprintf(3) fopen(3) fclose(3) ferror(3) clearerr(3) */ | |
| #include <string.h> /* memset(3) strncmp(3) strlen(3) strlcpy(3) stpncpy(3) */ | |
| #include <math.h> /* HUGE_VAL modf(3) isnormal(3) */ | |
| #include <errno.h> /* errno ERANGE EOVERFLOW EINVAL */ | |
| #include <sys/queue.h> | |
| #include "llrb.h" | |
| #include "json.h" | |
| /* | |
| * M I S C E L L A N E O U S R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| #define JSON_PASTE(x, y) x ## y | |
| #define JSON_XPASTE(x, y) JSON_PASTE(x, y) | |
| #define JSON_MIN(a, b) (((a) < (b))? (a) : (b)) | |
| #define JSON_CMP(a, b) (((a) < (b))? -1 : ((a) > (b))? 1 : 0) | |
| #define json_countof(a) (sizeof (a) / sizeof *(a)) | |
| #define json_endof(a) (&(a)[json_countof(a)]) | |
| #if __alignas_is_defined | |
| #define JSON_ALIGNAS(n) _Alignas(n) | |
| #else | |
| #define JSON_ALIGNAS(n) __attribute__((aligned(n))) | |
| #endif | |
| #undef SAY_ | |
| #define SAY_(file, func, line, fmt, ...) \ | |
| fprintf(stderr, "%s:%d: " fmt "%s", __func__, __LINE__, __VA_ARGS__) | |
| #undef SAY | |
| #define SAY(...) SAY_(__FILE__, __func__, __LINE__, __VA_ARGS__, "\n") | |
| #undef HAI | |
| #define HAI SAY("hai") | |
| #ifdef __GNUC__ | |
| #define JSON_NOTUSED __attribute__((unused)) | |
| #else | |
| #define JSON_NOTUSED | |
| #endif | |
| #if __linux | |
| static inline size_t json_strlcpy(char *dst, const char *src, size_t len) { | |
| char *end = stpncpy(dst, src, len); | |
| if (len) | |
| dst[len - 1] = '\0'; | |
| return end - dst; | |
| } /* json_strlcpy() */ | |
| #else | |
| #define json_strlcpy(...) strlcpy(__VA_ARGS__) | |
| #endif | |
| static inline size_t json_strlen(const char *src) { | |
| return (src)? strlen(src) : 0; | |
| } /* json_strlen() */ | |
| static inline void *json_make(size_t size, int *error) { | |
| void *p; | |
| if (!(p = malloc(size))) | |
| *error = errno; | |
| return p; | |
| } /* json_make() */ | |
| static void *json_make0(size_t size, int *error) { | |
| void *p; | |
| if ((p = json_make(size, error))) | |
| memset(p, 0, size); | |
| return p; | |
| } /* json_make0() */ | |
| #define json_isctype(map, ch) \ | |
| !!((map)[((ch) & 0xff) / 64] & (1ULL << ((ch) & 0xff))) | |
| static inline _Bool json_isdigit(unsigned char ch) { | |
| unsigned long long digit[4] = { 0x3ff000000000000ULL, 0, 0, 0 }; | |
| return json_isctype(digit, ch); | |
| } /* json_isdigit() */ | |
| static inline _Bool json_isgraph(unsigned char ch) { | |
| unsigned long long graph[4] = { 0xfffffffe00000000ULL, 0x7fffffffffffffffULL, 0, 0 }; | |
| return json_isctype(graph, ch); | |
| } /* json_isgraph() */ | |
| static inline _Bool json_isascii(unsigned char ch) { | |
| return !(0x80 & ch); | |
| } /* json_isascii() */ | |
| static inline _Bool json_isnumber(unsigned char ch) { | |
| /* + - . 0-9 E e */ | |
| unsigned long long number[4] = { 0x3ff680000000000ULL, 0x2000000020ULL, 0, 0 }; | |
| return json_isctype(number, ch); | |
| } /* json_isnumber() */ | |
| static inline size_t json_power2(size_t n) { | |
| #if defined SIZE_MAX | |
| --n; | |
| n |= n >> 1; | |
| n |= n >> 2; | |
| n |= n >> 4; | |
| n |= n >> 8; | |
| #if SIZE_MAX > 0xffffULL | |
| n |= n >> 16; | |
| #if SIZE_MAX > 0xffffffffULL | |
| n |= n >> 32; | |
| #if SIZE_MAX > 0xffffffffffffffffULL | |
| #error SIZE_MAX too big | |
| #endif | |
| #endif | |
| #endif | |
| return ++n; | |
| #else | |
| #error SIZE_MAX not defined | |
| #endif | |
| } /* json_power2() */ | |
| static inline int json_add(size_t *r, size_t a, size_t b) { | |
| if (~a < b) | |
| return ENOMEM; | |
| *r = a + b; | |
| return 0; | |
| } /* json_add() */ | |
| JSON_PUBLIC const char *json_strtype(enum json_type type) { | |
| switch (type) { | |
| default: | |
| /* FALL THROUGH */ | |
| case JSON_T_NULL: | |
| return "null"; | |
| case JSON_T_BOOLEAN: | |
| return "boolean"; | |
| case JSON_T_NUMBER: | |
| return "number"; | |
| case JSON_T_STRING: | |
| return "string"; | |
| case JSON_T_ARRAY: | |
| return "array"; | |
| case JSON_T_OBJECT: | |
| return "object"; | |
| } /* switch() */ | |
| } /* json_strtype() */ | |
| JSON_PUBLIC enum json_type json_itype(const char *type) { | |
| static const char name[][8] = { | |
| [JSON_T_NULL] = "null", | |
| [JSON_T_BOOLEAN] = "boolean", | |
| [JSON_T_NUMBER] = "number", | |
| [JSON_T_STRING] = "string", | |
| [JSON_T_ARRAY] = "array", | |
| [JSON_T_OBJECT] = "object", | |
| }; | |
| size_t i; | |
| if (type && *type) { | |
| for (i = 0; i < json_countof(name); i++) { | |
| if (!strcmp(type, name[i])) | |
| return (enum json_type)i; | |
| } | |
| } | |
| return JSON_T_NULL; | |
| } /* json_itype() */ | |
| JSON_PUBLIC const char *json_strerror(int error) { | |
| static const char *descr[] = { | |
| [JSON_EASSERT-JSON_EBASE] = "JSON assertion", | |
| [JSON_ELEXICAL-JSON_EBASE] = "JSON lexical error", | |
| [JSON_ESYNTAX-JSON_EBASE] = "JSON syntax error", | |
| [JSON_ETRUNCATED-JSON_EBASE] = "JSON truncated input", | |
| [JSON_ENOMORE-JSON_EBASE] = "JSON no more input needed", | |
| [JSON_ETYPING-JSON_EBASE] = "JSON illegal operation on type", | |
| [JSON_EBADPATH-JSON_EBASE] = "JSON malformed path", | |
| [JSON_EBIGPATH-JSON_EBASE] = "JSON path too long", | |
| [JSON_ETOODEEP-JSON_EBASE] = "JSON tree too deep", | |
| }; | |
| if (error >= JSON_EBASE && error < JSON_ELAST) | |
| return descr[error - JSON_EBASE]; | |
| else | |
| return strerror(error); | |
| } /* json_strerror() */ | |
| /* | |
| * V E R S I O N R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| JSON_PUBLIC const char *json_vendor(void) { | |
| return JSON_VENDOR; | |
| } /* json_vendor() */ | |
| JSON_PUBLIC int json_version(void) { | |
| return JSON_VERSION; | |
| } /* json_version() */ | |
| JSON_PUBLIC int json_v_rel(void) { | |
| return JSON_V_REL; | |
| } /* json_v_rel() */ | |
| JSON_PUBLIC int json_v_abi(void) { | |
| return JSON_V_ABI; | |
| } /* json_v_abi() */ | |
| JSON_PUBLIC int json_v_api(void) { | |
| return JSON_V_API; | |
| } /* json_v_api() */ | |
| /* | |
| * O B S T A C K R O U T I N E S | |
| * | |
| * Like the GNU thing, but simpler and easier. The principle purpose of | |
| * using a special-purpose allocator is deallocation efficiency, not to | |
| * outdo the system allocator. | |
| * | |
| * NOTES: | |
| * | |
| * o `Block' refers to the memory region allocated by the system. | |
| * | |
| * o `Page' refers to the data structure and associated memory region | |
| * used for chunking allocation requests. | |
| * | |
| * o `Page size' refers to the size of allocatable region of the Page | |
| * used to satisfy allocation requests. | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| #define JSON_O_ALIGN 16 | |
| #define JSON_O_SYSFUDGE 32UL /* system allocator overhead */ | |
| #define JSON_O_MINBLOCK 4096UL | |
| #define JSON_O_MAXBLOCK (SIZE_MAX >> 2UL) /* to simplify overflow detection */ | |
| struct json_obspage { | |
| LIST_ENTRY(json_obspage) le; | |
| size_t size; | |
| unsigned char JSON_ALIGNAS(JSON_O_ALIGN) data[1]; | |
| }; /* struct json_obspage */ | |
| struct json_obstack { | |
| int refs; | |
| int flags; | |
| size_t blksize; | |
| LIST_HEAD(, json_obspage) pages; | |
| unsigned char *tp, *p, *pe; | |
| }; /* struct json_obstack */ | |
| static inline int json_o_blksize(size_t *size, size_t n, int flags, size_t blksize) { | |
| if (n > JSON_O_MAXBLOCK) | |
| return ENOMEM; | |
| if (n <= blksize) { | |
| *size = blksize; | |
| } else if (flags & JSON_F_EPHEMERAL) { | |
| *size = json_power2(n + JSON_O_SYSFUDGE) - JSON_O_SYSFUDGE; | |
| } else { | |
| *size = n; | |
| } | |
| return 0; | |
| } /* json_o_blksize() */ | |
| static struct json_obstack *json_o_open(int flags, size_t blksize, int *error) { | |
| struct json_obspage *page; | |
| struct json_obstack *obs; | |
| size_t size; | |
| if (flags & JSON_F_DEBUG) { | |
| /* | |
| * set to 0 here so json_o_blksize doesn't need to check for | |
| * JSON_F_DEBUG every invocation | |
| */ | |
| blksize = 0; | |
| flags &= ~JSON_F_EPHEMERAL; | |
| } else if (!blksize && (flags & JSON_F_EPHEMERAL)) { | |
| /* use our default block size if non-specified */ | |
| blksize = JSON_O_MINBLOCK; | |
| } | |
| /* | |
| * calculate the penalty once so json_o_blksize doesn't need to on | |
| * every invocation | |
| */ | |
| if (blksize > JSON_O_SYSFUDGE) | |
| blksize -= JSON_O_SYSFUDGE; | |
| if ((*error = json_add(&size, offsetof(struct json_obspage, data), sizeof *obs))) | |
| return NULL; | |
| if ((*error = json_o_blksize(&size, size, flags, blksize))) | |
| return NULL; | |
| if (!(page = malloc(size))) { | |
| *error = errno; | |
| return NULL; | |
| } | |
| page->size = size - offsetof(struct json_obspage, data); | |
| obs = (struct json_obstack *)page->data; | |
| obs->refs = 1; | |
| obs->flags = flags; | |
| obs->tp = &page->data[sizeof *obs]; | |
| obs->p = obs->tp; | |
| obs->pe = &page->data[page->size]; | |
| LIST_INIT(&obs->pages); | |
| LIST_INSERT_HEAD(&obs->pages, page, le); | |
| return obs; | |
| } /* json_o_open() */ | |
| static int json_o_check(struct json_obstack **obs, int flags, size_t blksize) { | |
| int error; | |
| if (!*obs && !(*obs = json_o_open(flags, blksize, &error))) | |
| return error; | |
| return 0; | |
| } /* json_o_check() */ | |
| static void json_o_close(struct json_obstack *obs) { | |
| struct json_obspage *page, *next; | |
| if (!obs || --obs->refs > 0) | |
| return; | |
| for (page = LIST_FIRST(&obs->pages); page; page = next) { | |
| next = LIST_NEXT(page, le); | |
| LIST_REMOVE(page, le); | |
| free(page); | |
| } | |
| } /* json_o_close() */ | |
| static void json_o_acquire(struct json_obstack *obs) { | |
| ++obs->refs; | |
| } /* json_o_acquire() */ | |
| static void json_o_align(struct json_obstack *obs) { | |
| uintptr_t off, diff; | |
| if (obs->p != LIST_FIRST(&obs->pages)->data) { | |
| if ((off = (uintptr_t)obs->p & JSON_O_ALIGN)) { | |
| diff = JSON_O_ALIGN - off; | |
| if (diff < (uintptr_t)(obs->pe - obs->p)) | |
| obs->p += diff; | |
| else | |
| obs->p = obs->pe; | |
| } | |
| } | |
| obs->tp = obs->p; | |
| } /* json_o_align() */ | |
| static int json_o_grow(struct json_obstack *obs, size_t n) { | |
| struct json_obspage *page, *tmp; | |
| size_t size, p; | |
| int error; | |
| if ((size_t)(obs->pe - obs->p) >= n) | |
| return 0; | |
| if ((error = json_add(&size, offsetof(struct json_obspage, data), obs->p - obs->tp))) | |
| return error; | |
| if ((error = json_add(&size, size, n))) | |
| return error; | |
| if ((error = json_o_blksize(&size, size, obs->flags, obs->blksize))) | |
| return error; | |
| p = (size_t)(obs->p - obs->tp); | |
| /* | |
| * resize the page if no other objects were allocated | |
| */ | |
| if (obs->tp == LIST_FIRST(&obs->pages)->data) { | |
| page = LIST_FIRST(&obs->pages); | |
| LIST_REMOVE(page, le); | |
| if (!(tmp = realloc(page, size))) { | |
| LIST_INSERT_HEAD(&obs->pages, page, le); | |
| return errno; | |
| } | |
| page = tmp; | |
| page->size = size - offsetof(struct json_obspage, data); | |
| } else { | |
| if (!(page = malloc(size))) | |
| return errno; | |
| page->size = size - offsetof(struct json_obspage, data); | |
| memcpy(page->data, obs->tp, obs->p - obs->tp); | |
| } | |
| obs->tp = page->data; | |
| obs->p = obs->tp + p; | |
| obs->pe = &page->data[page->size]; | |
| LIST_INSERT_HEAD(&obs->pages, page, le); | |
| return 0; | |
| } /* json_o_grow() */ | |
| static void json_o_freepage(struct json_obstack *obs, struct json_page *page) { | |
| struct json_page *next; | |
| if (page == LIST_FIRST(&obs->pages)) { | |
| next = LIST_NEXT(page, le); | |
| obs->tp = next->data[next->size]; | |
| obs->p = obs->tp; | |
| obs->pe = obs->tp; | |
| } | |
| LIST_REMOVE(page, le); | |
| free(page); | |
| } /* json_o_freepage() */ | |
| static void json_o_free(struct json_obstack *obs, void *p) { | |
| struct json_page *page; | |
| if (p && !(obs->flags & JSON_F_EPHEMERAL)) { | |
| page = (struct json_page *)((char *)p - offsetof(struct json_page, data)); | |
| json_o_freepage(obs, page); | |
| } | |
| } /* json_o_free() */ | |
| static int json_o_new(struct json_obstack *obs, size_t n) { | |
| if (obs->flags & JSON_F_EPHEMERAL) { | |
| json_o_align(obs); | |
| } else { | |
| /* | |
| * make sure tp will be at the top of the page so it can be | |
| * deallocated | |
| */ | |
| if (obs->p != LIST_FIRST(&obs->pages)->data) | |
| obs->tp = obs->p = obs->pe; | |
| } | |
| return json_o_grow(obs, n); | |
| } /* json_o_new() */ | |
| static int json_o_set(struct json_obstack *obs, size_t n) { | |
| int error; | |
| if (obs->p - obs->tp < n) { | |
| if ((error = json_o_grow(obs, n - (obs->p - obs->tp)))) | |
| return error; | |
| } | |
| obs->p = obs->tp + n; | |
| return 0; | |
| } /* json_o_set() */ | |
| static void json_o_undo(struct json_obstack *obs) { | |
| struct json_page *page; | |
| if (obs->flags & JSON_F_EPHEMERAL) { | |
| obs->p = obs->tp; | |
| } else { | |
| json_o_freepage(obs, LIST_FIRST(&obs->pages)); | |
| } | |
| } /* json_o_undo() */ | |
| static void *json_o_top(struct json_obstack *obs) { | |
| return obs->tp; | |
| } /* json_o_top() */ | |
| static size_t json_o_len(struct json_obstack *obs) { | |
| return obs->p - obs->tp; | |
| } /* json_o_len() */ | |
| static int json_o_putc(struct json_obstack *obs, int ch) { | |
| int error; | |
| if (!(obs->p < obs->pe) && (error = json_o_grow(obs, 1))) | |
| return error; | |
| *obs->p++ = ch; | |
| return 0; | |
| } /* json_o_putc() */ | |
| static int json_o_cat(struct json_obstack *obs, const void *src, size_t len) { | |
| int error; | |
| if ((error = json_o_grow(obs, len))) | |
| return error; | |
| memcpy(obs->p, src, len); | |
| obs->p += len; | |
| return 0; | |
| } /* json_o_cat() */ | |
| static void *json_o_get(struct json_obstack *obs, size_t n, int *error) { | |
| if ((*error = json_o_new(obs, n))) | |
| return NULL; | |
| obs->p += n; | |
| return obs->tp; | |
| } /* json_o_get() */ | |
| static void *json_o_dup(struct json_obstack *obs, const void *src, size_t len, int *error) { | |
| void *dst; | |
| if ((dst = json_o_get(obs, len, error))) | |
| memcpy(dst, src, len); | |
| return dst; | |
| } /* json_o_dup() */ | |
| /* | |
| * S T R I N G C O L L E C T I O N R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| #define JSON_S_MINLENGTH 32 | |
| struct json_string { | |
| LLRB_ENTRY(json_string) rbe; | |
| size_t refs; | |
| size_t size; | |
| char *text; | |
| }; /* struct json_string */ | |
| struct json_strings { | |
| int refs; | |
| int flags; | |
| struct json_obstack *obstack; | |
| LLRB_HEAD(json_cache, json_string) cache; | |
| }; /* struct json_strings */ | |
| static inline int json_strcmp(struct json_string *a, struct json_string *b) { | |
| int cmp; | |
| if ((cmp = strncmp(a->text, b->text, JSON_MIN(a->size, b->size)))) | |
| return cmp; | |
| return JSON_CMP(a->size, b->size); | |
| } /* json_strcmp() */ | |
| LLRB_GENERATE_STATIC(json_cache, json_string, rbe, json_strcmp) | |
| static struct json_strings *json_s_open4(struct json_strings *reuse, int flags, struct json_obstack *obstack, int *error) { | |
| struct json_strings *S; | |
| if (reuse) { | |
| ++reuse->refs; | |
| return reuse; | |
| } else if (obstack) { | |
| json_o_acquire(obstack); | |
| } else if (!(obstack = json_o_open(flags, 0, error))) { | |
| goto error; | |
| } | |
| if (!(S = json_o_get(obstack, sizeof *S, error))) | |
| goto error; | |
| S->refs = 1; | |
| S->flags = flags; | |
| S->obstack = obstack; | |
| LLRB_INIT(&S->cache); | |
| return S; | |
| error: | |
| json_o_close(obstack); | |
| return NULL; | |
| } /* json_s_open4() */ | |
| static void json_s_close(struct json_strings *S) { | |
| if (!S || --S->refs > 0) | |
| return; | |
| json_o_close(S->obstack); | |
| } /* json_s_close() */ | |
| static int json_s_new(struct json_strings *S) { | |
| int error; | |
| if (S->) | |
| if ((error = json_o_new(S->obstack, sizeof (struct json_string) + JSON_S_MINLENGTH, &error))) | |
| return error; | |
| return json_o_set(S->obstack, sizeof (struct json_string)); | |
| } /* json_s_new() */ | |
| static int json_s_cat(struct json_strings *S, const void *src, size_t len) { | |
| return json_o_cat(S->obstack, src, len); | |
| } /* json_s_cat() */ | |
| static int json_s_putc(struct json_strings *S, int ch) { | |
| return json_o_putc(S->obstack, ch); | |
| } /* json_s_putc() */ | |
| static int json_s_putw(struct json_strings *S, int ch) { | |
| char seq[4]; | |
| int len; | |
| if (ch < 0x80) | |
| return json_s_putc(S, ch); | |
| if (ch < 0x800) { | |
| seq[0] = 0xc0 | (0x1f & (ch >> 6)); | |
| seq[1] = 0x80 | (0x3f & ch); | |
| len = 2; | |
| } else if (ch < 0x10000) { | |
| seq[0] = 0xe0 | (0x0f & (ch >> 12)); | |
| seq[1] = 0x80 | (0x3f & (ch >> 6)); | |
| seq[2] = 0x80 | (0x3f & ch); | |
| len = 3; | |
| } else { | |
| seq[0] = 0xf0 | (0x07 & (ch >> 18)); | |
| seq[1] = 0x80 | (0x3f & (ch >> 12)); | |
| seq[2] = 0x80 | (0x3f & (ch >> 6)); | |
| seq[3] = 0x80 | (0x3f & ch); | |
| len = 4; | |
| } | |
| return json_s_cat(S, seq, len); | |
| } /* json_s_putw() */ | |
| static struct json_string *json_s_end(struct json_strings *S, int *error) { | |
| struct json_string *str, *old; | |
| if ((*error = json_s_putc(S, '\0'))) | |
| return NULL; | |
| str = (struct json_string *)json_o_top(S->obstack); | |
| str->size = json_o_len(S->obstack) - sizeof *str - 1; | |
| str->text = (char *)str + sizeof *str; | |
| if (!(old = LLRB_INSERT(json_cache, &S->cache, str))) { | |
| json_o_undo(S->obstack); | |
| return old; | |
| } else { | |
| return str; | |
| } | |
| } /* json_s_end() */ | |
| static struct json_string *json_s_add(struct json_strings *S, void *src, size_t len, int *_error) { | |
| int error; | |
| } /* json_s_add() */ | |
| /* | |
| * M E M O R Y P O O L R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| struct json_memory { | |
| int flags; | |
| struct json_obstack *obstack; /* general purpose objects */ | |
| struct json_obstack *tokens; /* parsing tokens only */ | |
| struct json_strings *keys; /* object key name cache */ | |
| struct json_strings *values; /* string value cache */ | |
| }; /* struct json_memory */ | |
| static void json_m_destroy(struct json_memory *M) { | |
| struct json_obstack *obstack; | |
| json_s_close(M->values); | |
| M->values = NULL; | |
| json_s_close(M->keys); | |
| M->keys = NULL; | |
| json_obsclose(M->tokens); | |
| M->tokens = NULL; | |
| obstack = M->obstack; | |
| M->obstack = NULL; | |
| json_obsclose(obstack); | |
| } /* json_m_destroy() */ | |
| static int json_m_init(struct json_memory *M, const struct json_options *opts) { | |
| int error = 0; | |
| memset(M, 0, sizeof *M); | |
| M->flags = opts->flags; | |
| if (!(M->obstack = json_obsopen(opts->flags, &error))) | |
| goto error; | |
| if (!(M->keys = json_s_open4(opts->keys, opts->flags, obstack, &error))) | |
| goto error; | |
| if (!(M->values = json_s_open4(opts->values, opts->flags, obstack, &error))) | |
| goto error; | |
| return 0; | |
| error: | |
| json_m_destroy(M); | |
| return error; | |
| } /* json_m_init() */ | |
| static void json_m_move(struct json_memory *dst, struct json_memory *src) { | |
| dst->obstack = src->obstack; | |
| src->obstack = NULL; | |
| dst->tokens = src->tokens; | |
| src->tokens = NULL; | |
| dst->keys = src->keys; | |
| src->keys = NULL; | |
| dst->values = src->values; | |
| src->values = NULL; | |
| } /* json_m_move() */ | |
| /* | |
| * S T R I N G R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| #if 0 | |
| struct string { | |
| size_t limit; | |
| size_t length; | |
| char *text; | |
| }; /* struct string */ | |
| static const struct string nullstring = { 0, 0, "" }; | |
| #define string_fake(text, len) (&(struct string){ 0, (len), (char *)(text) }) | |
| static void string_init(struct string **S) { | |
| *S = (struct string *)&nullstring; | |
| } /* string_init() */ | |
| static void string_destroy(struct string **S) { | |
| if (*S != &nullstring) { | |
| free(*S); | |
| string_init(S); | |
| } | |
| } /* string_destroy() */ | |
| static void string_reset(struct string **S) { | |
| if (!*S) | |
| string_init(S); | |
| else if (*S != &nullstring) | |
| (*S)->length = 0; | |
| } /* string_reset() */ | |
| static void string_move(struct string **dst, struct string **src) { | |
| *dst = *src; | |
| *src = (struct string *)&nullstring; | |
| } /* string_move() */ | |
| static int string_grow(struct string **S, size_t len) { | |
| size_t size, need, limit; | |
| struct string *tmp; | |
| if ((*S)->limit - (*S)->length > len) | |
| return 0; | |
| if (~len < (*S)->length + 1) | |
| return ENOMEM; | |
| limit = (*S)->length + 1 + len; | |
| if (~sizeof **S < limit) | |
| return ENOMEM; | |
| need = sizeof **S + limit; | |
| size = sizeof **S + (*S)->limit; | |
| while (size < need) { | |
| if (~size < size) | |
| return ENOMEM; | |
| size *= 2; | |
| } | |
| if (*S == &nullstring) { | |
| if (!(tmp = malloc(size))) | |
| return errno; | |
| memset(tmp, 0, size); | |
| } else if (!(tmp = realloc(*S, size))) | |
| return errno; | |
| *S = tmp; | |
| (*S)->limit = size - sizeof **S; | |
| (*S)->text = (char *)*S + sizeof **S; | |
| memset(&(*S)->text[(*S)->length], 0, (*S)->limit - (*S)->length); | |
| return 0; | |
| } /* string_grow() */ | |
| static int string_cats(struct string **S, const void *src, size_t len) { | |
| int error; | |
| if ((error = string_grow(S, len))) | |
| return error; | |
| memcpy(&(*S)->text[(*S)->length], src, len); | |
| (*S)->length += len; | |
| return 0; | |
| } /* string_cats() */ | |
| static int string_putc(struct string **S, int ch) { | |
| int error; | |
| if ((error = string_grow(S, 1))) | |
| return error; | |
| (*S)->text[(*S)->length++] = ch; | |
| return 0; | |
| } /* string_putc() */ | |
| static int string_putw(struct string **S, int ch) { | |
| char seq[4]; | |
| int len; | |
| if (ch < 0x80) | |
| return string_putc(S, ch); | |
| if (ch < 0x800) { | |
| seq[0] = 0xc0 | (0x1f & (ch >> 6)); | |
| seq[1] = 0x80 | (0x3f & ch); | |
| len = 2; | |
| } else if (ch < 0x10000) { | |
| seq[0] = 0xe0 | (0x0f & (ch >> 12)); | |
| seq[1] = 0x80 | (0x3f & (ch >> 6)); | |
| seq[2] = 0x80 | (0x3f & ch); | |
| len = 3; | |
| } else { | |
| seq[0] = 0xf0 | (0x07 & (ch >> 18)); | |
| seq[1] = 0x80 | (0x3f & (ch >> 12)); | |
| seq[2] = 0x80 | (0x3f & (ch >> 6)); | |
| seq[3] = 0x80 | (0x3f & ch); | |
| len = 4; | |
| } | |
| return string_cats(S, seq, len); | |
| } /* string_putw() */ | |
| #endif | |
| /* | |
| * L E X E R R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| struct token { | |
| enum tokens { | |
| T_BEGIN_ARRAY, | |
| T_END_ARRAY, | |
| T_BEGIN_OBJECT, | |
| T_END_OBJECT, | |
| T_NAME_SEPARATOR, | |
| T_VALUE_SEPARATOR, | |
| T_STRING, | |
| T_NUMBER, | |
| T_BOOLEAN, | |
| T_NULL, | |
| } type; | |
| CIRCLEQ_ENTRY(token) cqe; | |
| union { | |
| struct string *string; | |
| double number; | |
| _Bool boolean; | |
| }; | |
| }; /* struct token */ | |
| static const enum json_type lex_typemap[] = { | |
| [T_BEGIN_ARRAY] = JSON_T_ARRAY, | |
| [T_END_ARRAY] = JSON_T_NULL, | |
| [T_BEGIN_OBJECT] = JSON_T_OBJECT, | |
| [T_END_OBJECT] = JSON_T_NULL, | |
| [T_NAME_SEPARATOR] = JSON_T_NULL , | |
| [T_VALUE_SEPARATOR] = JSON_T_NULL, | |
| [T_STRING] = JSON_T_STRING, | |
| [T_NUMBER] = JSON_T_NUMBER, | |
| [T_BOOLEAN] = JSON_T_BOOLEAN, | |
| [T_NULL] = JSON_T_NULL, | |
| }; /* lex_typemap[] */ | |
| JSON_NOTUSED static const char *lex_strtype(enum tokens type) { | |
| static const char name[][16] = { | |
| [T_BEGIN_ARRAY] = "begin-array", | |
| [T_END_ARRAY] = "end-array", | |
| [T_BEGIN_OBJECT] = "begin-object", | |
| [T_END_OBJECT] = "end-object", | |
| [T_NAME_SEPARATOR] = "name-separator", | |
| [T_VALUE_SEPARATOR] = "value-separator", | |
| [T_STRING] = "string", | |
| [T_NUMBER] = "number", | |
| [T_BOOLEAN] = "boolean", | |
| [T_NULL] = "null", | |
| }; | |
| return name[type]; | |
| } /* lex_strtype() */ | |
| struct lexer { | |
| struct json_memory *memory; | |
| struct json_strings *string; | |
| unsigned char stack[128]; | |
| size_t depth; | |
| void *state; | |
| struct { | |
| unsigned pos; | |
| unsigned row; | |
| unsigned col; | |
| } cursor; | |
| CIRCLEQ_HEAD(, token) tokens; | |
| struct token *token; | |
| // struct string *string; | |
| char *sp, *pe; | |
| int i, code, high; | |
| char number[64]; | |
| int error; | |
| }; /* struct lexer */ | |
| static void lex_init(struct lexer *L, struct json_memory *memory) { | |
| memset(L, 0, sizeof *L); | |
| L->memory = memory; | |
| L->cursor.row = 1; | |
| CIRCLEQ_INIT(&L->tokens); | |
| string_init(&L->string); | |
| } /* lex_init() */ | |
| static void tok_free(struct token *T) { | |
| if (T->type == T_STRING) | |
| string_destroy(&T->string); | |
| free(T); | |
| } /* tok_free() */ | |
| static void lex_destroy(struct lexer *L) { | |
| struct token *T; | |
| while (!CIRCLEQ_EMPTY(&L->tokens)) { | |
| T = CIRCLEQ_FIRST(&L->tokens); | |
| CIRCLEQ_REMOVE(&L->tokens, T, cqe); | |
| tok_free(T); | |
| } /* while (tokens) */ | |
| string_destroy(&L->string); | |
| } /* lex_destroy() */ | |
| /* | |
| * Helper routines for switching between key and value string collections. | |
| */ | |
| static int lex_up(struct lexer *L, enum tokens begin) { | |
| if (L->depth >= json_countof(L->stack) - 1) | |
| return JSON_ETOODEEP; | |
| L->stack[++L->depth] = begin; | |
| return 0; | |
| } /* lex_up() */ | |
| static int lex_down(struct lexer *L, enum tokens expect) { | |
| if (L->depth == 0) | |
| return JSON_ESYNTAX; | |
| if (L->stack[L->depth--] != expect) | |
| return JSON_ESYNTAX; | |
| if (L->stack[L->depth] == T_BEGIN_OBJECT) | |
| L->strings = L->memory->keys; | |
| else | |
| L->strings = L->memory->values; | |
| return 0; | |
| } /* lex_down() */ | |
| static int lex_push(struct lexer *L, enum tokens type, ...) { | |
| struct token *T; | |
| va_list ap; | |
| int error; | |
| if (!(T = json_make0(sizeof *T, &error))) | |
| return error; | |
| T->type = type; | |
| switch (type) { | |
| case T_BEGIN_ARRAY: | |
| L->strings = L->memory->values; | |
| if ((error = lex_up(L, T_BEGIN_ARRAY))) | |
| return error; | |
| break; | |
| case T_END_ARRAY: | |
| if ((error = lex_down(L, T_BEGIN_ARRAY))) | |
| return error; | |
| break; | |
| case T_BEGIN_OBJECT: | |
| L->strings = L->memory->keys; | |
| if ((error = lex_up(L, T_BEGIN_OBJECT))) | |
| return error; | |
| break; | |
| case T_END_OBJECT: | |
| if ((error = lex_down(L, T_BEGIN_OBJECT))) | |
| return error; | |
| break; | |
| case T_NAME_SEPERATOR: | |
| L->strings = L->memory->values; | |
| break; | |
| case T_VALUE_SEPERATOR: | |
| if (L->stack[L->depth] == T_BEGIN_OBJECT) | |
| L->strings = L->memory->keys; | |
| else | |
| L->strings = L->memory->values; | |
| break; | |
| case T_STRING: | |
| va_start(ap, type); | |
| T->string = va_arg(ap, struct string *); | |
| va_end(ap); | |
| break; | |
| case T_NUMBER: | |
| va_start(ap, type); | |
| T->number = va_arg(ap, double); | |
| va_end(ap); | |
| break; | |
| case T_BOOLEAN: | |
| va_start(ap, type); | |
| T->boolean = va_arg(ap, int); | |
| va_end(ap); | |
| break; | |
| default: | |
| break; | |
| } /* switch() */ | |
| CIRCLEQ_INSERT_TAIL(&L->tokens, T, cqe); | |
| L->token = T; | |
| return 0; | |
| } /* lex_push() */ | |
| static void lex_newnum(struct lexer *L) { | |
| L->sp = L->number; | |
| L->pe = &L->number[sizeof L->number - 1]; | |
| } /* lex_newnum() */ | |
| static inline _Bool lex_isnum(int ch) { | |
| return json_isnumber(ch); | |
| } /* lex_isnum() */ | |
| static int lex_catnum(struct lexer *L, int ch) { | |
| if (L->sp < L->pe) { | |
| *L->sp++ = ch; | |
| return 0; | |
| } else | |
| return EOVERFLOW; | |
| } /* lex_catnum() */ | |
| static int lex_pushnum(struct lexer *L) { | |
| double number; | |
| char *end; | |
| *L->sp = '\0'; | |
| number = strtod(L->number, &end); | |
| if (number == 0) { | |
| if (end == L->number) | |
| return JSON_ELEXICAL; | |
| if (errno == ERANGE) | |
| return ERANGE; | |
| } else if (number == HUGE_VAL && errno == ERANGE) { | |
| return ERANGE; | |
| } else if (*end != '\0') | |
| return JSON_ELEXICAL; | |
| return lex_push(L, T_NUMBER, number); | |
| } /* lex_pushnum() */ | |
| static inline _Bool lex_ishigh(int code) { | |
| return (code >= 0xD800 && code <= 0xDBFF); | |
| } /* lex_ishigh() */ | |
| static inline _Bool lex_islow(int code) { | |
| return (code >= 0xDC00 && code <= 0xDFFF); | |
| } /* lex_islow() */ | |
| static int lex_frompair(int hi, int lo) { | |
| return (hi << 10) + lo + (0x10000 - (0xD800 << 10) - 0xDC00); | |
| } /* lex_frompair() */ | |
| #define resume() do { \ | |
| if (L->state) \ | |
| goto *L->state; \ | |
| } while (0) | |
| #define popchar() do { \ | |
| JSON_XPASTE(L, __LINE__): \ | |
| if (p >= pe) { L->state = &&JSON_XPASTE(L, __LINE__); return 0; } \ | |
| ch = *p++; \ | |
| L->cursor.pos++; \ | |
| L->cursor.col++; \ | |
| } while (0) | |
| #define ungetchar() do { \ | |
| p--; \ | |
| L->cursor.pos--; \ | |
| L->cursor.col--; \ | |
| } while (0) | |
| #define pushtoken(...) do { \ | |
| if ((error = lex_push(L, __VA_ARGS__))) \ | |
| goto error; \ | |
| } while (0) | |
| #define expect(c) do { popchar(); if (ch != (c)) goto invalid; } while (0) | |
| static int lex_parse(struct lexer *L, const void *src, size_t len) { | |
| const unsigned char *p = src, *pe = p + len; | |
| int ch, error; | |
| resume(); | |
| if ((error = json_obsprep(&L->memory->tokens, L->memory->flags))) | |
| return error; | |
| L->strings = L->memory->values; | |
| start: | |
| popchar(); | |
| switch (ch) { | |
| case ' ': case '\t': case '\r': | |
| break; | |
| case '\n': | |
| L->cursor.row++; | |
| L->cursor.col = 0; | |
| break; | |
| case '[': | |
| pushtoken(T_BEGIN_ARRAY); | |
| break; | |
| case ']': | |
| pushtoken(T_END_ARRAY); | |
| break; | |
| case '{': | |
| pushtoken(T_BEGIN_OBJECT); | |
| break; | |
| case '}': | |
| pushtoken(T_END_OBJECT); | |
| break; | |
| case ':': | |
| pushtoken(T_NAME_SEPARATOR); | |
| break; | |
| case ',': | |
| pushtoken(T_VALUE_SEPARATOR); | |
| break; | |
| case '"': | |
| goto string; | |
| case '+': case '-': case '.': | |
| case '0': case '1': case '2': case '3': case '4': | |
| case '5': case '6': case '7': case '8': case '9': | |
| ungetchar(); | |
| goto number; | |
| case 'n': | |
| goto null; | |
| case 't': | |
| goto btrue; | |
| case 'f': | |
| goto bfalse; | |
| default: | |
| goto invalid; | |
| } /* switch (ch) */ | |
| goto start; | |
| string: | |
| string_reset(&L->string); | |
| for (;;) { | |
| popchar(); | |
| switch (ch) { | |
| case '"': | |
| goto endstr; | |
| case '\\': | |
| popchar(); | |
| switch (ch) { | |
| case '"': | |
| case '/': | |
| case '\\': | |
| goto catstr; | |
| case 'b': | |
| ch = '\b'; | |
| goto catstr; | |
| case 'f': | |
| ch = '\f'; | |
| goto catstr; | |
| case 'n': | |
| ch = '\n'; | |
| goto catstr; | |
| case 'r': | |
| ch = '\r'; | |
| goto catstr; | |
| case 't': | |
| ch = '\t'; | |
| goto catstr; | |
| case 'u': | |
| L->i = 0; | |
| L->code = 0; | |
| while (L->i++ < 4) { | |
| popchar(); | |
| if (json_isdigit(ch)) { | |
| L->code <<= 4; | |
| L->code += ch - '0'; | |
| } else if (ch >= 'A' && ch <= 'F') { | |
| L->code <<= 4; | |
| L->code += 10 + (ch - 'A'); | |
| } else if (ch >= 'a' && ch <= 'f') { | |
| L->code <<= 4; | |
| L->code += 10 + (ch - 'a'); | |
| } else | |
| goto invalid; | |
| } /* while() */ | |
| if (lex_ishigh(L->code)) { | |
| L->high = L->code; | |
| break; | |
| } else if (lex_islow(L->code)) { | |
| L->code = lex_frompair(L->high, L->code); | |
| L->high = 0; | |
| } | |
| if ((error = string_putw(&L->string, L->code))) | |
| goto error; | |
| break; | |
| default: | |
| goto invalid; | |
| } /* switch() */ | |
| break; | |
| default: | |
| catstr: | |
| if ((error = string_putc(&L->string, ch))) | |
| goto error; | |
| break; | |
| } /* switch() */ | |
| } /* for() */ | |
| endstr: | |
| pushtoken(T_STRING, L->string); | |
| string_init(&L->string); | |
| goto start; | |
| number: | |
| lex_newnum(L); | |
| popchar(); | |
| while (lex_isnum(ch)) { | |
| if ((error = lex_catnum(L, ch))) | |
| goto error; | |
| popchar(); | |
| } | |
| ungetchar(); | |
| if ((error = lex_pushnum(L))) | |
| goto error; | |
| goto start; | |
| null: | |
| expect('u'); | |
| expect('l'); | |
| expect('l'); | |
| pushtoken(T_NULL); | |
| goto start; | |
| btrue: | |
| expect('r'); | |
| expect('u'); | |
| expect('e'); | |
| pushtoken(T_BOOLEAN, 1); | |
| goto start; | |
| bfalse: | |
| expect('a'); | |
| expect('l'); | |
| expect('s'); | |
| expect('e'); | |
| pushtoken(T_BOOLEAN, 0); | |
| goto start; | |
| invalid: | |
| if (json_isgraph(ch)) | |
| fprintf(stderr, "invalid char (%c) at line %u, column %u\n", ch, L->cursor.row, L->cursor.col); | |
| else | |
| fprintf(stderr, "invalid char (0x%.2x) at line %u, column %u\n", ch, L->cursor.row, L->cursor.col); | |
| error = JSON_ELEXICAL; | |
| goto error; | |
| error: | |
| L->error = error; | |
| L->state = &&failed; | |
| failed: | |
| return L->error; | |
| } /* lex_parse() */ | |
| /* | |
| * V A L U E R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| struct node { | |
| union { | |
| struct json_value *key; | |
| json_index_t index; | |
| }; | |
| struct json_value *value; | |
| struct json_value *parent; | |
| union { | |
| LLRB_ENTRY(node) rbe; | |
| CIRCLEQ_ENTRY(node) cqe; | |
| }; | |
| }; /* struct node */ | |
| struct json_value { | |
| enum json_type type; | |
| struct node *node; | |
| union { /* mutually exclusive usage */ | |
| struct json_value *root; | |
| void *state; | |
| }; | |
| union { | |
| struct { | |
| LLRB_HEAD(array, node) nodes; | |
| json_index_t count; | |
| } array; | |
| struct { | |
| LLRB_HEAD(object, node) nodes; | |
| json_index_t count; | |
| } object; | |
| struct string *string; | |
| double number; | |
| _Bool boolean; | |
| }; | |
| }; /* struct json_value */ | |
| static int array_cmp(struct node *a, struct node *b) { | |
| return JSON_CMP(a->index, b->index); | |
| } /* array_cmp() */ | |
| LLRB_GENERATE_STATIC(array, node, rbe, array_cmp) | |
| /* NOTE: All keys must be strings per RFC 4627. */ | |
| static int object_cmp(struct node *a, struct node *b) { | |
| int cmp; | |
| if ((cmp = strncmp(a->key->string->text, b->key->string->text, JSON_MIN(a->key->string->length, b->key->string->length)))) | |
| return cmp; | |
| return JSON_CMP(a->key->string->length, b->key->string->length); | |
| } /* object_cmp() */ | |
| LLRB_GENERATE_STATIC(object, node, rbe, object_cmp) | |
| static int value_init(struct json_value *V, enum json_type type, struct token *T) { | |
| memset(V, 0, sizeof *V); | |
| V->type = type; | |
| switch (type) { | |
| case JSON_T_STRING: | |
| if (T) { | |
| string_move(&V->string, &T->string); | |
| } else { | |
| string_init(&V->string); | |
| } | |
| break; | |
| case JSON_T_NUMBER: | |
| V->number = (T)? T->number : 0.0; | |
| break; | |
| case JSON_T_BOOLEAN: | |
| V->boolean = (T)? T->boolean : 0; | |
| break; | |
| default: | |
| break; | |
| } /* switch() */ | |
| return 0; | |
| } /* value_init() */ | |
| static struct json_value *value_open(enum json_type type, struct token *T, int *error) { | |
| struct json_value *V; | |
| if (!(V = json_make(sizeof *V, error)) | |
| || (*error = value_init(V, type, T))) | |
| return 0; | |
| return V; | |
| } /* value_open() */ | |
| static void value_close(struct json_value *, _Bool); | |
| static int array_push(struct json_value *A, struct json_value *V) { | |
| struct node *N; | |
| int error; | |
| if (!(N = json_make(sizeof *N, &error))) | |
| return error; | |
| N->index = A->array.count++; | |
| N->value = V; | |
| N->parent = A; | |
| V->node = N; | |
| V->root = NULL; | |
| LLRB_INSERT(array, &A->array.nodes, N); | |
| return 0; | |
| } /* array_push() */ | |
| static int array_insert(struct json_value *A, int index, struct json_value *V) { | |
| struct node *N, *prev; | |
| int error; | |
| if (index < 0) | |
| index = A->array.count - index; | |
| if (index < 0 || index >= A->array.count) | |
| return array_push(A, V); | |
| if (!(N = json_make(sizeof *N, &error))) | |
| return error; | |
| N->index = index; | |
| N->value = V; | |
| N->parent = A; | |
| V->node = N; | |
| V->root = NULL; | |
| if (!(prev = LLRB_INSERT(array, &A->array.nodes, N))) | |
| return 0; | |
| free(N); | |
| value_close(prev->value, 0); | |
| prev->value = V; | |
| V->node = prev; | |
| return 0; | |
| } /* array_insert() */ | |
| static struct json_value *array_index(struct json_value *A, int index) { | |
| struct node node, *result; | |
| if (index < 0) { | |
| index = A->array.count - index; | |
| if (index < 0) | |
| return NULL; | |
| } | |
| node.index = index; | |
| if ((result = LLRB_FIND(array, &A->array.nodes, &node))) | |
| return result->value; | |
| return NULL; | |
| } /* array_index() */ | |
| static int object_insert(struct json_value *O, struct json_value *K, struct json_value *V) { | |
| struct node *N, *prev; | |
| int error; | |
| if (!(N = json_make(sizeof *N, &error))) | |
| return error; | |
| N->key = K; | |
| N->value = V; | |
| N->parent = O; | |
| K->node = N; | |
| V->node = N; | |
| V->root = NULL; | |
| O->object.count++; | |
| if (!(prev = LLRB_INSERT(object, &O->object.nodes, N))) | |
| return 0; | |
| free(N); | |
| value_close(prev->key, 0); | |
| value_close(prev->value, 0); | |
| prev->key = K; | |
| prev->value = V; | |
| K->node = prev; | |
| V->node = prev; | |
| return 0; | |
| } /* object_insert() */ | |
| static struct json_value *object_search(struct json_value *O, const void *name, size_t len) { | |
| struct json_value key; | |
| struct node node, *result; | |
| key.type = JSON_T_STRING; | |
| key.string = string_fake(name, len); | |
| node.key = &key; | |
| if ((result = LLRB_FIND(object, &O->object.nodes, &node))) | |
| return result->value; | |
| return NULL; | |
| } /* object_search() */ | |
| CIRCLEQ_HEAD(orphans, node); | |
| static void array_remove(struct json_value *V, struct node *N, struct orphans *indices) { | |
| LLRB_REMOVE(array, &V->array.nodes, N); | |
| N->parent = 0; | |
| CIRCLEQ_INSERT_TAIL(indices, N, cqe); | |
| } /* array_remove() */ | |
| static void array_clear(struct json_value *V, struct orphans *indices) { | |
| struct node *N, *nxt; | |
| for (N = LLRB_MIN(array, &V->array.nodes); N; N = nxt) { | |
| nxt = LLRB_NEXT(array, &V->array.nodes, N); | |
| array_remove(V, N, indices); | |
| } | |
| } /* array_clear() */ | |
| static void object_remove(struct json_value *V, struct node *N, struct orphans *keys) { | |
| LLRB_REMOVE(object, &V->object.nodes, N); | |
| N->parent = 0; | |
| V->object.count--; | |
| CIRCLEQ_INSERT_TAIL(keys, N, cqe); | |
| } /* object_remove() */ | |
| static void object_clear(struct json_value *V, struct orphans *keys) { | |
| struct node *N, *nxt; | |
| for (N = LLRB_MIN(object, &V->object.nodes); N; N = nxt) { | |
| nxt = LLRB_NEXT(object, &V->array.nodes, N); | |
| object_remove(V, N, keys); | |
| } | |
| } /* object_clear() */ | |
| static void value_clear(struct json_value *V, struct orphans *indices, struct orphans *keys) { | |
| switch (V->type) { | |
| case JSON_T_ARRAY: | |
| array_clear(V, indices); | |
| break; | |
| case JSON_T_OBJECT: | |
| object_clear(V, keys); | |
| break; | |
| case JSON_T_STRING: | |
| string_reset(&V->string); | |
| break; | |
| case JSON_T_BOOLEAN: | |
| V->boolean = 0; | |
| break; | |
| case JSON_T_NUMBER: | |
| V->number = 0.0; | |
| break; | |
| default: | |
| break; | |
| } /* switch() */ | |
| } /* value_clear() */ | |
| static void node_remove(struct node *N, struct orphans *indices, struct orphans *keys) { | |
| if (N->parent->type == JSON_T_ARRAY) | |
| array_remove(N->parent, N, indices); | |
| else | |
| object_remove(N->parent, N, keys); | |
| } /* node_remove() */ | |
| static void value_destroy(struct json_value *V, struct orphans *indices, struct orphans *keys) { | |
| value_clear(V, indices, keys); | |
| if (V->type == JSON_T_STRING) | |
| string_destroy(&V->string); | |
| } /* value_destroy() */ | |
| static void orphans_free(struct orphans *indices, struct orphans *keys) { | |
| struct node *N; | |
| do { | |
| while (!CIRCLEQ_EMPTY(indices)) { | |
| N = CIRCLEQ_FIRST(indices); | |
| CIRCLEQ_REMOVE(indices, N, cqe); | |
| value_destroy(N->value, indices, keys); | |
| free(N->value); | |
| free(N); | |
| } | |
| while (!CIRCLEQ_EMPTY(keys)) { | |
| N = CIRCLEQ_FIRST(keys); | |
| CIRCLEQ_REMOVE(keys, N, cqe); | |
| value_destroy(N->key, indices, keys); | |
| free(N->key); | |
| value_destroy(N->value, indices, keys); | |
| free(N->value); | |
| free(N); | |
| } | |
| } while (!CIRCLEQ_EMPTY(indices) || !CIRCLEQ_EMPTY(keys)); | |
| } /* orphans_free() */ | |
| static void value_close(struct json_value *V, _Bool node) { | |
| struct orphans indices, keys; | |
| if (!V) | |
| return; | |
| CIRCLEQ_INIT(&indices); | |
| CIRCLEQ_INIT(&keys); | |
| value_destroy(V, &indices, &keys); | |
| if (V->node && node) { | |
| node_remove(V->node, &indices, &keys); | |
| } else { | |
| free(V); | |
| } | |
| orphans_free(&indices, &keys); | |
| } /* value_close() */ | |
| static _Bool value_issimple(struct json_value *V) { | |
| return V->type != JSON_T_ARRAY && V->type != JSON_T_OBJECT; | |
| } /* value_issimple() */ | |
| static _Bool value_iskey(struct json_value *V) { | |
| return (V->node && V->node->parent->type == JSON_T_OBJECT && V->node->key == V); | |
| } /* value_iskey() */ | |
| static _Bool value_isvalue(struct json_value *V) { | |
| return (V->node && V->node->parent->type == JSON_T_OBJECT && V->node->value == V); | |
| } /* value_isvalue() */ | |
| static int value_convert(struct json_value *V, enum json_type type) { | |
| struct orphans indices, keys; | |
| struct json_value *R; | |
| struct node *N; | |
| int error; | |
| if (V->type == type) | |
| return 0; | |
| if (value_iskey(V)) | |
| return JSON_EASSERT; | |
| R = V->root; | |
| N = V->node; | |
| CIRCLEQ_INIT(&indices); | |
| CIRCLEQ_INIT(&keys); | |
| value_destroy(V, &indices, &keys); | |
| orphans_free(&indices, &keys); | |
| error = value_init(V, type, NULL); | |
| V->node = N; | |
| V->root = R; | |
| return error; | |
| } /* value_convert() */ | |
| static double value_number(struct json_value *V) { | |
| return (V && V->type == JSON_T_NUMBER)? V->number : 0.0; | |
| } /* value_number() */ | |
| static const char *value_string(struct json_value *V) { | |
| return (V && V->type == JSON_T_STRING)? V->string->text : ""; | |
| } /* value_string() */ | |
| static double value_length(struct json_value *V) { | |
| return (V && V->type == JSON_T_STRING)? V->string->length : 0; | |
| } /* value_length() */ | |
| static double value_count(struct json_value *V) { | |
| switch ((V)? V->type : JSON_T_NULL) { | |
| case JSON_T_ARRAY: | |
| return V->array.count; | |
| case JSON_T_OBJECT: | |
| return V->object.count; | |
| default: | |
| return 0; | |
| } | |
| } /* value_count() */ | |
| static _Bool value_boolean(struct json_value *V) { | |
| if (!V) | |
| return 0; | |
| if (V->type == JSON_T_BOOLEAN) | |
| return V->boolean; | |
| switch (V->type) { | |
| case JSON_T_NUMBER: | |
| return isnormal(V->number); | |
| case JSON_T_ARRAY: | |
| return !!V->array.count; | |
| case JSON_T_OBJECT: | |
| return !!V->object.count; | |
| default: | |
| return 0; | |
| } /* switch() */ | |
| } /* value_boolean() */ | |
| #if 0 | |
| static struct json_value *value_search(struct json_value *J, const void *name, size_t len) { | |
| struct json_value key; | |
| switch (J->type) { | |
| case JSON_V_OBJECT: | |
| key.type = JSON_V_STRING; | |
| key.string = string_fake(name, len); | |
| return | |
| } else | |
| return NULL; | |
| } /* value_search() */ | |
| #endif | |
| static struct json_value *value_parent(struct json_value *V) { | |
| return (V->node)? V->node->parent : NULL; | |
| } /* value_parent() */ | |
| static struct json_value *value_root(struct json_value *top) { | |
| struct json_value *nxt; | |
| while (top && (nxt = value_parent(top))) | |
| top = nxt; | |
| return top; | |
| } /* value_root() */ | |
| static struct json_value *value_descend(struct json_value *V) { | |
| struct node *N; | |
| if (V->type == JSON_T_ARRAY) { | |
| if ((N = LLRB_MIN(array, &V->array.nodes))) | |
| return N->value; | |
| } else if (V->type == JSON_T_OBJECT) { | |
| if ((N = LLRB_MIN(object, &V->object.nodes))) | |
| return N->key; | |
| } | |
| return 0; | |
| } /* value_descend() */ | |
| static struct node *node_next(struct node *N) { | |
| if (N->parent->type == JSON_T_ARRAY) | |
| return LLRB_NEXT(array, &N->parent->array.nodes, N); | |
| else | |
| return LLRB_NEXT(object, &N->parent->object.nodes, N); | |
| } /* node_next() */ | |
| static struct json_value *value_adjacent(struct json_value *V) { | |
| struct node *N; | |
| if (V->node && (N = node_next(V->node))) { | |
| if (N->parent->type == JSON_T_ARRAY) | |
| return N->value; | |
| else | |
| return N->key; | |
| } | |
| return 0; | |
| } /* value_adjacent() */ | |
| #define ORDER_PRE JSON_I_PREORDER | |
| #define ORDER_POST JSON_I_POSTORDER | |
| static struct json_value *value_next(struct json_value *V, int *order, int *depth) { | |
| struct json_value *nxt; | |
| if (!*order) { | |
| *order = ORDER_PRE; | |
| *depth = 0; | |
| return V; | |
| } else if ((*order & ORDER_PRE) && (V->type == JSON_T_ARRAY || V->type == JSON_T_OBJECT)) { | |
| if ((nxt = value_descend(V))) { | |
| ++*depth; | |
| return nxt; | |
| } | |
| *order = ORDER_POST; | |
| return V; | |
| } else if (!V->node) { | |
| return NULL; | |
| } else if (value_iskey(V)) { | |
| *order = ORDER_PRE; | |
| return V->node->value; | |
| } else if (*depth > 0 && (nxt = value_adjacent(V))) { | |
| *order = ORDER_PRE; | |
| return nxt; | |
| } else if (*depth > 0) { | |
| *order = ORDER_POST; | |
| --*depth; | |
| return V->node->parent; | |
| } else { | |
| return NULL; | |
| } | |
| } /* value_next() */ | |
| /* | |
| * P R I N T E R R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| struct printer { | |
| int flags, state, sstate, order, error; | |
| struct json_value *value; | |
| int i, depth; | |
| char literal[64]; | |
| struct { | |
| char *p, *pe; | |
| } buffer; | |
| }; /* struct printer */ | |
| static struct json_value *print_root(struct json_value *root, int flags) { | |
| return (flags & JSON_F_PARTIAL)? root : value_root(root); | |
| } /* print_root() */ | |
| static void print_init(struct printer *P, struct json_value *V, int flags) { | |
| memset(P, 0, sizeof *P); | |
| P->flags = flags; | |
| P->value = V; | |
| } /* print_init() */ | |
| #define RESUME() switch (P->sstate) { case 0: (void)0 | |
| #define YIELD() do { \ | |
| P->sstate = __LINE__; \ | |
| return p - (char *)dst; \ | |
| case __LINE__: (void)0; \ | |
| } while (0) | |
| #define STOP() do { \ | |
| P->sstate = __LINE__; \ | |
| case __LINE__: return p - (char *)dst; \ | |
| } while (0) | |
| #define PUTCHAR(ch) do { \ | |
| while (p >= pe) \ | |
| YIELD(); \ | |
| *p++ = (ch); \ | |
| } while (0) | |
| #define END } (void)0 | |
| static size_t print_simple(struct printer *P, void *dst, size_t lim, struct json_value *V, int order) { | |
| char *p = dst, *pe = p + lim; | |
| RESUME(); | |
| switch (V->type) { | |
| case JSON_T_ARRAY: | |
| if (order & ORDER_PRE) | |
| P->literal[0] = '['; | |
| else | |
| P->literal[0] = ']'; | |
| P->buffer.p = P->literal; | |
| P->buffer.pe = &P->literal[1]; | |
| goto literal; | |
| case JSON_T_OBJECT: | |
| if (order & ORDER_PRE) | |
| P->literal[0] = '{'; | |
| else | |
| P->literal[0] = '}'; | |
| P->buffer.p = P->literal; | |
| P->buffer.pe = &P->literal[1]; | |
| goto literal; | |
| case JSON_T_STRING: | |
| P->buffer.p = V->string->text; | |
| P->buffer.pe = &V->string->text[V->string->length]; | |
| goto string; | |
| case JSON_T_NUMBER: { | |
| double i; | |
| int count; | |
| if (0.0 == modf(V->number, &i)) | |
| count = snprintf(P->literal, sizeof P->literal, "%lld", (long long)i); | |
| else | |
| count = snprintf(P->literal, sizeof P->literal, "%f", V->number); | |
| if (count == -1) { | |
| P->error = errno; | |
| goto error; | |
| } else if ((size_t)count >= sizeof P->literal) { | |
| P->error = EOVERFLOW; | |
| goto error; | |
| } | |
| P->buffer.p = P->literal; | |
| P->buffer.pe = &P->literal[count]; | |
| goto literal; | |
| } | |
| case JSON_T_BOOLEAN: { | |
| size_t count = json_strlcpy(P->literal, ((V->boolean)? "true" : "false"), sizeof P->literal); | |
| P->buffer.p = P->literal; | |
| P->buffer.pe = &P->literal[count]; | |
| goto literal; | |
| } | |
| case JSON_T_NULL: { | |
| size_t count = json_strlcpy(P->literal, "null", sizeof P->literal); | |
| P->buffer.p = P->literal; | |
| P->buffer.pe = &P->literal[count]; | |
| goto literal; | |
| } | |
| } /* switch (V->type) */ | |
| string: | |
| PUTCHAR('"'); | |
| while (P->buffer.p < P->buffer.pe) { | |
| if (json_isgraph(*P->buffer.p)) { | |
| if (*P->buffer.p == '"' || *P->buffer.p == '/' || *P->buffer.p == '\\') | |
| PUTCHAR('\\'); | |
| PUTCHAR(*P->buffer.p++); | |
| } else if (*P->buffer.p == ' ') { | |
| PUTCHAR(*P->buffer.p++); | |
| } else if (json_isascii(*P->buffer.p)) { | |
| PUTCHAR('\\'); | |
| if (*P->buffer.p == '\b') | |
| PUTCHAR('b'); | |
| else if (*P->buffer.p == '\f') | |
| PUTCHAR('f'); | |
| else if (*P->buffer.p == '\n') | |
| PUTCHAR('n'); | |
| else if (*P->buffer.p == '\r') | |
| PUTCHAR('r'); | |
| else if (*P->buffer.p == '\t') | |
| PUTCHAR('t'); | |
| else { | |
| PUTCHAR('u'); | |
| PUTCHAR('0'); | |
| PUTCHAR('0'); | |
| PUTCHAR("0123456789abcdef"[0x0f & (*P->buffer.p >> 4)]); | |
| PUTCHAR("0123456789abcdef"[0x0f & (*P->buffer.p >> 0)]); | |
| } | |
| P->buffer.p++; | |
| } else { | |
| PUTCHAR(*P->buffer.p++); | |
| } | |
| } /* while() */ | |
| PUTCHAR('"'); | |
| STOP(); | |
| literal: | |
| while (P->buffer.p < P->buffer.pe) | |
| PUTCHAR(*P->buffer.p++); | |
| STOP(); | |
| error: | |
| STOP(); | |
| END; | |
| return 0; | |
| } /* print_simple() */ | |
| #undef RESUME | |
| #undef YIELD | |
| #undef PUTCHAR | |
| #undef STOP | |
| #undef END | |
| #define RESUME switch (P->state) { case 0: (void)0 | |
| #define YIELD() do { \ | |
| P->state = __LINE__; \ | |
| return p - (char *)dst; \ | |
| case __LINE__: (void)0; \ | |
| } while (0) | |
| #define STOP() do { \ | |
| P->state = __LINE__; \ | |
| case __LINE__: return p - (char *)dst; \ | |
| } while (0) | |
| #define PUTCHAR_(ch, cond, ...) do { \ | |
| if ((cond)) { \ | |
| while (p >= pe) \ | |
| YIELD(); \ | |
| *p++ = (ch); \ | |
| } \ | |
| } while (0) | |
| #define PUTCHAR(...) PUTCHAR_(__VA_ARGS__, 1) | |
| #define END } (void)0 | |
| static size_t print(struct printer *P, void *dst, size_t lim) { | |
| char *p = dst, *pe = p + lim; | |
| size_t count; | |
| RESUME; | |
| while ((P->value = value_next(P->value, &P->order, &P->depth))) { | |
| if ((!value_isvalue(P->value) || (P->order & ORDER_POST)) | |
| && (P->flags & JSON_F_PRETTY)) { | |
| for (P->i = 0; P->i < P->depth; P->i++) | |
| PUTCHAR('\t'); | |
| } | |
| P->sstate = 0; | |
| count = 0; | |
| do { | |
| p += count; | |
| if (!(p < pe)) | |
| YIELD(); | |
| } while ((count = print_simple(P, p, pe - p, P->value, P->order))); | |
| if (P->error) | |
| STOP(); | |
| if (value_iskey(P->value)) { | |
| PUTCHAR(' ', (P->flags & JSON_F_PRETTY)); | |
| PUTCHAR(':'); | |
| PUTCHAR(' ', (P->flags & JSON_F_PRETTY)); | |
| } else { | |
| if ((P->order == ORDER_POST || value_issimple(P->value)) | |
| && value_adjacent(P->value)) | |
| PUTCHAR(','); | |
| PUTCHAR('\n', (P->flags & JSON_F_PRETTY)); | |
| } | |
| } | |
| STOP(); | |
| END; | |
| return 0; | |
| } /* print() */ | |
| #undef RESUME | |
| #undef YIELD | |
| #undef PUTCHAR | |
| #undef STOP | |
| #undef END | |
| /* | |
| * P A R S E R R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| struct parser { | |
| struct json_memory *memory; | |
| struct lexer lexer; | |
| CIRCLEQ_HEAD(, token) tokens; | |
| void *state; | |
| struct json_value *root; | |
| struct json_value *key; | |
| struct json_value *value; | |
| int error; | |
| }; /* struct parser */ | |
| static void parse_init(struct parser *P, struct json_memory *memory) { | |
| memset(P, 0, sizeof *P); | |
| P->memory = memory; | |
| lex_init(&P->lexer, memory); | |
| CIRCLEQ_INIT(&P->tokens); | |
| } /* parse_init() */ | |
| static void parse_destroy(struct parser *P) { | |
| struct token *T; | |
| struct json_value *root; | |
| lex_destroy(&P->lexer); | |
| while (!CIRCLEQ_EMPTY(&P->tokens)) { | |
| T = CIRCLEQ_FIRST(&P->tokens); | |
| CIRCLEQ_REMOVE(&P->tokens, T, cqe); | |
| tok_free(T); | |
| } /* while (tokens) */ | |
| if ((root = value_root(P->root))) | |
| value_close(root, 1); | |
| P->root = NULL; | |
| } /* parse_destroy() */ | |
| static struct json_value *tovalue(struct token *T, int *error) { | |
| return value_open(lex_typemap[T->type], T, error); | |
| } /* tovalue() */ | |
| #define RESUME() do { \ | |
| T = (CIRCLEQ_EMPTY(&P->tokens))? 0 : CIRCLEQ_LAST(&P->tokens); \ | |
| if (P->state) \ | |
| goto *P->state; \ | |
| } while (0) | |
| #define YIELD() do { \ | |
| P->state = &&JSON_XPASTE(L, __LINE__); \ | |
| return EAGAIN; \ | |
| JSON_XPASTE(L, __LINE__): (void)0; \ | |
| } while (0) | |
| #define STOP(why) do { \ | |
| P->error = (why); \ | |
| P->state = &&JSON_XPASTE(L, __LINE__); \ | |
| JSON_XPASTE(L, __LINE__): return P->error; \ | |
| } while (0) | |
| #define POPTOKEN() do { \ | |
| while (CIRCLEQ_EMPTY(&P->lexer.tokens)) \ | |
| YIELD(); \ | |
| T = CIRCLEQ_FIRST(&P->lexer.tokens); \ | |
| CIRCLEQ_REMOVE(&P->lexer.tokens, T, cqe); \ | |
| CIRCLEQ_INSERT_TAIL(&P->tokens, T, cqe); \ | |
| } while (0) | |
| #define POPSTACK() do { \ | |
| void *state = P->root->state; \ | |
| if (P->root->node) \ | |
| P->root = P->root->node->parent; \ | |
| goto *state; \ | |
| } while (0) | |
| #define PUSHARRAY(V) do { \ | |
| (V)->state = &&JSON_XPASTE(L, __LINE__); \ | |
| P->root = V; \ | |
| goto array; \ | |
| JSON_XPASTE(L, __LINE__): (void)0; \ | |
| } while (0) | |
| #define PUSHOBJECT(V) do { \ | |
| (V)->state = &&JSON_XPASTE(L, __LINE__); \ | |
| P->root = V; \ | |
| goto object; \ | |
| JSON_XPASTE(L, __LINE__): (void)0; \ | |
| } while (0) | |
| #define TOVALUE(T) do { \ | |
| if (!(P->value = V = tovalue(T, &error))) \ | |
| STOP(error); \ | |
| } while (0) | |
| #define TOKEY(T) do { \ | |
| if (!(P->key = V = tovalue(T, &error))) \ | |
| STOP(error); \ | |
| } while (0) | |
| #define INSERT2ARRAY() do { \ | |
| if ((error = array_push(P->root, P->value))) \ | |
| STOP(error); \ | |
| P->value = 0; \ | |
| } while (0) | |
| #define INSERT2OBJECT() do { \ | |
| if ((error = object_insert(P->root, P->key, P->value))) \ | |
| STOP(error); \ | |
| P->key = 0; \ | |
| P->value = 0; \ | |
| } while (0) | |
| #define LOOP for (;;) | |
| static int parse(struct parser *P, const void *src, size_t len) { | |
| struct token *T; | |
| struct json_value *V; | |
| int error; | |
| if ((error = lex_parse(&P->lexer, src, len))) | |
| return error; | |
| RESUME(); | |
| POPTOKEN(); | |
| switch (T->type) { | |
| case T_BEGIN_ARRAY: | |
| if (!(P->root = value_open(JSON_T_ARRAY, T, &error))) | |
| STOP(error); | |
| P->root->state = &&stop; | |
| goto array; | |
| case T_BEGIN_OBJECT: | |
| if (!(P->root = value_open(JSON_T_OBJECT, T, &error))) | |
| STOP(error); | |
| P->root->state = &&stop; | |
| goto object; | |
| default: | |
| STOP(JSON_ESYNTAX); | |
| } /* switch() */ | |
| array: | |
| LOOP { | |
| POPTOKEN(); | |
| switch (T->type) { | |
| case T_BEGIN_ARRAY: | |
| TOVALUE(T); | |
| INSERT2ARRAY(); | |
| PUSHARRAY(V); | |
| break; | |
| case T_END_ARRAY: | |
| POPSTACK(); | |
| case T_BEGIN_OBJECT: | |
| TOVALUE(T); | |
| INSERT2ARRAY(); | |
| PUSHOBJECT(V); | |
| break; | |
| case T_END_OBJECT: | |
| STOP(JSON_ESYNTAX); | |
| case T_VALUE_SEPARATOR: | |
| break; | |
| case T_NAME_SEPARATOR: | |
| STOP(JSON_ESYNTAX); | |
| default: | |
| TOVALUE(T); | |
| INSERT2ARRAY(); | |
| break; | |
| } /* switch() */ | |
| } /* LOOP */ | |
| object: | |
| LOOP { | |
| POPTOKEN(); | |
| switch (T->type) { | |
| case T_END_OBJECT: | |
| POPSTACK(); | |
| case T_VALUE_SEPARATOR: | |
| continue; | |
| case T_STRING: | |
| break; | |
| default: | |
| STOP(JSON_ESYNTAX); | |
| } /* switch (key) */ | |
| TOKEY(T); | |
| POPTOKEN(); | |
| if (T->type != T_NAME_SEPARATOR) | |
| STOP(JSON_ESYNTAX); | |
| POPTOKEN(); | |
| switch (T->type) { | |
| case T_BEGIN_ARRAY: | |
| TOVALUE(T); | |
| INSERT2OBJECT(); | |
| PUSHARRAY(V); | |
| break; | |
| case T_END_ARRAY: | |
| STOP(JSON_ESYNTAX); | |
| case T_BEGIN_OBJECT: | |
| TOVALUE(T); | |
| INSERT2OBJECT(); | |
| PUSHOBJECT(V); | |
| break; | |
| case T_END_OBJECT: | |
| STOP(JSON_ESYNTAX); | |
| case T_NAME_SEPARATOR: | |
| STOP(JSON_ESYNTAX); | |
| default: | |
| TOVALUE(T); | |
| INSERT2OBJECT(); | |
| break; | |
| } /* switch() */ | |
| } /* LOOP */ | |
| stop: | |
| return 0; | |
| } /* parse() */ | |
| #undef RESUME | |
| #undef YIELD | |
| #undef STOP | |
| #undef POPTOKEN | |
| #undef POPSTACK | |
| #undef PUSHARRAY | |
| #undef PUSHOBJECT | |
| #undef TOVALUE | |
| #undef TOKEY | |
| #undef INSERT2ARRAY | |
| #undef INSERT2OBJECT | |
| #undef LOOP | |
| /* | |
| * J S O N C O R E R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| struct json { | |
| int refs; | |
| struct json_memory memory; | |
| int flags, mode; | |
| struct parser parser; | |
| struct printer printer; | |
| jmp_buf *trap; | |
| struct json_value *root; | |
| }; /* struct json */ | |
| JSON_PUBLIC struct json *json_xopen(const struct json_options *opts, int *_error) { | |
| struct json_memory memory; | |
| struct json *J; | |
| int error; | |
| if ((error = json_m_init(&memory, opts))) | |
| goto error; | |
| if (!(J = json_obsget(obstack, sizeof *J, &error))) | |
| goto error; | |
| memset(J, 0, sizeof *J); | |
| J->refs = 1; | |
| json_m_move(&J->memory, &memory); | |
| J->flags = opts->flags; | |
| J->mode = ~((opts->flags & (JSON_F_NOAUTOVIV|JSON_F_NOCONVERT)) >> 4) | |
| & (JSON_M_AUTOVIV|JSON_M_CONVERT); | |
| parse_init(&J->parser, &J->memory); | |
| J->trap = NULL; | |
| J->root = NULL; | |
| return J; | |
| error: | |
| *_error = error; | |
| json_m_destroy(&memory); | |
| return NULL; | |
| } /* json_xopen() */ | |
| JSON_PUBLIC struct json *json_open(int flags, int *error) { | |
| struct json_options opts = { .flags = flags, NULL, NULL }; | |
| return json_xopen(&opts, error); | |
| } /* json_open() */ | |
| JSON_PUBLIC void json_close(struct json *J) { | |
| struct json_value *root; | |
| if (!J) | |
| return; | |
| parse_destroy(&J->parser); | |
| if ((root = value_root(J->root))) | |
| value_close(root, 1); | |
| free(J); | |
| } /* json_close() */ | |
| JSON_PUBLIC jmp_buf *json_setjmp(struct json *J, jmp_buf *trap) { | |
| jmp_buf *otrap = J->trap; | |
| J->trap = trap; | |
| return otrap; | |
| } /* json_setjmp() */ | |
| JSON_PUBLIC int json_throw(struct json *J, int error) { | |
| if (J->trap) | |
| _longjmp(*J->trap, error); | |
| return error; | |
| } /* json_throw() */ | |
| JSON_PUBLIC int json_ifthrow(struct json *J, int error) { | |
| return (error)? json_throw(J, error) : 0; | |
| } /* json_ifthrow() */ | |
| JSON_PUBLIC int json_parse(struct json *J, const void *src, size_t len) { | |
| int error; | |
| if (J->root) | |
| return json_throw(J, JSON_ENOMORE); | |
| if ((error = parse(&J->parser, src, len))) | |
| return error; | |
| J->root = J->parser.root; | |
| J->parser.root = NULL; | |
| J->root->root = NULL; | |
| parse_destroy(&J->parser); | |
| return 0; | |
| } /* json_parse() */ | |
| JSON_PUBLIC int json_loadbuffer(struct json *J, const void *src, size_t len) { | |
| int error; | |
| if (J->root) | |
| return json_throw(J, JSON_ENOMORE); | |
| if ((error = json_parse(J, src, len))) | |
| return json_throw(J, (error == EAGAIN)? JSON_ETRUNCATED : error); | |
| return 0; | |
| } /* json_loadbuffer() */ | |
| JSON_PUBLIC JSON_DEPRECATED int json_loadlstring(struct json *J, const void *src, size_t len) { | |
| return json_loadbuffer(J, src, len); | |
| } /* json_loadlstring() */ | |
| JSON_PUBLIC int json_loadstring(struct json *J, const char *src) { | |
| return json_loadbuffer(J, src, json_strlen(src)); | |
| } /* json_loadstring() */ | |
| JSON_PUBLIC int json_loadfile(struct json *J, FILE *fp) { | |
| char buffer[512]; | |
| size_t count; | |
| int error; | |
| if (J->root) | |
| return json_throw(J, JSON_ENOMORE); | |
| clearerr(fp); | |
| while ((count = fread(buffer, 1, sizeof buffer, fp))) { | |
| if (!(error = json_parse(J, buffer, count))) | |
| return 0; | |
| else if (error != EAGAIN) | |
| return json_throw(J, error); | |
| } | |
| if (ferror(fp)) | |
| return json_throw(J, errno); | |
| return JSON_ETRUNCATED; | |
| } /* json_loadfile() */ | |
| JSON_PUBLIC int json_loadpath(struct json *J, const char *path) { | |
| struct jsonxs xs; | |
| FILE *fp = NULL; | |
| int error; | |
| if (J->root) | |
| return json_throw(J, JSON_ENOMORE); | |
| if ((error = json_enter(J, &xs))) | |
| goto leave; | |
| if (!(fp = fopen(path, "r"))) | |
| json_throw(J, errno); | |
| json_loadfile(J, fp); | |
| leave: | |
| json_leave(J, &xs); | |
| if (fp) | |
| fclose(fp); | |
| return (error)? json_throw(J, error) : 0; | |
| } /* json_loadpath() */ | |
| JSON_PUBLIC size_t json_compose(struct json *J, void *dst, size_t lim, int flags, int *error) { | |
| struct json_value *root; | |
| size_t count; | |
| if (!J->printer.state) { | |
| if (!(root = print_root(J->root, flags|J->flags))) | |
| return 0; | |
| print_init(&J->printer, root, flags|J->flags); | |
| } | |
| if ((count = print(&J->printer, dst, lim))) | |
| return count; | |
| if (J->printer.error) { | |
| if (error) | |
| *error = J->printer.error; | |
| json_throw(J, J->printer.error); | |
| } | |
| if (error) | |
| *error = 0; | |
| return 0; | |
| } /* json_compose() */ | |
| JSON_PUBLIC void json_rewind(struct json *J) { | |
| print_init(&J->printer, 0, 0); | |
| } /* json_rewind() */ | |
| JSON_PUBLIC JSON_DEPRECATED void json_flush(struct json *J) { | |
| json_rewind(J); | |
| } /* json_flush() */ | |
| JSON_PUBLIC int json_getc(struct json *J, int flags, int *error) { | |
| char c; | |
| while (json_compose(J, &c, 1, flags, error)) | |
| return (unsigned char)c; | |
| return EOF; | |
| } /* json_getc() */ | |
| JSON_PUBLIC int json_printfile(struct json *J, FILE *fp, int flags) { | |
| struct printer P; | |
| struct json_value *root; | |
| char buffer[512]; | |
| size_t count; | |
| int error; | |
| if (!(root = print_root(J->root, flags|J->flags))) | |
| return 0; | |
| print_init(&P, root, flags|J->flags); | |
| while ((count = print(&P, buffer, sizeof buffer))) { | |
| if (count != fwrite(buffer, 1, count, fp)) | |
| goto syerr; | |
| } | |
| if ((error = P.error)) | |
| goto error; | |
| else if (0 != fflush(fp)) | |
| goto syerr; | |
| return 0; | |
| syerr: | |
| error = errno; | |
| error: | |
| return json_throw(J, error); | |
| } /* json_printfile() */ | |
| JSON_PUBLIC size_t json_printstring(struct json *J, void *dst, size_t lim, int flags, int *error) { | |
| struct printer P; | |
| struct json_value *root; | |
| char buffer[512], *p, *pe; | |
| size_t count, total; | |
| if (!(root = print_root(J->root, flags|J->flags))) | |
| goto empty; | |
| print_init(&P, root, flags|J->flags); | |
| p = dst; | |
| pe = p + lim; | |
| total = 0; | |
| while ((count = print(&P, buffer, sizeof buffer))) { | |
| if (p < pe) { | |
| memcpy(p, buffer, JSON_MIN((size_t)(pe - p), count)); | |
| p += JSON_MIN((size_t)(pe - p), count); | |
| } | |
| total += count; | |
| } | |
| if (P.error) | |
| goto error; | |
| if (lim) | |
| ((char *)dst)[JSON_MIN(lim - 1, total)] = '\0'; | |
| return total; | |
| error: | |
| if (error) | |
| *error = P.error; | |
| json_throw(J, P.error); | |
| empty: | |
| if (lim) | |
| *(char *)dst = '\0'; | |
| return 0; | |
| } /* json_printstring() */ | |
| /* | |
| * J S O N V A L U E R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| JSON_PUBLIC struct json_value *json_root(struct json *J) { | |
| return J->root; | |
| } /* json_root() */ | |
| JSON_PUBLIC enum json_type json_v_type(struct json *J JSON_NOTUSED, struct json_value *V) { | |
| return V->type; | |
| } /* json_v_type() */ | |
| static int json_v_search_(struct json_value **V, struct json *J JSON_NOTUSED, struct json_value *O, int mode, const void *name, size_t len) { | |
| struct json_value *K = NULL; | |
| int error; | |
| *V = NULL; | |
| if (O->type != JSON_T_OBJECT) { | |
| if (!(mode & JSON_M_AUTOVIV) || !(mode & JSON_M_CONVERT)) | |
| return 0; | |
| if ((error = value_convert(O, JSON_T_OBJECT))) | |
| goto error; | |
| } | |
| if ((*V = object_search(O, name, len)) || !(mode & JSON_M_AUTOVIV)) | |
| return 0; | |
| if (!(K = value_open(JSON_T_STRING, NULL, &error))) | |
| goto error; | |
| if ((error = string_cats(&K->string, name, len))) | |
| goto error; | |
| if (!(*V = value_open(JSON_T_NULL, NULL, &error))) | |
| goto error; | |
| if ((error = object_insert(O, K, *V))) | |
| goto error; | |
| return 0; | |
| error: | |
| value_close(K, 0); | |
| value_close(*V, 0); | |
| *V = NULL; | |
| return error; | |
| } /* json_v_search_() */ | |
| JSON_PUBLIC struct json_value *json_v_search(struct json *J, struct json_value *O, int mode, const void *name, size_t len) { | |
| struct json_value *V; | |
| int error; | |
| if ((error = json_v_search_(&V, J, O, mode, name, len))) | |
| json_throw(J, error); | |
| return V; | |
| } /* json_v_search() */ | |
| static int json_v_index_(struct json_value **V, struct json *J JSON_NOTUSED, struct json_value *A, int mode, int index) { | |
| int error; | |
| *V = NULL; | |
| if (A->type != JSON_T_ARRAY) { | |
| if (!(mode & JSON_M_AUTOVIV) || !(mode & JSON_M_CONVERT)) | |
| return 0; | |
| if ((error = value_convert(A, JSON_T_ARRAY))) | |
| goto error; | |
| } | |
| if ((*V = array_index(A, index)) || !(mode & JSON_M_AUTOVIV)) | |
| return 0; | |
| if (!(*V = value_open(JSON_T_NULL, NULL, &error))) | |
| goto error; | |
| if ((error = array_insert(A, index, *V))) | |
| goto error; | |
| return 0; | |
| error: | |
| value_close(*V, 0); | |
| *V = NULL; | |
| return error; | |
| } /* json_v_index_() */ | |
| JSON_PUBLIC struct json_value *json_v_index(struct json *J, struct json_value *O, int mode, int index) { | |
| struct json_value *V; | |
| int error; | |
| if ((error = json_v_index_(&V, J, O, mode, index))) | |
| json_throw(J, error); | |
| return V; | |
| } /* json_v_index_() */ | |
| JSON_PUBLIC int json_v_delete(struct json *J JSON_NOTUSED, struct json_value *V) { | |
| struct json_value *root; | |
| for (root = J->root; root; root = root->root) { | |
| if (root == V) { | |
| J->root = V->root; | |
| V->root = NULL; | |
| break; | |
| } | |
| } | |
| value_close(V, 1); | |
| return 0; | |
| } /* json_v_delete() */ | |
| JSON_PUBLIC int json_v_clear(struct json *J JSON_NOTUSED, struct json_value *V) { | |
| struct orphans indices, keys; | |
| CIRCLEQ_INIT(&indices); | |
| CIRCLEQ_INIT(&keys); | |
| value_clear(V, &indices, &keys); | |
| orphans_free(&indices, &keys); | |
| return 0; | |
| } /* json_v_clear() */ | |
| JSON_PUBLIC double json_v_number(struct json *J, struct json_value *V) { | |
| if (V && V->type != JSON_T_NUMBER && (J->flags & JSON_F_STRONG)) | |
| json_throw(J, JSON_ETYPING); | |
| return value_number(V); | |
| } /* json_v_number() */ | |
| JSON_PUBLIC const char *json_v_string(struct json *J, struct json_value *V) { | |
| if (V && V->type != JSON_T_STRING && (J->flags & JSON_F_STRONG)) | |
| json_throw(J, JSON_ETYPING); | |
| return value_string(V); | |
| } /* json_v_string() */ | |
| JSON_PUBLIC size_t json_v_length(struct json *J, struct json_value *V) { | |
| if (V && V->type != JSON_T_STRING && (J->flags & JSON_F_STRONG)) | |
| json_throw(J, JSON_ETYPING); | |
| return value_length(V); | |
| } /* json_v_length() */ | |
| JSON_PUBLIC size_t json_v_count(struct json *J, struct json_value *V) { | |
| if (V && V->type != JSON_T_ARRAY && V->type != JSON_T_OBJECT && (J->flags & JSON_F_STRONG)) | |
| json_throw(J, JSON_ETYPING); | |
| return value_count(V); | |
| } /* json_v_count() */ | |
| JSON_PUBLIC _Bool json_v_boolean(struct json *J, struct json_value *V) { | |
| if (V && V->type != JSON_T_BOOLEAN && (J->flags & JSON_F_STRONG)) | |
| json_throw(J, JSON_ETYPING); | |
| return value_boolean(V); | |
| } /* json_v_boolean() */ | |
| JSON_PUBLIC int json_v_setnumber(struct json *J, struct json_value *V, double number) { | |
| int error; | |
| if ((error = value_convert(V, JSON_T_NUMBER))) | |
| return json_throw(J, error); | |
| V->number = number; | |
| return 0; | |
| } /* json_v_setnumber() */ | |
| JSON_PUBLIC int json_v_setbuffer(struct json *J, struct json_value *V, const void *sp, size_t len) { | |
| int error; | |
| if ((error = value_convert(V, JSON_T_STRING))) | |
| return json_throw(J, error); | |
| string_reset(&V->string); | |
| return json_ifthrow(J, string_cats(&V->string, sp, len)); | |
| } /* json_v_setbuffer() */ | |
| JSON_PUBLIC JSON_DEPRECATED int json_v_setlstring(struct json *J, struct json_value *V, const void *sp, size_t len) { | |
| return json_v_setbuffer(J, V, sp, len); | |
| } /* json_v_setlstring() */ | |
| JSON_PUBLIC int json_v_setstring(struct json *J, struct json_value *V, const void *sp) { | |
| return json_v_setbuffer(J, V, sp, json_strlen(sp)); | |
| } /* json_v_setstring() */ | |
| JSON_PUBLIC int json_v_setboolean(struct json *J, struct json_value *V, _Bool boolean) { | |
| int error; | |
| if ((error = value_convert(V, JSON_T_BOOLEAN))) | |
| return json_throw(J, error); | |
| V->boolean = boolean; | |
| return 0; | |
| } /* json_v_setboolean() */ | |
| JSON_PUBLIC int json_v_setnull(struct json *J, struct json_value *V) { | |
| return json_ifthrow(J, value_convert(V, JSON_T_NULL)); | |
| } /* json_v_setnull() */ | |
| JSON_PUBLIC int json_v_setarray(struct json *J, struct json_value *V) { | |
| return json_ifthrow(J, value_convert(V, JSON_T_ARRAY)); | |
| } /* json_v_setarray() */ | |
| JSON_PUBLIC int json_v_setobject(struct json *J, struct json_value *V) { | |
| return json_ifthrow(J, value_convert(V, JSON_T_OBJECT)); | |
| } /* json_v_setobject() */ | |
| JSON_PUBLIC int json_i_level(struct json *J JSON_NOTUSED, struct json_iterator *I) { | |
| return I->level + I->_.depth; | |
| } /* json_i_level() */ | |
| JSON_PUBLIC int json_i_depth(struct json *J JSON_NOTUSED, struct json_iterator *I) { | |
| return I->_.depth; | |
| } /* json_i_depth() */ | |
| JSON_PUBLIC int json_i_order(struct json *J JSON_NOTUSED, struct json_iterator *I) { | |
| return I->_.order; | |
| } /* json_i_order() */ | |
| JSON_PUBLIC void json_i_skip(struct json *J JSON_NOTUSED, struct json_iterator *I) { | |
| I->_.order = ORDER_POST; | |
| } /* json_i_skip() */ | |
| JSON_PUBLIC void json_v_start(struct json *J JSON_NOTUSED, struct json_iterator *I, struct json_value *V) { | |
| memset(&I->_, 0, sizeof I->_); | |
| I->_.value = V; | |
| if (I->level < 0) | |
| I->level = 0; | |
| if (I->depth <= 0) | |
| I->depth = INT_MAX; | |
| } /* json_v_start() */ | |
| JSON_PUBLIC struct json_value *json_v_next(struct json *J JSON_NOTUSED, struct json_iterator *I) { | |
| struct json_value *V = I->_.value; | |
| while ((V = value_next(V, &I->_.order, &I->_.depth))) { | |
| if (value_iskey(V)) { | |
| continue; | |
| } else if (I->level > I->_.depth) { | |
| continue; | |
| } else if (I->level + I->depth <= I->_.depth) { | |
| json_i_skip(J, I); | |
| continue; | |
| } else if ((I->flags & (JSON_I_POSTORDER|JSON_I_PREORDER)) | |
| && !(I->flags & I->_.order)) { | |
| continue; | |
| } | |
| break; | |
| } | |
| return I->_.value = V; | |
| } /* json_v_next() */ | |
| JSON_PUBLIC struct json_value *json_v_keyof(struct json *J JSON_NOTUSED, struct json_value *V) { | |
| return (V->node && V->node->parent->type == JSON_T_OBJECT)? V->node->key : NULL; | |
| } /* json_v_keyof() */ | |
| JSON_PUBLIC int json_v_indexof(struct json *J JSON_NOTUSED, struct json_value *V) { | |
| return (V->node && V->node->parent->type == JSON_T_ARRAY)? V->node->index : -1; | |
| } /* json_v_indexof() */ | |
| /* | |
| * J S O N P A T H R O U T I N E S | |
| * | |
| * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
| struct json_path { | |
| int type, index; | |
| char key[256], *kp; | |
| size_t len; | |
| const char *fmt, *fp; | |
| va_list ap; | |
| struct json_value *value; | |
| }; /* struct json_path */ | |
| #define path_start(path, fmt) do { \ | |
| (path)->fmt = (fmt); \ | |
| (path)->fp = (fmt); \ | |
| va_start((path)->ap, fmt); \ | |
| } while (0) | |
| #define path_end(path) \ | |
| va_end((path)->ap) | |
| #define path_load(error, path, J, fmt, mode) do { \ | |
| path_start((path), fmt); \ | |
| *error = path_exec((J), (path), (mode)); \ | |
| path_end((path)); \ | |
| } while (0) | |
| static _Bool path_eof(struct json_path *path) { | |
| return !*path->fp; | |
| } /* path_eof() */ | |
| static unsigned path_getc(struct json_path *path) { | |
| return (*path->fp)? *path->fp++ : 0; | |
| } /* path_getc() */ | |
| static int path_popc(struct json_path *path) { | |
| int ch; | |
| switch ((ch = path_getc(path))) { | |
| case '\\': | |
| return path_getc(path); | |
| case '[': | |
| case ']': | |
| case '.': | |
| case '#': | |
| case '$': | |
| return -ch; | |
| default: | |
| return ch; | |
| } /* switch() */ | |
| } /* path_popc() */ | |
| static void path_unget(struct json_path *path) { | |
| /* NOTE: should never be putting back an escaped character. */ | |
| --path->fp; | |
| } /* path_unget() */ | |
| static int key_putc(struct json_path *path, int ch) { | |
| if (path->kp < json_endof(path->key) - 1) { | |
| *path->kp++ = ch; | |
| return 0; | |
| } else | |
| return JSON_EBIGPATH; | |
| } /* key_putc() */ | |
| static int key_puts(struct json_path *path, const char *str) { | |
| size_t len, lim; | |
| lim = json_endof(path->key) - path->kp; | |
| len = json_strlcpy(path->kp, str, lim); | |
| if (len >= lim) | |
| return JSON_EBIGPATH; | |
| path->kp += len; | |
| return 0; | |
| } /* key_puts() */ | |
| static _Bool path_next(struct json_path *path, int *error) { | |
| int ch, nf, sign, index, len; | |
| const char *str; | |
| path->type = 0; | |
| path->kp = path->key; | |
| if (!(ch = path_popc(path))) { | |
| return 0; | |
| } else if (ch == -'[') | |
| goto array; | |
| if (ch == -'.') { | |
| ch = path_popc(path); | |
| if (ch == -'.' || ch == -'[') | |
| goto syntx; /* back-to-back separators illegal */ | |
| } | |
| nf = 0; | |
| while (ch && ch != -'.' && ch != -'[') { | |
| ++nf; | |
| switch (ch) { | |
| case -'#': | |
| index = va_arg(path->ap, int); | |
| len = snprintf(path->kp, json_endof(path->key) - path->kp, "%d", index); | |
| if (len >= json_endof(path->key) - path->kp) { | |
| *error = JSON_EBIGPATH; | |
| goto error; | |
| } else if (len < 0) | |
| goto syerr; | |
| path->kp += len; | |
| break; | |
| case -'$': | |
| str = va_arg(path->ap, char *); | |
| if ((*error = key_puts(path, str))) | |
| goto error; | |
| break; | |
| default: | |
| if ((*error = key_putc(path, ch))) | |
| goto error; | |
| break; | |
| } | |
| ch = path_popc(path); | |
| } | |
| if (ch) | |
| path_unget(path); | |
| path->type = JSON_T_OBJECT; | |
| *path->kp = '\0'; | |
| path->len = path->kp - path->key; | |
| /* | |
| * NOTE: Allow empty string keys as long as we consumed a valid key | |
| * format. | |
| */ | |
| return nf > 0; | |
| array: | |
| sign = 1; | |
| index = 0; | |
| ch = path_popc(path); | |
| if (ch == -'#') { | |
| index = va_arg(path->ap, int); | |
| ch = path_popc(path); | |
| } else if (ch == '-') { | |
| sign = -1; | |
| goto index; | |
| } else if (json_isdigit(ch)) { | |
| index: | |
| do { | |
| index *= 10; | |
| index += ch - '0'; | |
| } while ((ch = path_popc(path)) > 0 && json_isdigit(ch)); | |
| } else { | |
| goto syntx; | |
| } | |
| if (ch != -']') { | |
| goto syntx; | |
| } | |
| path->type = JSON_T_ARRAY; | |
| path->index = sign * index; | |
| return 1; | |
| syerr: | |
| *error = errno; | |
| goto error; | |
| syntx: | |
| *error = JSON_EBADPATH; | |
| error: | |
| return 0; | |
| } /* path_next() */ | |
| static int path_exec(struct json *J, struct json_path *path, int mode) { | |
| int error = 0; | |
| if (!J->root && (mode & JSON_M_AUTOVIV)) { | |
| if (!(J->root = value_open(JSON_T_NULL, NULL, &error))) | |
| return error; | |
| } | |
| path->value = J->root; | |
| while (path->value && path_next(path, &error)) { | |
| if (path->type == JSON_T_OBJECT) | |
| error = json_v_search_(&path->value, J, path->value, mode, path->key, path->len); | |
| else | |
| error = json_v_index_(&path->value, J, path->value, mode, path->index); | |
| } | |
| return error; | |
| } /* path_exec() */ | |
| static _Bool path_exists(struct json_path *path) { | |
| return path->value && path_eof(path); | |
| } /* path_exists() */ | |
| JSON_PUBLIC int json_push(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) /* either JSON_F_NOAUTOVIV or JSON_F_NOCONVERT */ | |
| return json_throw(J, JSON_ETYPING); | |
| path.value->root = J->root; | |
| J->root = path.value; | |
| return 0; | |
| } /* json_push() */ | |
| JSON_PUBLIC void json_pop(struct json *J) { | |
| struct json_value *oroot; | |
| if ((oroot = J->root) && oroot->root) { | |
| J->root = oroot->root; | |
| oroot->root = NULL; | |
| } | |
| } /* json_pop() */ | |
| JSON_PUBLIC void json_popall(struct json *J) { | |
| struct json_value *oroot; | |
| while ((oroot = J->root) && oroot->root) { | |
| J->root = oroot->root; | |
| oroot->root = NULL; | |
| } | |
| } /* json_popall() */ | |
| JSON_PUBLIC struct json_value *json_top(struct json *J) { | |
| return J->root; | |
| } /* json_top() */ | |
| JSON_PUBLIC void json_delete(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, 0); | |
| json_ifthrow(J, error); | |
| if (path.value) | |
| json_v_delete(J, path.value); | |
| } /* json_delete() */ | |
| JSON_PUBLIC enum json_type json_type(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, 0); | |
| json_ifthrow(J, error); | |
| return (path.value)? path.value->type : JSON_T_NULL; | |
| } /* json_type() */ | |
| JSON_PUBLIC _Bool json_exists(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, 0); | |
| json_ifthrow(J, error); | |
| return (path.value)? 1 : 0; | |
| } /* json_exists() */ | |
| JSON_PUBLIC double json_number(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, 0); | |
| json_ifthrow(J, error); | |
| return json_v_number(J, path.value); | |
| } /* json_number() */ | |
| JSON_PUBLIC const char *json_string(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, 0); | |
| json_ifthrow(J, error); | |
| return json_v_string(J, path.value); | |
| } /* json_string() */ | |
| JSON_PUBLIC size_t json_length(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, 0); | |
| json_ifthrow(J, error); | |
| return json_v_length(J, path.value); | |
| } /* json_length() */ | |
| JSON_PUBLIC size_t json_count(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, 0); | |
| json_ifthrow(J, error); | |
| return json_v_count(J, path.value); | |
| } /* json_count() */ | |
| JSON_PUBLIC _Bool json_boolean(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, 0); | |
| json_ifthrow(J, error); | |
| return json_v_boolean(J, path.value); | |
| } /* json_boolean() */ | |
| JSON_PUBLIC int json_setnumber(struct json *J, double number, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) | |
| return json_throw(J, JSON_ETYPING); | |
| return json_v_setnumber(J, path.value, number); | |
| } /* json_setnumber() */ | |
| JSON_PUBLIC int json_setbuffer(struct json *J, const void *src, size_t len, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) | |
| return json_throw(J, JSON_ETYPING); | |
| return json_v_setbuffer(J, path.value, src, len); | |
| } /* json_setbuffer() */ | |
| JSON_PUBLIC JSON_DEPRECATED int json_setlstring(struct json *J, const void *src, size_t len, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) | |
| return json_throw(J, JSON_ETYPING); | |
| return json_v_setbuffer(J, path.value, src, len); | |
| } /* json_setlstring() */ | |
| JSON_PUBLIC int json_setstring(struct json *J, const void *src, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) | |
| return json_throw(J, JSON_ETYPING); | |
| return json_v_setstring(J, path.value, src); | |
| } /* json_setstring() */ | |
| JSON_PUBLIC int json_setboolean(struct json *J, _Bool boolean, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) | |
| return json_throw(J, JSON_ETYPING); | |
| return json_v_setboolean(J, path.value, boolean); | |
| } /* json_setboolean() */ | |
| JSON_PUBLIC int json_setnull(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) | |
| return json_throw(J, JSON_ETYPING); | |
| return json_v_setnull(J, path.value); | |
| } /* json_setnull() */ | |
| JSON_PUBLIC int json_setarray(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) | |
| return json_throw(J, JSON_ETYPING); | |
| return json_v_setarray(J, path.value); | |
| } /* json_setarray() */ | |
| JSON_PUBLIC int json_setobject(struct json *J, const char *fmt, ...) { | |
| struct json_path path; | |
| int error; | |
| path_load(&error, &path, J, fmt, J->mode); | |
| if (error) | |
| return json_throw(J, error); | |
| if (!path_exists(&path)) | |
| return json_throw(J, JSON_ETYPING); | |
| return json_v_setobject(J, path.value); | |
| } /* json_setobject() */ | |
| #if JSON_MAIN | |
| #include <stdio.h> | |
| #include <unistd.h> | |
| #include <libgen.h> | |
| #include <err.h> | |
| #if defined FFI_H_PATH | |
| #include FFI_H_PATH | |
| #else | |
| #include <ffi/ffi.h> | |
| #endif | |
| struct call { | |
| void *fun; | |
| ffi_type *rtype; | |
| union { | |
| double d; | |
| int i; | |
| char *p; | |
| unsigned long lu; | |
| char c; | |
| } rval; | |
| int argc; | |
| union { | |
| double d; | |
| int i; | |
| char *p; | |
| unsigned long lu; | |
| char c; | |
| } arg[16]; | |
| ffi_type *type[16]; | |
| }; /* struct call */ | |
| static void call_init(struct call *call, ffi_type *rtype, void *fun) { | |
| call->fun = fun; | |
| call->rtype = rtype; | |
| call->argc = 0; | |
| } /* call_init() */ | |
| static void call_push(struct call *call, ffi_type *type, ...) { | |
| va_list ap; | |
| if (call->argc >= (int)json_countof(call->arg)) | |
| return; | |
| va_start(ap, type); | |
| if (type == &ffi_type_pointer) | |
| call->arg[call->argc].p = va_arg(ap, void *); | |
| else if (type == &ffi_type_ulong) | |
| call->arg[call->argc].lu = va_arg(ap, unsigned long); | |
| else if (type == &ffi_type_double) | |
| call->arg[call->argc].d = va_arg(ap, double); | |
| else if (type == &ffi_type_schar) | |
| call->arg[call->argc].c = va_arg(ap, int); | |
| else | |
| call->arg[call->argc].i = va_arg(ap, int); | |
| call->type[call->argc++] = type; | |
| va_end(ap); | |
| } /* call_push() */ | |
| static void call_path(struct call *call, int *argc, char ***argv) { | |
| struct json_path path; | |
| int ch; | |
| if (*argc) { | |
| path.fmt = path.fp = **argv; | |
| --*argc; | |
| ++*argv; | |
| } else | |
| path.fmt = path.fp = ""; | |
| call_push(call, &ffi_type_pointer, path.fmt); | |
| while ((ch = path_popc(&path))) { | |
| switch (ch) { | |
| case -'#': | |
| if (!*argc) | |
| errx(1, "fewer arguments than format specifiers"); | |
| call_push(call, &ffi_type_sint, atoi(**argv)); | |
| --*argc; | |
| ++*argv; | |
| break; | |
| case -'$': | |
| if (!*argc) | |
| errx(1, "fewer arguments than format specifiers"); | |
| call_push(call, &ffi_type_pointer, **argv); | |
| --*argc; | |
| ++*argv; | |
| break; | |
| default: | |
| break; | |
| } /* switch() */ | |
| } /* while() */ | |
| } /* call_path() */ | |
| static void call_exec(struct call *fun) { | |
| void *arg[json_countof(fun->arg)]; | |
| ffi_cif cif; | |
| int i; | |
| for (i = 0; i < (int)json_countof(arg); i++) | |
| arg[i] = &fun->arg[i]; | |
| if (FFI_OK != ffi_prep_cif(&cif, FFI_DEFAULT_ABI, fun->argc, fun->rtype, fun->type)) | |
| errx(1, "FFI call failed"); | |
| ffi_call(&cif, FFI_FN(fun->fun), &fun->rval, arg); | |
| } /* call_exec() */ | |
| #define USAGE \ | |
| "%s [-pPf:Vh] [CMD [ARG ...] ...]\n" \ | |
| " -p pretty print\n" \ | |
| " -P print partial subtree\n" \ | |
| " -A disable autovivification\n" \ | |
| " -C disable conversion\n" \ | |
| " -s enable strong typing\n" \ | |
| " -f PATH file to parse\n" \ | |
| " -V print version\n" \ | |
| " -h print usage\n" \ | |
| "\n" \ | |
| "COMMANDS\n" \ | |
| " print print document to stdout using json_printfile\n" \ | |
| " puts print document to stdout using json_printstring\n" \ | |
| " rewind rewind printer to beginning\n" \ | |
| " delete PATH delete node\n" \ | |
| " remove N delete the Nth node in the path stack\n" \ | |
| " type PATH print node type\n" \ | |
| " exists PATH print whether node exists--yes or no\n" \ | |
| " number PATH print number value\n" \ | |
| " string PATH print string value\n" \ | |
| " length PATH print string length\n" \ | |
| " count PATH print object or array entry count\n" \ | |
| " boolean PATH print boolean value--true or false\n" \ | |
| " push PATH push node onto top of path stack\n" \ | |
| " pop pop a node from path stack\n" \ | |
| " popall pop all nodes except real root\n" \ | |
| " setnumber NUM PATH set node to number\n" \ | |
| " setstring TXT PATH set node to string\n" \ | |
| " setboolean BOOL PATH set node to boolean\n" \ | |
| " setnull PATH set node to null\n" \ | |
| " setarray PATH convert node to array\n" \ | |
| " setobject PATH convert node to object\n" \ | |
| "\n" \ | |
| "PATH FORMAT\n" \ | |
| " A path consists of object and array entry indices, each of which may contain\n" \ | |
| " one or more format specifiers. For each format specifier the respective\n" \ | |
| " number or string should be passed as an additional path argument in its\n" \ | |
| " respective argument position.\n" \ | |
| "\n" \ | |
| " Object key names should be preceded by a period, and array indices enclosed\n" \ | |
| " in square brackets. The # format specifier takes a numeric argument, while\n" \ | |
| " the $ format specifier takes a string as a replacement value. Example:\n" \ | |
| "\n" \ | |
| " foo[#].b$ 0 ar\n" \ | |
| "\n" \ | |
| " This path first indexes the root node as an object with a key of \"foo\".\n" \ | |
| " \"foo\" is then indexed as an array with a value at position 0. The 0 node is\n" \ | |
| " in turn treated as an object with a key of \"bar\". These commands\n" \ | |
| "\n" \ | |
| " setnumber 47 foo[#].b$ 0 ar\n" \ | |
| " number foo[0].bar\n" \ | |
| "\n" \ | |
| " will print the number 47.0 to stdout.\n" \ | |
| "\n" \ | |
| "Report bugs to <william@25thandClement.com>\n" | |
| static void usage(const char *arg0, FILE *fp) { | |
| fprintf(fp, USAGE, arg0); | |
| } /* usage() */ | |
| static void version(const char *arg0, FILE *fp) { | |
| fprintf(fp, "%s (json.c) %.8X\n", arg0, json_version()); | |
| fprintf(fp, "built %s %s\n", __DATE__, __TIME__); | |
| fprintf(fp, "vendor %s\n", json_vendor()); | |
| fprintf(fp, "release %.8X\n", json_v_rel()); | |
| fprintf(fp, "abi %.8X\n", json_v_abi()); | |
| fprintf(fp, "api %.8X\n", json_v_api()); | |
| } /* version() */ | |
| static int lex_main(const char *); | |
| int main(int argc, char **argv) { | |
| extern int optind; | |
| char *arg0 = (argc)? argv[0] : "json"; | |
| struct json *J; | |
| int opt, error; | |
| int volatile flags = 0; | |
| const char *volatile file = NULL, *volatile cmd; | |
| struct call fun; | |
| struct jsonxs trap; | |
| while (-1 != (opt = getopt(argc, argv, "pPACsf:Vh"))) { | |
| switch (opt) { | |
| case 'p': | |
| flags |= JSON_F_PRETTY; | |
| break; | |
| case 'P': | |
| flags |= JSON_F_PARTIAL; | |
| break; | |
| case 'A': | |
| flags |= JSON_F_NOAUTOVIV; | |
| break; | |
| case 'C': | |
| flags |= JSON_F_NOCONVERT; | |
| break; | |
| case 's': | |
| flags |= JSON_F_STRONG; | |
| break; | |
| case 'f': | |
| file = optarg; | |
| break; | |
| case 'V': | |
| version(basename(arg0), stdout); | |
| return 0; | |
| case 'h': | |
| usage(basename(arg0), stdout); | |
| return 0; | |
| default: | |
| usage(basename(arg0), stderr); | |
| return 1; | |
| } /* switch() */ | |
| } /* switch() */ | |
| argc -= optind; | |
| argv += optind; | |
| if (argc) { | |
| cmd = *argv; | |
| argc--; | |
| argv++; | |
| } else { | |
| cmd = "print"; | |
| } | |
| if (!strcmp(cmd, "lex")) | |
| return lex_main(file); | |
| J = json_open(flags, &error); | |
| if (file) { | |
| if (!strcmp(file, "-")) | |
| error = json_loadfile(J, stdin); | |
| else | |
| error = json_loadpath(J, file); | |
| if (error) | |
| errx(1, "%s: %s", file, json_strerror(error)); | |
| } | |
| if ((error = json_enter(J, &trap))) | |
| errx(1, "%s: %s", file, json_strerror(error)); | |
| do { | |
| if (!strcmp(cmd, "print")) { | |
| if ((error = json_printfile(J, stdout, flags))) | |
| errx(1, "stdout: %s", json_strerror(error)); | |
| if (!(flags & JSON_F_PRETTY)) | |
| fputc('\n', stdout); | |
| } else if (!strcmp(cmd, "puts")) { | |
| char *buf; | |
| size_t len; | |
| len = json_printstring(J, NULL, 0, flags, NULL); | |
| if (!(buf = malloc(len + 1))) | |
| err(1, "stdout"); | |
| json_printstring(J, buf, len + 1, flags, NULL); | |
| fputs(buf, stdout); | |
| free(buf); | |
| if (!(flags & JSON_F_PRETTY)) | |
| fputc('\n', stdout); | |
| } else if (!strcmp(cmd, "rewind")) { | |
| json_rewind(J); | |
| } else if (!strcmp(cmd, "delete")) { | |
| call_init(&fun, &ffi_type_void, (void *)&json_delete); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "type")) { | |
| call_init(&fun, &ffi_type_sint, (void *)&json_type); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| puts(json_strtype(fun.rval.i)); | |
| } else if (!strcmp(cmd, "exists")) { | |
| call_init(&fun, &ffi_type_schar, (void *)&json_exists); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| printf("%s\n", (fun.rval.c)? "yes" : "no"); | |
| } else if (!strcmp(cmd, "number")) { | |
| call_init(&fun, &ffi_type_double, (void *)&json_number); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| printf("%f\n", fun.rval.d); | |
| } else if (!strcmp(cmd, "string")) { | |
| call_init(&fun, &ffi_type_pointer, (void *)&json_string); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| printf("%s\n", fun.rval.p); | |
| } else if (!strcmp(cmd, "length")) { | |
| call_init(&fun, &ffi_type_ulong, (void *)&json_length); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| printf("%lu\n", fun.rval.lu); | |
| } else if (!strcmp(cmd, "count")) { | |
| call_init(&fun, &ffi_type_ulong, (void *)&json_count); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| printf("%lu\n", fun.rval.lu); | |
| } else if (!strcmp(cmd, "boolean")) { | |
| call_init(&fun, &ffi_type_schar, (void *)&json_boolean); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| printf("%s\n", (fun.rval.c)? "true" : "false"); | |
| } else if (!strcmp(cmd, "push")) { | |
| call_init(&fun, &ffi_type_sint, (void *)&json_push); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "pop")) { | |
| call_init(&fun, &ffi_type_void, (void *)&json_pop); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "popall")) { | |
| call_init(&fun, &ffi_type_void, (void *)&json_popall); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "delete")) { | |
| call_init(&fun, &ffi_type_void, (void *)&json_delete); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "setnumber")) { | |
| call_init(&fun, &ffi_type_sint, (void *)&json_setnumber); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| if (!argc) | |
| errx(1, "setnumber: missing argument"); | |
| call_push(&fun, &ffi_type_double, atof(*argv)); | |
| argc--; argv++; | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "setstring")) { | |
| call_init(&fun, &ffi_type_sint, (void *)&json_setbuffer); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| if (!argc) | |
| errx(1, "setstring: missing argument"); | |
| call_push(&fun, &ffi_type_pointer, *argv); | |
| call_push(&fun, &ffi_type_ulong, strlen(*argv)); | |
| argc--; argv++; | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "setboolean")) { | |
| call_init(&fun, &ffi_type_sint, (void *)&json_setboolean); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| if (!argc) | |
| errx(1, "setboolean: missing argument"); | |
| call_push(&fun, &ffi_type_schar, (**argv == 't' || **argv == '1')); | |
| argc--; argv++; | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "setnull")) { | |
| call_init(&fun, &ffi_type_sint, (void *)&json_setnull); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "setarray")) { | |
| call_init(&fun, &ffi_type_sint, (void *)&json_setarray); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "setobject")) { | |
| call_init(&fun, &ffi_type_sint, (void *)&json_setobject); | |
| call_push(&fun, &ffi_type_pointer, J); | |
| call_path(&fun, &argc, &argv); | |
| call_exec(&fun); | |
| } else if (!strcmp(cmd, "remove")) { | |
| struct json_value *stack[16], *root; | |
| int i, n, error; | |
| if (!argc) | |
| errx(1, "remove: missing argument"); | |
| if ((i = atoi(*argv)) < 0 || i >= (int)json_countof(stack)) | |
| errx(1, "remove: %d: illegal argument", i); | |
| argc--; argv++; | |
| n = 0; | |
| for (root = J->root; root && n < (int)json_countof(stack); root = root->root) { | |
| stack[n++] = root; | |
| } | |
| if (n > 0) { | |
| i = JSON_MIN(i, n - 1); | |
| if ((error = json_v_delete(J, stack[n - (i + 1)]))) | |
| errx(1, "remove: %d: %s", i, json_strerror(error)); | |
| } | |
| } else { | |
| errx(1, "%s: invalid command", cmd); | |
| } | |
| if (argc) { | |
| cmd = *argv; | |
| argc--; | |
| argv++; | |
| } else | |
| cmd = NULL; | |
| } while (cmd); | |
| json_close(J); | |
| return 0; | |
| } /* main() */ | |
| static int lex_main(const char *file) { | |
| FILE *fp = stdin; | |
| struct lexer L; | |
| char ibuf[1]; | |
| size_t count; | |
| struct token *T; | |
| int error; | |
| if (strcmp(file, "-") && !(fp = fopen(file, "r"))) | |
| err(1, "%s", file); | |
| lex_init(&L); | |
| while ((count = fread(ibuf, 1, sizeof ibuf, fp))) { | |
| if ((error = lex_parse(&L, ibuf, count))) | |
| errx(1, "parse: %s", strerror(error)); | |
| } | |
| if (fp != stdin) | |
| fclose(fp); | |
| CIRCLEQ_FOREACH(T, &L.tokens, cqe) { | |
| switch (T->type) { | |
| case T_STRING: | |
| fprintf(stdout, "%s: %.*s\n", lex_strtype(T->type), (int)T->string->length, T->string->text); | |
| break; | |
| case T_NUMBER: | |
| fprintf(stdout, "%s: %f\n", lex_strtype(T->type), T->number); | |
| break; | |
| default: | |
| fprintf(stdout, "%s\n", lex_strtype(T->type)); | |
| } | |
| } | |
| lex_destroy(&L); | |
| return 0; | |
| } /* lex_main() */ | |
| #endif /* JSON_MAIN */ | |
| /* | |
| * Sanitize macro namespace. | |
| */ | |
| #undef SAY | |
| #undef HAI | |