From 5b7aeae4b33b686e52304127d1648f7b71c9b405 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Wed, 8 Jul 2015 16:21:16 +0200 Subject: [PATCH] JSON: Skeleton, memory representation, parser --- Makefile | 7 +- default.cfg | 3 + ucw-json/Makefile | 47 +++++ ucw-json/doc/Makefile | 20 ++ ucw-json/doc/index.txt | 13 ++ ucw-json/doc/json.txt | 14 ++ ucw-json/json-test.c | 22 ++ ucw-json/json-test.t | 2 + ucw-json/json.c | 93 +++++++++ ucw-json/json.h | 127 ++++++++++++ ucw-json/libucw-json.pc | 11 + ucw-json/parse.c | 434 ++++++++++++++++++++++++++++++++++++++++ 12 files changed, 792 insertions(+), 1 deletion(-) create mode 100644 ucw-json/Makefile create mode 100644 ucw-json/doc/Makefile create mode 100644 ucw-json/doc/index.txt create mode 100644 ucw-json/doc/json.txt create mode 100644 ucw-json/json-test.c create mode 100644 ucw-json/json-test.t create mode 100644 ucw-json/json.c create mode 100644 ucw-json/json.h create mode 100644 ucw-json/libucw-json.pc create mode 100644 ucw-json/parse.c diff --git a/Makefile b/Makefile index 51ffa3ed..4fb2bbfd 100644 --- a/Makefile +++ b/Makefile @@ -55,12 +55,17 @@ LIBXML=$(o)/ucw-xml/libucw-xml.pc include $(s)/ucw-xml/Makefile endif +ifdef CONFIG_JSON +LIBJSON=$(o)/ucw-json/libucw-json.pc +include $(s)/ucw-json/Makefile +endif + # Build documentation by default? ifdef CONFIG_DOC all: docs endif -libs: $(LIBUCW) $(LIBXML) $(LIBIMAGES) $(LIBCHARSET) +libs: $(LIBUCW) $(LIBXML) $(LIBJSON) $(LIBIMAGES) $(LIBCHARSET) # And finally the default rules of the build system include $(BUILDSYS)/Makebottom diff --git a/default.cfg b/default.cfg index a78a78b9..82cc006e 100644 --- a/default.cfg +++ b/default.cfg @@ -38,5 +38,8 @@ Set("CONFIG_CHARSET_UTILS"); # Libucw-xml Set("CONFIG_XML"); +# Libucw-json +Set("CONFIG_JSON"); + # Return success 1; diff --git a/ucw-json/Makefile b/ucw-json/Makefile new file mode 100644 index 00000000..0e5bd98b --- /dev/null +++ b/ucw-json/Makefile @@ -0,0 +1,47 @@ +# Makefile for the UCW JSON library +# (c) 2015 Martin Mares + +DIRS+=ucw-json +PROGS+=$(o)/ucw-json/json-test + +LIBJSON_MODS=json parse +LIBJSON_MOD_PATHS=$(addprefix $(o)/ucw-json/,$(LIBJSON_MODS)) +LIBJSON_INCLUDES=json.h +LIBJSON_DEPS=$(LIBUCW) + +$(o)/ucw-json/libucw-json$(LV).a: $(addsuffix .o,$(LIBJSON_MOD_PATHS)) +$(o)/ucw-json/libucw-json$(LV).so: $(addsuffix .oo,$(LIBJSON_MOD_PATHS)) $(LIBJSON_DEPS) +$(o)/ucw-json/libucw-json$(LV).so: SONAME_SUFFIX=.0 +$(o)/ucw-json/libucw-json.pc: $(LIBJSON_DEPS) + +ifdef CONFIG_INSTALL_API +$(o)/ucw-json/libucw-json.pc: $(addprefix $(o)/ucw-json/libucw-json$(LV),.a .so) +endif + +TESTS+=$(o)/ucw-json/json-test.test +$(o)/ucw-json/json-test: $(o)/ucw-json/json-test.o $(LIBJSON) $(LIBUCW) +$(o)/ucw-json/json-test.test: $(o)/ucw-json/json-test + +API_LIBS+=libucw-json +API_INCLUDES+=$(o)/ucw-json/.include-stamp +$(o)/ucw-json/.include-stamp: $(addprefix $(s)/ucw-json/,$(LIBJSON_INCLUDES)) +$(o)/ucw-json/.include-stamp: IDST=ucw-json +run/lib/pkgconfig/libucw-json.pc: $(o)/ucw-json/libucw-json.pc + +INSTALL_TARGETS+=install-libucw-json-lib +install-libucw-json-lib: + install -d -m 755 $(DESTDIR)$(INSTALL_LIB_DIR) + install -m 644 run/lib/libucw-json$(LV).so.0 $(DESTDIR)$(INSTALL_LIB_DIR)/libucw-json$(LV).so.0.0 + ln -sf libucw-json$(LV).so.0.0 $(DESTDIR)$(INSTALL_LIB_DIR)/libucw-json$(LV).so.0 +.PHONY: install-libucw-json-lib + +INSTALL_TARGETS+=install-libucw-json-api +install-libucw-json-api: + install -d -m 755 $(DESTDIR)$(INSTALL_INCLUDE_DIR)/ucw-json $(DESTDIR)$(INSTALL_LIB_DIR) $(DESTDIR)$(INSTALL_PKGCONFIG_DIR) + install -m 644 run/lib/pkgconfig/libucw-json.pc $(DESTDIR)$(INSTALL_PKGCONFIG_DIR) + install -m 644 $(addprefix run/include/ucw-json/,$(LIBJSON_INCLUDES)) $(DESTDIR)$(INSTALL_INCLUDE_DIR)/ucw-json + ln -sf libucw-json$(LV).so.0.0 $(DESTDIR)$(INSTALL_LIB_DIR)/libucw-json$(LV).so + install -m 644 run/lib/libucw-json$(LV).a $(DESTDIR)$(INSTALL_LIB_DIR) +.PHONY: install-libucw-json-api + +include $(s)/ucw-json/doc/Makefile diff --git a/ucw-json/doc/Makefile b/ucw-json/doc/Makefile new file mode 100644 index 00000000..0d7468ea --- /dev/null +++ b/ucw-json/doc/Makefile @@ -0,0 +1,20 @@ +# Makefile for the UCW-JSON documentation + +DIRS+=ucw-json/doc + +JSON_DOCS=json index +JSON_DOCS_HTML=$(addprefix $(o)/ucw-json/doc/,$(addsuffix .html,$(JSON_DOCS))) + +DOCS+=$(JSON_DOCS_HTML) +DOC_MODULES+=ucw-json +$(JSON_DOCS_HTML): DOC_MODULE=ucw-json + +ifdef CONFIG_DOC +INSTALL_TARGETS+=install-libucw-json-docs +endif + +.PHONY: install-libucw-json-docs + +install-libucw-json-docs: $(JSON_DOCS_HTML) + install -d -m 755 $(DESTDIR)$(INSTALL_DOC_DIR)/ucw-json/ + install -m 644 $^ $(DESTDIR)$(INSTALL_DOC_DIR)/ucw-json/ diff --git a/ucw-json/doc/index.txt b/ucw-json/doc/index.txt new file mode 100644 index 00000000..0f1fc691 --- /dev/null +++ b/ucw-json/doc/index.txt @@ -0,0 +1,13 @@ +The UCW-JSON library +==================== + +This library provides a light-weight JSON parser and generator built atop <<../ucw/index:,LibUCW>>. + +Modules +------- +- <> + +Authors +------- + +- Martin Mareš diff --git a/ucw-json/doc/json.txt b/ucw-json/doc/json.txt new file mode 100644 index 00000000..3d88e035 --- /dev/null +++ b/ucw-json/doc/json.txt @@ -0,0 +1,14 @@ +JSON Parser +=========== + +ucw-json/json.h +------------- + +FIXME + +To parse a document, create a parser context (<>), +initialize it with <>, fill in requested parsing mode, pointers to hooks, and +other parameters. Then call <> or <> as you need. At the end, dispose +of the context by <> or recycle it by <>. + +!!ucw-json/json.h diff --git a/ucw-json/json-test.c b/ucw-json/json-test.c new file mode 100644 index 00000000..807ecf12 --- /dev/null +++ b/ucw-json/json-test.c @@ -0,0 +1,22 @@ +/* + * UCW JSON Library -- Tests + * + * (c) 2015 Martin Mares + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#include +#include +#include + +#include +#include +#include + +int +main(int argc, char **argv) +{ + return 0; +} diff --git a/ucw-json/json-test.t b/ucw-json/json-test.t new file mode 100644 index 00000000..8dc9983f --- /dev/null +++ b/ucw-json/json-test.t @@ -0,0 +1,2 @@ +# Tests for the JSON library +# (c) 2015 Martin Mares diff --git a/ucw-json/json.c b/ucw-json/json.c new file mode 100644 index 00000000..35ee49f9 --- /dev/null +++ b/ucw-json/json.c @@ -0,0 +1,93 @@ +/* + * UCW JSON Library -- Data Representation + * + * (c) 2015 Martin Mares + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#undef LOCAL_DEBUG + +#include +#include +#include +#include + +struct json_context *json_new(void) +{ + struct mempool *mp = mp_new(4096); + struct json_context *js = mp_alloc_zero(mp, sizeof(*js)); + js->pool = mp; + mp_save(mp, &js->init_state); + return js; +} + +void json_delete(struct json_context *js) +{ + mp_delete(js->pool); +} + +void json_reset(struct json_context *js) +{ + mp_restore(js->pool, &js->init_state); +} + + +struct json_node *json_new_node(struct json_context *js, enum json_node_type type) +{ + struct json_node *n = mp_alloc_fast(js->pool, sizeof(*n)); + n->type = type; + return n; +} + +struct json_node *json_new_array(struct json_context *js) +{ + struct json_node *n = json_new_node(js, JSON_ARRAY); + GARY_INIT_SPACE_ALLOC(n->elements, 4, mp_get_allocator(js->pool)); + return n; +} + +void json_array_append(struct json_node *array, struct json_node *elt) +{ + ASSERT(array->type == JSON_ARRAY); + *GARY_PUSH(array->elements) = elt; +} + +struct json_node *json_new_object(struct json_context *js) +{ + struct json_node *n = json_new_node(js, JSON_OBJECT); + GARY_INIT_SPACE_ALLOC(n->pairs, 4, mp_get_allocator(js->pool)); + return n; +} + +void json_object_set(struct json_node *n, const char *key, struct json_node *value) +{ + for (size_t i=0; i < GARY_SIZE(n->pairs); i++) + if (!strcmp(n->pairs[i].key, key)) + { + if (value) + n->pairs[i].value = value; + else + { + n->pairs[i] = n->pairs[GARY_SIZE(n->pairs) - 1]; + GARY_POP(n->pairs); + } + return; + } + + if (value) + { + struct json_pair *p = GARY_PUSH(n->pairs); + p->key = key; + p->value = value; + } +} + +struct json_node *json_object_get(struct json_node *n, const char *key) +{ + for (size_t i=0; i < GARY_SIZE(n->pairs); i++) + if (!strcmp(n->pairs[i].key, key)) + return n->pairs[i].value; + return NULL; +} diff --git a/ucw-json/json.h b/ucw-json/json.h new file mode 100644 index 00000000..5340a30e --- /dev/null +++ b/ucw-json/json.h @@ -0,0 +1,127 @@ +/* + * UCW JSON Library + * + * (c) 2015 Martin Mares + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#ifndef _UCW_JSON_JSON_H +#define _UCW_JSON_JSON_H + +#include +#include +#include +#include + +#ifdef CONFIG_UCW_CLEAN_ABI +// FIXME +#endif + +/*** + * === FIXME + ***/ + +struct json_context { + struct mempool *pool; + struct mempool_state init_state; + // FIXME: Size limit? + + struct fastbuf *in_fb; + uint in_line; + bool in_eof; + struct json_node *next_token; + struct json_node *trivial_token; + int next_char; +}; + +struct json_context *json_new(void); +void json_delete(struct json_context *js); +void json_reset(struct json_context *js); + +enum json_node_type { + JSON_INVALID, + JSON_NULL, + JSON_BOOLEAN, + JSON_NUMBER, + JSON_STRING, + JSON_ARRAY, + JSON_OBJECT, + // These are not real nodes, but raw tokens + JSON_BEGIN_ARRAY, + JSON_END_ARRAY, + JSON_BEGIN_OBJECT, + JSON_END_OBJECT, + JSON_NAME_SEP, + JSON_VALUE_SEP, + JSON_EOF, +}; + +struct json_node { + enum json_node_type type; + union { + bool boolean; + double number; + const char *string; + struct json_node **elements; // Growing array + struct json_pair *pairs; // Growing array + }; +}; + +struct json_pair { + const char *key; + struct json_node *value; + // FIXME: Hash table +}; + +struct json_node *json_new_node(struct json_context *js, enum json_node_type type); + +static inline struct json_node *json_new_null(struct json_context *js) +{ + return json_new_node(js, JSON_NULL); +} + +static inline struct json_node *json_new_bool(struct json_context *js, bool value) +{ + struct json_node *n = json_new_node(js, JSON_BOOLEAN); + n->boolean = value; + return n; +} + +static inline struct json_node *json_new_number(struct json_context *js, double value) +{ + struct json_node *n = json_new_node(js, JSON_NUMBER); + n->number = value; + return n; +} + +static inline struct json_node *json_new_string_ref(struct json_context *js, const char *value) +{ + struct json_node *n = json_new_node(js, JSON_STRING); + n->string = value; + return n; +} + +static inline struct json_node *json_new_string(struct json_context *js, const char *value) +{ + return json_new_string_ref(js, mp_strdup(js->pool, value)); +} + +struct json_node *json_new_array(struct json_context *js); +void json_array_append(struct json_node *array, struct json_node *elt); + +struct json_node *json_new_object(struct json_context *js); +// FIXME: key must not be freed +void json_object_set(struct json_node *n, const char *key, struct json_node *value); +struct json_node *json_object_get(struct json_node *n, const char *key); + +void json_set_input(struct json_context *js, struct fastbuf *in); +struct json_node *json_peek_token(struct json_context *js); +struct json_node *json_next_token(struct json_context *js); + +struct json_node *json_next_object(struct json_context *js); + +struct json_node *json_parse(struct json_context *js, struct fastbuf *fb); + +#endif diff --git a/ucw-json/libucw-json.pc b/ucw-json/libucw-json.pc new file mode 100644 index 00000000..905f39c7 --- /dev/null +++ b/ucw-json/libucw-json.pc @@ -0,0 +1,11 @@ +# pkg-config metadata for libucw-json + +libdir=@LIBDIR@ +incdir=. + +Name: libucw-json +Description: JSON parser for LibUCW project +Version: @UCW_VERSION@ +Cflags: -I${incdir} +Libs: -L${libdir} @SO_LINK_PATH@ -lucw-json@UCW_ABI_SUFFIX@ +Requires.private: @DEPS@ diff --git a/ucw-json/parse.c b/ucw-json/parse.c new file mode 100644 index 00000000..181c6afb --- /dev/null +++ b/ucw-json/parse.c @@ -0,0 +1,434 @@ +/* + * UCW JSON Library -- Parser + * + * (c) 2015 Martin Mares + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#undef LOCAL_DEBUG + +#include +#include +#include +#include +#include + +#include +#include + +void json_set_input(struct json_context *js, struct fastbuf *in) +{ + js->in_fb = in; + js->in_line = 1; + js->next_char = -1; + js->next_token = NULL; + js->in_eof = 0; + if (!js->trivial_token) + js->trivial_token = json_new_node(js, JSON_INVALID); +} + +// FIXME: Report column as well as line? +static void NONRET json_parse_error(struct json_context *js, const char *msg) +{ + trans_throw("ucw.js.parse", js, "%s at line %u", msg, js->in_line); +} + +static int json_get_char(struct json_context *js) +{ + int c = bget_utf8_32_repl(js->in_fb, -2); + if (unlikely(c < 0)) + { + if (c == -2) + json_parse_error(js, "Malformed UTF-8 character"); + js->in_eof = 1; + // FIXME: Reject alternative sequences + return c; + } + return c; +} + +static void json_unget_char(struct json_context *js, int c) +{ + js->next_char = c; +} + +static struct json_node *json_triv_token(struct json_context *js, enum json_node_type type) +{ + js->trivial_token->type = type; + return js->trivial_token; +} + +static struct json_node *json_parse_number(struct json_context *js, int c) +{ + mp_push(js->pool); + char *p = mp_start_noalign(js->pool, 0); + + // Optional minus + if (c == '-') + { + p = mp_append_char(js->pool, p, c); + c = json_get_char(js); + if (!(c >= '0' && c <= '9')) + json_parse_error(js, "Malformed number: just minus"); + } + + // Integer part + if (c == '0') + { + // Leading zeroes are forbidden by RFC 7159 + p = mp_append_char(js->pool, p, c); + c = json_get_char(js); + if (c >= '0' && c <= '9') + json_parse_error(js, "Malformed number: leading zero"); + } + else + { + while (c >= '0' && c <= '9') + { + p = mp_append_char(js->pool, p, c); + c = json_get_char(js); + } + } + + // Fractional part + if (c == '.') + { + p = mp_append_char(js->pool, p, c); + if (!(c >= '0' && c <= '9')) + json_parse_error(js, "Malformed number: no digits after decimal point"); + while (c >= '0' && c <= '9') + { + p = mp_append_char(js->pool, p, c); + c = json_get_char(js); + } + } + + // Exponent + if (c == 'e' || c == 'E') + { + p = mp_append_char(js->pool, p, c); + c = json_get_char(js); + if (c == '+' || c == '-') + { + p = mp_append_char(js->pool, p, c); + c = json_get_char(js); + } + if (!(c >= '0' && c <= '9')) + json_parse_error(js, "Malformed number: empty exponent"); + while (c >= '0' && c <= '9') + { + p = mp_append_char(js->pool, p, c); + c = json_get_char(js); + } + } + + json_unget_char(js, c); + + p = mp_end_string(js->pool, p); + errno = 0; + double val = strtod(p, NULL); + if (errno == ERANGE) + json_parse_error(js, "Number out of range"); + mp_pop(js->pool); + + return json_new_number(js, val); +} + +static struct json_node *json_parse_name(struct json_context *js, int c) +{ + mp_push(js->pool); + char *p = mp_start_noalign(js->pool, 0); + + while (c >= 'a' && c <= 'z') + { + p = mp_append_char(js->pool, p, c); + c = json_get_char(js); + } + json_unget_char(js, c); + + p = mp_end_string(js->pool, p); + struct json_node *n; + if (!strcmp(p, "null")) + n = json_new_null(js); + else if (!strcmp(p, "false")) + n = json_new_bool(js, 0); + else if (!strcmp(p, "true")) + n = json_new_bool(js, 1); + else + json_parse_error(js, "Invalid literal name"); + + mp_pop(js->pool); + return n; +} + +static uint json_parse_hex4(struct json_context *js) +{ + uint x = 0; + for (int i=0; i<4; i++) + { + x = x << 4; + int c = json_get_char(js); + if (c >= '0' && c <= '9') + x += c - '0'; + else if (c >= 'a' && c <= 'f') + x += c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + x += c - 'A' + 10; + else + json_parse_error(js, "Invalid Unicode escape sequence"); + } + return x; +} + +static struct json_node *json_parse_string(struct json_context *js, int c) +{ + char *p = mp_start_noalign(js->pool, 0); + + c = json_get_char(js); + while (c != '"') + { + if (unlikely(c < 0x20)) + { + if (c < 0 || c == 0x0d || c == 0x0a) + json_parse_error(js, "Unterminated string"); + else + json_parse_error(js, "Invalid control character in string"); + } + if (unlikely(c >= 0xd800 && c < 0xf900)) + { + if (c < 0xe000) + json_parse_error(js, "Invalid surrogate character in string"); + else + json_parse_error(js, "Invalid private-use character in string"); + } + if (unlikely(c > 0xf0000)) + { + if (c > 0x10ffff) + json_parse_error(js, "Invalid non-Unicode character in string"); + else + json_parse_error(js, "Invalid private-use character in string"); + } + if (c == '\\') + { + c = json_get_char(js); + switch (c) + { + case '"': + case '\\': + case '/': + break; + case 'b': + c = 0x08; + break; + case 'f': + c = 0x0c; + break; + case 'n': + c = 0x0a; + break; + case 'r': + c = 0x0d; + break; + case 't': + c = 0x09; + break; + case 'u': + { + uint x = json_parse_hex4(js); + if (!x) + json_parse_error(js, "Zero bytes in strings are not supported"); + if (x >= 0xd800 && x < 0xf900) + { + if (x < 0xdc00) + { + // High surrogate: low surrogate must follow + uint y = 0; + if (json_get_char(js) == '\\' && json_get_char(js) == 'u') + y = json_parse_hex4(js); + if (!(y >= 0xdc00 && y < 0xe000)) + json_parse_error(js, "Escaped high surrogate codepoint must be followed by a low surrogate codepoint"); + c = 0x10000 | ((x & 0x03ff) << 10) | (y & 0x03ff); + if (c > 0xf0000) + json_parse_error(js, "Invalid escaped private-use character"); + } + else if (x < 0xe000) + { + // Low surrogate + json_parse_error(js, "Invalid escaped surrogate codepoint"); + } + else + json_parse_error(js, "Invalid escaped private-use character"); + } + break; + } + default: + json_parse_error(js, "Invalid backslash sequence in string"); + } + } + p = mp_append_utf8_32(js->pool, p, c); + c = json_get_char(js); + } + + p = mp_end_string(js->pool, p); + return json_new_string_ref(js, p); +} + +struct json_node *json_peek_token(struct json_context *js) +{ + if (unlikely(js->in_eof)) + return json_triv_token(js, JSON_EOF); + + int c = js->next_char; + if (c >= 0) + js->next_char = -1; + else + c = json_get_char(js); + + while (c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d) + { + if (c == 0x0a) + js->in_line++; + c = json_get_char(js); + } + if (c < 0) + return json_triv_token(js, JSON_EOF); + + if (c >= '0' && c <= '9' || c == '-') + return json_parse_number(js, c); + + if (c >= 'a' && c <= 'z') + return json_parse_name(js, c); + + if (c == '"') + return json_parse_string(js, c); + + switch (c) + { + case '[': + return json_triv_token(js, JSON_BEGIN_ARRAY); + case ']': + return json_triv_token(js, JSON_END_ARRAY); + case '{': + return json_triv_token(js, JSON_BEGIN_OBJECT); + case '}': + return json_triv_token(js, JSON_END_OBJECT); + case ':': + return json_triv_token(js, JSON_NAME_SEP); + case ',': + return json_triv_token(js, JSON_VALUE_SEP); + default: + json_parse_error(js, "Invalid character"); + } +} + +struct json_node *json_next_token(struct json_context *js) +{ + if (!js->next_token) + json_peek_token(js); + struct json_node *t = js->next_token; + js->next_token = NULL; + return t; +} + +struct json_node *json_next_object(struct json_context *js) +{ + struct json_node *t = json_next_token(js); + + switch (t->type) + { + case JSON_EOF: + return NULL; + + // Elementary values + case JSON_NULL: + case JSON_BOOLEAN: + case JSON_NUMBER: + case JSON_STRING: + return t; + + // Array + case JSON_BEGIN_ARRAY: + { + struct json_node *a = json_new_array(js); + if (json_peek_token(js)->type == JSON_END_ARRAY) + json_next_token(js); + else for (;;) + { + struct json_node *v = json_next_object(js); + if (!v) + json_parse_error(js, "Unterminated array"); + json_array_append(a, v); + + t = json_next_token(js); + if (t->type == JSON_END_ARRAY) + break; + if (t->type != JSON_VALUE_SEP) + json_parse_error(js, "Comma expected"); + } + return a; + } + + // Object + case JSON_BEGIN_OBJECT: + { + struct json_node *o = json_new_object(js); + if (json_peek_token(js)->type == JSON_END_OBJECT) + json_next_token(js); + else for (;;) + { + struct json_node *k = json_next_object(js); + if (!k) + json_parse_error(js, "Unterminated object"); + if (k->type != JSON_STRING) + json_parse_error(js, "Object key must be a string"); + + t = json_next_token(js); + if (t->type != JSON_NAME_SEP) + json_parse_error(js, "Colon expected"); + + struct json_node *v = json_next_object(js); + if (!v) + json_parse_error(js, "Unterminated object"); + if (json_object_get(o, k->string)) // FIXME: Optimize + json_parse_error(js, "Key already set"); + json_object_set(o, k->string, v); + + t = json_next_token(js); + if (t->type == JSON_END_OBJECT) + break; + if (t->type != JSON_VALUE_SEP) + json_parse_error(js, "Comma expected"); + } + return o; + } + + // Misplaced characters + case JSON_END_ARRAY: + json_parse_error(js, "Misplaced end of array"); + case JSON_END_OBJECT: + json_parse_error(js, "Misplaced end of object"); + case JSON_NAME_SEP: + json_parse_error(js, "Misplaced colon"); + case JSON_VALUE_SEP: + json_parse_error(js, "Misplaced comma"); + default: + ASSERT(0); + } +} + +struct json_node *json_parse(struct json_context *js, struct fastbuf *fb) +{ + json_set_input(js, fb); + + struct json_node *n = json_next_object(js); + if (!n) + json_parse_error(js, "Empty input"); + + struct json_node *t = json_next_token(js); + if (t->type != JSON_EOF) + json_parse_error(js, "Only one top-level value allowed"); + + return n; +} -- 2.39.2