From d6f6f1a7d033b45afdfaf73c4f7f96789441a2ee Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Tue, 2 Jun 2020 05:04:00 -0500 Subject: [PATCH 01/20] bitwise checks on all things jsmntype_t jsmnint_t typedef'd to allow easy changing of main int type jsmntype_t extended to allow more checks JSMN_STRICT now has simplified format checking via parser->expected JSMN_NEXT_SIBLING allow for easy parsing to a parents next sibling --- Makefile | 1 + jsmn.h | 402 ++++++++++++++++++++++++++++++++---------------- test/tests.c | 28 ++-- test/testutil.h | 4 +- 4 files changed, 287 insertions(+), 148 deletions(-) diff --git a/Makefile b/Makefile index dcbdd89d..9ff174e3 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ clean: rm -f *.o example/*.o rm -f simple_example rm -f jsondump + rm -f test/test_default test/test_strict test/test_links test/test_strict_links .PHONY: clean test diff --git a/jsmn.h b/jsmn.h index 3178dcc9..db587678 100644 --- a/jsmn.h +++ b/jsmn.h @@ -38,82 +38,119 @@ extern "C" { /** * JSON type identifier. Basic types are: - * o Object - * o Array - * o String - * o Other primitive: number, boolean (true/false) or null */ typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1, - JSMN_ARRAY = 2, - JSMN_STRING = 3, - JSMN_PRIMITIVE = 4 + JSMN_UNDEFINED = 0x00, + JSMN_OBJECT = 0x01, //!< Object + JSMN_ARRAY = 0x02, //!< Array + JSMN_STRING = 0x04, //!< String + JSMN_PRIMITIVE = 0x08, //!< Other primitive: number, boolean (true/false) or null + + JSMN_KEY = 0x10, //!< is a key + JSMN_VALUE = 0x20, //!< is a value + + JSMN_CLOSE = 0x40, //!< Close OBJECT '}' or ARRAY ']' + JSMN_DELIMITER = 0x80, //!< Colon ':' after KEY, Comma ',' after VALUE + + // Combined elements for valid enum tests + JSMN_ANY_TYPE = JSMN_OBJECT | JSMN_ARRAY | JSMN_STRING | JSMN_PRIMITIVE, + + JSMN_OBJ_VAL = JSMN_OBJECT | JSMN_VALUE, + JSMN_ARR_VAL = JSMN_ARRAY | JSMN_VALUE, + JSMN_STR_KEY = JSMN_STRING | JSMN_KEY, + JSMN_STR_VAL = JSMN_STRING | JSMN_VALUE, + JSMN_PRI_VAL = JSMN_PRIMITIVE | JSMN_VALUE, +#if !defined(JSMN_STRICT) + JSMN_OBJ_KEY = JSMN_OBJECT | JSMN_KEY, + JSMN_ARR_KEY = JSMN_ARRAY | JSMN_KEY, + JSMN_PRI_KEY = JSMN_PRIMITIVE | JSMN_KEY, +#endif } jsmntype_t; enum jsmnerr { - /* Not enough tokens were provided */ - JSMN_ERROR_NOMEM = -1, - /* Invalid character inside JSON string */ - JSMN_ERROR_INVAL = -2, - /* The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_PART = -3 + JSMN_SUCCESS = 0, + JSMN_ERROR_NOMEM = -1, //!< Not enough tokens were provided + JSMN_ERROR_INVAL = -2, //!< Invalid character inside JSON string + JSMN_ERROR_PART = -3, //!< The string is not a full JSON packet, more bytes expected }; +typedef unsigned int jsmnint_t; +#define JSMN_NEG ((jsmnint_t)-1) + /** * JSON token description. - * type type (object, array, string etc.) - * start start position in JSON data string - * end end position in JSON data string */ -typedef struct jsmntok { - jsmntype_t type; - int start; - int end; - int size; +typedef struct jsmntok_t { + jsmntype_t type; //!< type (object, array, string etc.) + jsmnint_t start; //!< start position in JSON data string + jsmnint_t end; //!< end position in JSON data string + jsmnint_t size; //!< number of children #ifdef JSMN_PARENT_LINKS - int parent; + jsmnint_t parent; //!< parent id +#endif +#ifdef JSMN_NEXT_SIBLING + jsmnint_t next_sibling; //!< next sibling id #endif } jsmntok_t; /** - * JSON parser. Contains an array of token blocks available. Also stores + * JSON parser + * + * Contains an array of token blocks available. Also stores * the string being parsed now and current position in that string. */ typedef struct jsmn_parser { - unsigned int pos; /* offset in the JSON string */ - unsigned int toknext; /* next token to allocate */ - int toksuper; /* superior token node, e.g. parent object or array */ + jsmnint_t pos; //!< offset in the JSON string + jsmnint_t toknext; //!< next token to allocate + jsmnint_t toksuper; //!< superior token node, e.g. parent object or array + jsmntype_t expected; //!< Expected jsmn type(s) } jsmn_parser; /** - * Create JSON parser over an array of tokens + * @brief Create JSON parser over an array of tokens + * + * @param[out] parser jsmn parser */ -JSMN_API void jsmn_init(jsmn_parser *parser); +JSMN_API +void jsmn_init(jsmn_parser *parser); /** - * Run JSON parser. It parses a JSON data string into and array of tokens, each - * describing - * a single JSON object. + * @brief Run JSON parser + * + * It parses a JSON data string into and array of tokens, each + * describing a single JSON object. + * + * @param[in,out] parser jsmn parser + * @param[in] js JSON data string + * @param[in] len JSON data string length + * @param[in,out] tokens pointer to memory allocated for tokens or NULL + * @param[in] num_tokens number of tokens allocated + * @return jsmnint_t number of tokens found or ERRNO */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens); +JSMN_API +jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens); #ifndef JSMN_HEADER /** * Allocates a fresh unused token from the token pool. */ -static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, - const size_t num_tokens) { +static +jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, + const size_t num_tokens) { jsmntok_t *tok; if (parser->toknext >= num_tokens) { return NULL; } tok = &tokens[parser->toknext++]; - tok->start = tok->end = -1; + tok->start = tok->end = JSMN_NEG; tok->size = 0; #ifdef JSMN_PARENT_LINKS - tok->parent = -1; + tok->parent = JSMN_NEG; +#endif +#ifdef JSMN_NEXT_SIBLING + tok->next_sibling = JSMN_NEG; #endif return tok; } @@ -121,29 +158,60 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, /** * Fills token type and boundaries. */ -static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, - const int start, const int end) { +static +void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, + const jsmnint_t start, const jsmnint_t end) { token->type = type; token->start = start; token->end = end; token->size = 0; } +#ifdef JSMN_NEXT_SIBLING +/** + * Set previous child's next_sibling to current token + */ +static +void jsmn_next_sibling(jsmn_parser *parser, jsmntok_t *tokens) { + // Start with parent's first child + jsmnint_t sibling = parser->toksuper + 1; + + // If the first child is the current token + if (sibling == parser->toknext - 1) + return; + + // Loop until we find previous sibling + while (tokens[sibling].next_sibling != JSMN_NEG) + sibling = tokens[sibling].next_sibling; + + // Set previous sibling's next_sibling to current token + tokens[sibling].next_sibling = parser->toknext - 1; +} +#endif + /** * Fills next available token with JSON primitive. */ -static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { +static +jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { +#ifdef JSMN_STRICT + // If a primitive wasn't expected + if (tokens != NULL && !(parser->expected & JSMN_PRIMITIVE)) { + return JSMN_ERROR_INVAL; + } +#endif + jsmntok_t *token; - int start; + jsmnint_t start; start = parser->pos; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { #ifndef JSMN_STRICT - /* In strict mode primitive must be followed by "," or "}" or "]" */ + // In strict mode primitive must be followed by "," or "}" or "]" case ':': #endif case '\t': @@ -154,8 +222,7 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, case ']': case '}': goto found; - default: - /* to quiet a warning from gcc*/ + default: // to quiet a warning from gcc break; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { @@ -164,7 +231,7 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, } } #ifdef JSMN_STRICT - /* In strict mode primitive must be followed by a comma/object/array */ + // In strict mode primitive must be followed by a comma/object/array parser->pos = start; return JSMN_ERROR_PART; #endif @@ -172,7 +239,7 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, found: if (tokens == NULL) { parser->pos--; - return 0; + return JSMN_SUCCESS; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { @@ -180,33 +247,49 @@ static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_STRICT + token->type |= JSMN_VALUE; + parser->expected = (JSMN_DELIMITER | JSMN_CLOSE); +#endif #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; +#endif +#ifdef JSMN_NEXT_SIBLING + jsmn_next_sibling(parser, tokens); #endif parser->pos--; - return 0; + return JSMN_SUCCESS; } /** * Fills next token with JSON string. */ -static int jsmn_parse_string(jsmn_parser *parser, const char *js, - const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { +static +jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { +#ifdef JSMN_STRICT + // If a string wasn't expected + if (tokens != NULL && !(parser->expected & JSMN_STRING)) { + return JSMN_ERROR_INVAL; + } +#endif + jsmntok_t *token; - int start = parser->pos; + char c; + jsmnint_t i, start = parser->pos; parser->pos++; - /* Skip starting quote */ + // Skip starting quote for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c = js[parser->pos]; + c = js[parser->pos]; - /* Quote: end of string */ + // Quote: end of string if (c == '\"') { if (tokens == NULL) { - return 0; + return JSMN_SUCCESS; } token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { @@ -214,18 +297,31 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); +#ifdef JSMN_STRICT + // If the parent type is an object and the previous token is an object or value + if ((tokens[parser->toksuper].type & JSMN_OBJECT) && + (tokens[parser->toknext - 2].type & (JSMN_OBJECT | JSMN_VALUE))) { + token->type |= JSMN_KEY; + parser->expected = JSMN_DELIMITER; + } else { + token->type |= JSMN_VALUE; + parser->expected = (JSMN_DELIMITER | JSMN_CLOSE); + } +#endif #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif - return 0; +#ifdef JSMN_NEXT_SIBLING + jsmn_next_sibling(parser, tokens); +#endif + return JSMN_SUCCESS; } - /* Backslash: Quoted symbol expected */ + // Backslash: Quoted symbol expected if (c == '\\' && parser->pos + 1 < len) { - int i; parser->pos++; switch (js[parser->pos]) { - /* Allowed escaped symbols */ + // Allowed escaped symbols case '\"': case '/': case '\\': @@ -235,15 +331,14 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, case 'n': case 't': break; - /* Allows escaped symbol \uXXXX */ + // Allows escaped symbol \uXXXX case 'u': parser->pos++; - for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; - i++) { - /* If it isn't a hex character we have an error */ - if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + // If it isn't a hex character we have an error + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || // 0-9 + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || // A-F + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { // a-f parser->pos = start; return JSMN_ERROR_INVAL; } @@ -251,7 +346,7 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, } parser->pos--; break; - /* Unexpected symbol */ + // Unexpected symbol default: parser->pos = start; return JSMN_ERROR_INVAL; @@ -265,17 +360,18 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, /** * Parse JSON string and fill tokens. */ -JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, - jsmntok_t *tokens, const unsigned int num_tokens) { - int r; - int i; +JSMN_API +jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, + const size_t len, jsmntok_t *tokens, + const size_t num_tokens) { + jsmnint_t r; + jsmnint_t i; jsmntok_t *token; - int count = parser->toknext; + jsmnint_t count = parser->toknext; + char c; + jsmntype_t type; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - char c; - jsmntype_t type; - c = js[parser->pos]; switch (c) { case '{': @@ -288,20 +384,29 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, if (token == NULL) { return JSMN_ERROR_NOMEM; } - if (parser->toksuper != -1) { - jsmntok_t *t = &tokens[parser->toksuper]; + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_STRICT - /* In strict mode an object or array can't become a key */ - if (t->type == JSMN_OBJECT) { - return JSMN_ERROR_INVAL; - } + // If an OBJECT or ARRAY (respectively) wasn't expected + if (!(parser->expected & token->type)) { + return JSMN_ERROR_INVAL; + } + + token->type |= JSMN_VALUE; + if (token->type & JSMN_OBJECT) { + parser->expected = (JSMN_STRING | JSMN_CLOSE); + } else { + parser->expected = (JSMN_ANY_TYPE | JSMN_CLOSE); + } #endif - t->size++; + if (parser->toksuper != JSMN_NEG) { + tokens[parser->toksuper].size++; #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; +#endif +#ifdef JSMN_NEXT_SIBLING + jsmn_next_sibling(parser, tokens); #endif } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); token->start = parser->pos; parser->toksuper = parser->toknext - 1; break; @@ -310,6 +415,11 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, if (tokens == NULL) { break; } +#ifdef JSMN_STRICT + if (!(parser->expected & JSMN_CLOSE)) { + return JSMN_ERROR_INVAL; + } +#endif type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_PARENT_LINKS if (parser->toknext < 1) { @@ -317,16 +427,16 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, } token = &tokens[parser->toknext - 1]; for (;;) { - if (token->start != -1 && token->end == -1) { - if (token->type != type) { + if (token->start != JSMN_NEG && token->end == JSMN_NEG) { + if (!(token->type & type)) { return JSMN_ERROR_INVAL; } token->end = parser->pos + 1; parser->toksuper = token->parent; break; } - if (token->parent == -1) { - if (token->type != type || parser->toksuper == -1) { + if (token->parent == JSMN_NEG) { + if (!(token->type & type) || parser->toksuper == JSMN_NEG) { return JSMN_ERROR_INVAL; } break; @@ -334,37 +444,44 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, token = &tokens[token->parent]; } #else - for (i = parser->toknext - 1; i >= 0; i--) { + for (i = parser->toknext - 1; i != JSMN_NEG; i--) { token = &tokens[i]; - if (token->start != -1 && token->end == -1) { - if (token->type != type) { + if (token->start != JSMN_NEG && token->end == JSMN_NEG) { + if (!(token->type & type)) { return JSMN_ERROR_INVAL; } - parser->toksuper = -1; + parser->toksuper = JSMN_NEG; token->end = parser->pos + 1; break; } } - /* Error if unmatched closing bracket */ - if (i == -1) { + // Error if unmatched closing bracket + if (i == JSMN_NEG) { return JSMN_ERROR_INVAL; } - for (; i >= 0; i--) { + for (; i != JSMN_NEG; i--) { token = &tokens[i]; - if (token->start != -1 && token->end == -1) { + if (token->start != JSMN_NEG && token->end == JSMN_NEG) { parser->toksuper = i; break; } } +#endif +#ifdef JSMN_STRICT + if (parser->toksuper == JSMN_NEG) { + parser->expected = (JSMN_OBJECT | JSMN_ARRAY); + } else { + parser->expected = (JSMN_DELIMITER | JSMN_CLOSE); + } #endif break; case '\"': r = jsmn_parse_string(parser, js, len, tokens, num_tokens); - if (r < 0) { + if (r != JSMN_SUCCESS) { return r; } count++; - if (parser->toksuper != -1 && tokens != NULL) { + if (tokens != NULL && parser->toksuper != JSMN_NEG) { tokens[parser->toksuper].size++; } break; @@ -374,28 +491,57 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, case ' ': break; case ':': + if (tokens != NULL && parser->toksuper != JSMN_NEG) { +#ifdef JSMN_STRICT + // If a DELIMITER wasn't expected or the previous token wasn't a KEY + if (!(parser->expected & JSMN_DELIMITER) || + !(tokens[parser->toknext - 1].type & JSMN_KEY)) { + return JSMN_ERROR_INVAL; + } + parser->expected = JSMN_ANY_TYPE; +#else + // ':' is not a valid DELIMITER in an ARRAY + if (tokens[parser->toksuper].type & JSMN_ARRAY) { + return JSMN_ERROR_INVAL; + } +#endif + } parser->toksuper = parser->toknext - 1; break; case ',': - if (tokens != NULL && parser->toksuper != -1 && - tokens[parser->toksuper].type != JSMN_ARRAY && - tokens[parser->toksuper].type != JSMN_OBJECT) { + if (tokens != NULL && parser->toksuper != JSMN_NEG) { +#ifdef JSMN_STRICT + // If a DELIMITER wasn't expected or the previous token was a KEY + if (!(parser->expected & JSMN_DELIMITER) || + tokens[parser->toknext - 1].type & JSMN_KEY) { + return JSMN_ERROR_INVAL; + } + // If this is in an OBJECT, a STRING KEY must follow a comma + if (tokens[parser->toksuper].type & JSMN_OBJECT) { + parser->expected = JSMN_STRING; + // else this is in an ARRAY which allows ANY_TYPE to follow + } else { + parser->expected = JSMN_ANY_TYPE; + } +#endif + if (!(tokens[parser->toksuper].type & (JSMN_OBJECT | JSMN_ARRAY))) { #ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; + parser->toksuper = tokens[parser->toksuper].parent; #else - for (i = parser->toknext - 1; i >= 0; i--) { - if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { - if (tokens[i].start != -1 && tokens[i].end == -1) { - parser->toksuper = i; - break; + for (i = parser->toknext - 1; i != JSMN_NEG; i--) { + if (tokens[i].type & (JSMN_OBJECT | JSMN_ARRAY)) { + if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { + parser->toksuper = i; + break; + } } } - } #endif + } } break; #ifdef JSMN_STRICT - /* In strict mode primitives are: numbers and booleans */ + // In strict mode primitives are: numbers and booleans case '-': case '0': case '1': @@ -410,30 +556,22 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, case 't': case 'f': case 'n': - /* And they must not be keys of the object */ - if (tokens != NULL && parser->toksuper != -1) { - const jsmntok_t *t = &tokens[parser->toksuper]; - if (t->type == JSMN_OBJECT || - (t->type == JSMN_STRING && t->size != 0)) { - return JSMN_ERROR_INVAL; - } - } #else - /* In non-strict mode every unquoted value is a primitive */ + // In non-strict mode every unquoted value is a primitive default: #endif r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); - if (r < 0) { + if (r != JSMN_SUCCESS) { return r; } count++; - if (parser->toksuper != -1 && tokens != NULL) { + if (tokens != NULL && parser->toksuper != JSMN_NEG) { tokens[parser->toksuper].size++; } break; #ifdef JSMN_STRICT - /* Unexpected char in strict mode */ + // Unexpected char in strict mode default: return JSMN_ERROR_INVAL; #endif @@ -441,9 +579,9 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, } if (tokens != NULL) { - for (i = parser->toknext - 1; i >= 0; i--) { - /* Unmatched opened object or array */ - if (tokens[i].start != -1 && tokens[i].end == -1) { + for (i = parser->toknext - 1; i != JSMN_NEG; i--) { + // Unmatched opened object or array + if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { return JSMN_ERROR_PART; } } @@ -456,16 +594,18 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, * Creates a new parser based over a given buffer with an array of tokens * available. */ -JSMN_API void jsmn_init(jsmn_parser *parser) { +JSMN_API +void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; - parser->toksuper = -1; + parser->toksuper = JSMN_NEG; + parser->expected = (JSMN_OBJECT | JSMN_ARRAY); } -#endif /* JSMN_HEADER */ +#endif // JSMN_HEADER #ifdef __cplusplus } #endif -#endif /* JSMN_H */ +#endif // JSMN_H diff --git a/test/tests.c b/test/tests.c index d8a4d922..ce9f77e8 100644 --- a/test/tests.c +++ b/test/tests.c @@ -36,13 +36,12 @@ int test_object(void) { check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); -/* FIXME */ -/*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/ -/*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/ -/*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/ -/*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/ -/*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/ -/*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/ + check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2)); + check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4)); + check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4)); + check(parse("{,}", JSMN_ERROR_INVAL, 4)); #endif return 0; } @@ -53,8 +52,7 @@ int test_array(void) { /*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/ check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10")); check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); - /* FIXME */ - /*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/ + check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3)); return 0; } @@ -97,7 +95,7 @@ int test_string(void) { } int test_partial_string(void) { - int r; + jsmnint_t r; unsigned long i; jsmn_parser p; jsmntok_t tok[5]; @@ -120,7 +118,7 @@ int test_partial_string(void) { int test_partial_array(void) { #ifdef JSMN_STRICT - int r; + jsmnint_t r; unsigned long i; jsmn_parser p; jsmntok_t tok[10]; @@ -144,7 +142,7 @@ int test_partial_array(void) { int test_array_nomem(void) { int i; - int r; + jsmnint_t r; jsmn_parser p; jsmntok_t toksmall[10], toklarge[10]; const char *js; @@ -171,7 +169,7 @@ int test_array_nomem(void) { int test_unquoted_keys(void) { #ifndef JSMN_STRICT - int r; + jsmnint_t r; jsmn_parser p; jsmntok_t tok[10]; const char *js; @@ -188,7 +186,7 @@ int test_unquoted_keys(void) { } int test_issue_22(void) { - int r; + jsmnint_t r; jsmn_parser p; jsmntok_t tokens[128]; const char *js; @@ -219,7 +217,7 @@ int test_issue_27(void) { int test_input_length(void) { const char *js; - int r; + jsmnint_t r; jsmn_parser p; jsmntok_t tokens[10]; diff --git a/test/testutil.h b/test/testutil.h index bdee1393..936ad2f1 100644 --- a/test/testutil.h +++ b/test/testutil.h @@ -28,7 +28,7 @@ static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok, size = va_arg(ap, int); value = NULL; } - if (t[i].type != type) { + if (!(t[i].type & type)) { printf("token %lu type is %d, not %d\n", i, t[i].type, type); return 0; } @@ -71,7 +71,7 @@ static int tokeq(const char *s, jsmntok_t *tokens, unsigned long numtok, ...) { } static int parse(const char *s, int status, unsigned long numtok, ...) { - int r; + jsmnint_t r; int ok = 1; va_list args; jsmn_parser p; From 14ecc274a1c4d185873e09faf9e17a8c12b831af Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Tue, 2 Jun 2020 05:51:51 -0500 Subject: [PATCH 02/20] Update README.md --- README.md | 101 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index f8249f3d..858b7085 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ JSMN [![Build Status](https://travis-ci.org/zserge/jsmn.svg?branch=master)](https://travis-ci.org/zserge/jsmn) -jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be +jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be easily integrated into resource-limited or embedded projects. You can find more information about JSON format at [json.org][1] @@ -11,7 +11,7 @@ You can find more information about JSON format at [json.org][1] Library sources are available at https://github.com/zserge/jsmn The web page with some information about jsmn can be found at -[http://zserge.com/jsmn.html][2] +[https://zserge.com/jsmn.html][2] Philosophy ---------- @@ -19,7 +19,7 @@ Philosophy Most JSON parsers offer you a bunch of functions to load JSON data, parse it and extract any value by its name. jsmn proves that checking the correctness of every JSON packet or allocating temporary objects to store parsed JSON fields -often is an overkill. +often is an overkill. JSON format itself is extremely simple, so why should we complicate it? @@ -46,12 +46,12 @@ Design ------ The rudimentary jsmn object is a **token**. Let's consider a JSON string: - - '{ "name" : "Jack", "age" : 27 }' - +```json +{ "name" : "Jack", "age" : 27 } +``` It holds the following tokens: -* Object: `{ "name" : "Jack", "age" : 27}` (the whole object) +* Object: `{ "name" : "Jack", "age" : 27 }` (the whole object) * Strings: `"name"`, `"Jack"`, `"age"` (keys and some values) * Number: `27` @@ -64,8 +64,7 @@ token. jsmn supports the following token types: * Object - a container of key-value pairs, e.g.: `{ "foo":"bar", "x":0.3 }` -* Array - a sequence of values, e.g.: - `[ 1, 2, 3 ]` +* Array - a sequence of values, e.g.: `[ 1, 2, 3 ]` * String - a quoted sequence of chars, e.g.: `"foo"` * Primitive - a number, a boolean (`true`, `false`) or `null` @@ -81,12 +80,12 @@ Usage Download `jsmn.h`, include it, done. -``` +```c #include "jsmn.h" ... jsmn_parser p; -jsmntok_t t[128]; /* We expect no more than 128 JSON tokens */ +jsmntok_t t[128]; // We expect no more than 128 JSON tokens jsmn_init(&p); r = jsmn_parse(&p, s, strlen(s), t, 128); @@ -95,14 +94,15 @@ r = jsmn_parse(&p, s, strlen(s), t, 128); Since jsmn is a single-header, header-only library, for more complex use cases you might need to define additional macros. `#define JSMN_STATIC` hides all jsmn API symbols by making them static. Also, if you want to include `jsmn.h` -from multiple C files, to avoid duplication of symbols you may define `JSMN_HEADER` macro. +from multiple C files, to avoid duplication of symbols you may define +`JSMN_HEADER` macro. -``` -/* In every .c file that uses jsmn include only declarations: */ +```c +// In every .c file that uses jsmn include only declarations: #define JSMN_HEADER #include "jsmn.h" -/* Additionally, create one jsmn.c file for jsmn implementation: */ +// Additionally, create one jsmn.c file for jsmn implementation: #include "jsmn.h" ``` @@ -110,53 +110,56 @@ API --- Token types are described by `jsmntype_t`: - - typedef enum { - JSMN_UNDEFINED = 0, - JSMN_OBJECT = 1, - JSMN_ARRAY = 2, - JSMN_STRING = 3, - JSMN_PRIMITIVE = 4 - } jsmntype_t; - +```c +typedef enum { + JSMN_UNDEFINED = 0x00, + JSMN_OBJECT = 0x01, //!< Object + JSMN_ARRAY = 0x02, //!< Array + JSMN_STRING = 0x04, //!< String + JSMN_PRIMITIVE = 0x08, //!< Other primitive: number, boolean (true/false) or null + ... +} jsmntype_t; +``` **Note:** Unlike JSON data types, primitive tokens are not divided into numbers, booleans and null, because one can easily tell the type using the first character: -* 't', 'f' - boolean +* 't', 'f' - boolean * 'n' - null * '-', '0'..'9' - number Token is an object of `jsmntok_t` type: - - typedef struct { - jsmntype_t type; // Token type - int start; // Token start position - int end; // Token end position - int size; // Number of child (nested) tokens - } jsmntok_t; - -**Note:** string tokens point to the first character after -the opening quote and the previous symbol before final quote. This was made -to simplify string extraction from JSON data. +```c +typedef struct { + jsmntype_t type; //!< type (object, array, string etc.) + jsmnint_t start; //!< start position in JSON data string + jsmnint_t end; //!< end position in JSON data string + jsmnint_t size; //!< number of children + ... +} jsmntok_t; +``` +**Note:** string tokens point to the first character after the opening quote +and the previous symbol before final quote. This was made to simplify string +extraction from JSON data. All job is done by `jsmn_parser` object. You can initialize a new parser using: +```c +jsmn_parser parser; +jsmntok_t tokens[10]; - jsmn_parser parser; - jsmntok_t tokens[10]; - - jsmn_init(&parser); - - // js - pointer to JSON string - // tokens - an array of tokens available - // 10 - number of tokens available - jsmn_parse(&parser, js, strlen(js), tokens, 10); +jsmn_init(&parser); +// js - pointer to JSON string +// tokens - an array of tokens available +// 10 - number of tokens available +jsmn_parse(&parser, js, strlen(js), tokens, 10); +``` This will create a parser, and then it tries to parse up to 10 JSON tokens from the `js` string. A non-negative return value of `jsmn_parse` is the number of tokens actually used by the parser. + Passing NULL instead of the tokens array would not store parsing results, but instead the function will return the number of tokens needed to parse the given string. This can be useful if you don't know yet how many tokens to allocate. @@ -175,8 +178,8 @@ You will get this error until you reach the end of JSON data. Other info ---------- -This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php), - so feel free to integrate it in your commercial products. +This software is distributed under [MIT license](https://www.opensource.org/licenses/mit-license.php), +so feel free to integrate it in your commercial products. -[1]: http://www.json.org/ -[2]: http://zserge.com/jsmn.html +[1]: https://www.json.org/ +[2]: https://zserge.com/jsmn.html From d5d1a5013ae9d8f9e3fb49b2cbf8089caa19a19a Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Tue, 2 Jun 2020 07:01:00 -0500 Subject: [PATCH 03/20] README.md verbage (PR #114) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 858b7085..388f8913 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,8 @@ typedef struct { and the previous symbol before final quote. This was made to simplify string extraction from JSON data. -All job is done by `jsmn_parser` object. You can initialize a new parser using: +All jobs are done by the `jsmn_parser` object. You can initialize a new parser +using: ```c jsmn_parser parser; jsmntok_t tokens[10]; From 025c4b3448733c49d57a41da068d155db58f91a6 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Tue, 2 Jun 2020 22:53:16 -0500 Subject: [PATCH 04/20] Order escapes and whitespace to match rfc8259. --- jsmn.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jsmn.h b/jsmn.h index db587678..ed530fae 100644 --- a/jsmn.h +++ b/jsmn.h @@ -214,10 +214,10 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, // In strict mode primitive must be followed by "," or "}" or "]" case ':': #endif + case ' ': case '\t': - case '\r': case '\n': - case ' ': + case '\r': case ',': case ']': case '}': @@ -323,12 +323,12 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, switch (js[parser->pos]) { // Allowed escaped symbols case '\"': - case '/': case '\\': + case '/': case 'b': case 'f': - case 'r': case 'n': + case 'r': case 't': break; // Allows escaped symbol \uXXXX @@ -485,10 +485,10 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, tokens[parser->toksuper].size++; } break; + case ' ': case '\t': - case '\r': case '\n': - case ' ': + case '\r': break; case ':': if (tokens != NULL && parser->toksuper != JSMN_NEG) { From 8c715ec766154f8b2aad75ae4474652af125798d Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Tue, 2 Jun 2020 22:55:56 -0500 Subject: [PATCH 05/20] Replace possibly confusing bitwise checks with inline functions. --- jsmn.h | 84 ++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/jsmn.h b/jsmn.h index ed530fae..cf82151c 100644 --- a/jsmn.h +++ b/jsmn.h @@ -132,6 +132,24 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens); +/** + * @brief Check if a token is of a certain type + * + * This is a helper function to avoid confusion with bitwise operators. + * + * This is a permissive function in that if the type you are testing for is a + * STRING that is also a KEY it will return true if the token is either a STRING + * or a KEY. If you want a strict test against a complex type, compare the + * return value with the complex type. + * e.g. if (jsmn_is_type(token, JSMN_CMPLX) == JSMN_CMPLX) ... + * + * @param[in] token pointer to a jsmn token + * @param[in] type jsmntype_t to compare + * @return jsmnint_t 0 if false, + */ +JSMN_API +jsmntype_t jsmn_is_type(const jsmntok_t *token, const jsmntype_t type); + #ifndef JSMN_HEADER /** * Allocates a fresh unused token from the token pool. @@ -189,6 +207,18 @@ void jsmn_next_sibling(jsmn_parser *parser, jsmntok_t *tokens) { } #endif +extern inline +jsmntype_t jsmn_is_type(const jsmntok_t *token, const jsmntype_t type) +{ + return (token->type & type); +} + +static inline +jsmntype_t jsmn_is_expected(const jsmn_parser *parser, const jsmntype_t type) +{ + return (parser->expected & type); +} + /** * Fills next available token with JSON primitive. */ @@ -197,8 +227,9 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { #ifdef JSMN_STRICT - // If a primitive wasn't expected - if (tokens != NULL && !(parser->expected & JSMN_PRIMITIVE)) { + // If a PRIMITIVE wasn't expected + if (tokens != NULL && + !jsmn_is_expected(parser, JSMN_PRIMITIVE)) { return JSMN_ERROR_INVAL; } #endif @@ -249,7 +280,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); #ifdef JSMN_STRICT token->type |= JSMN_VALUE; - parser->expected = (JSMN_DELIMITER | JSMN_CLOSE); + parser->expected = JSMN_DELIMITER | JSMN_CLOSE; #endif #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; @@ -269,8 +300,9 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { #ifdef JSMN_STRICT - // If a string wasn't expected - if (tokens != NULL && !(parser->expected & JSMN_STRING)) { + // If a STRING wasn't expected + if (tokens != NULL && + !jsmn_is_expected(parser, JSMN_STRING)) { return JSMN_ERROR_INVAL; } #endif @@ -299,13 +331,13 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); #ifdef JSMN_STRICT // If the parent type is an object and the previous token is an object or value - if ((tokens[parser->toksuper].type & JSMN_OBJECT) && - (tokens[parser->toknext - 2].type & (JSMN_OBJECT | JSMN_VALUE))) { + if (jsmn_is_type(&tokens[parser->toksuper], JSMN_OBJECT) && + jsmn_is_type(&tokens[parser->toknext - 2], JSMN_OBJECT | JSMN_VALUE)) { token->type |= JSMN_KEY; parser->expected = JSMN_DELIMITER; } else { token->type |= JSMN_VALUE; - parser->expected = (JSMN_DELIMITER | JSMN_CLOSE); + parser->expected = JSMN_DELIMITER | JSMN_CLOSE; } #endif #ifdef JSMN_PARENT_LINKS @@ -387,15 +419,15 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_STRICT // If an OBJECT or ARRAY (respectively) wasn't expected - if (!(parser->expected & token->type)) { + if (!jsmn_is_expected(parser, token->type)) { return JSMN_ERROR_INVAL; } token->type |= JSMN_VALUE; - if (token->type & JSMN_OBJECT) { - parser->expected = (JSMN_STRING | JSMN_CLOSE); + if (jsmn_is_type(token, JSMN_OBJECT)) { + parser->expected = JSMN_STRING | JSMN_CLOSE; } else { - parser->expected = (JSMN_ANY_TYPE | JSMN_CLOSE); + parser->expected = JSMN_ANY_TYPE | JSMN_CLOSE; } #endif if (parser->toksuper != JSMN_NEG) { @@ -416,7 +448,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; } #ifdef JSMN_STRICT - if (!(parser->expected & JSMN_CLOSE)) { + if (!jsmn_is_expected(parser, JSMN_CLOSE)) { return JSMN_ERROR_INVAL; } #endif @@ -428,7 +460,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, token = &tokens[parser->toknext - 1]; for (;;) { if (token->start != JSMN_NEG && token->end == JSMN_NEG) { - if (!(token->type & type)) { + if (!jsmn_is_type(token, type)) { return JSMN_ERROR_INVAL; } token->end = parser->pos + 1; @@ -436,7 +468,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; } if (token->parent == JSMN_NEG) { - if (!(token->type & type) || parser->toksuper == JSMN_NEG) { + if (!jsmn_is_type(token, type) || parser->toksuper == JSMN_NEG) { return JSMN_ERROR_INVAL; } break; @@ -447,7 +479,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, for (i = parser->toknext - 1; i != JSMN_NEG; i--) { token = &tokens[i]; if (token->start != JSMN_NEG && token->end == JSMN_NEG) { - if (!(token->type & type)) { + if (!jsmn_is_type(token, type)) { return JSMN_ERROR_INVAL; } parser->toksuper = JSMN_NEG; @@ -469,9 +501,9 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, #endif #ifdef JSMN_STRICT if (parser->toksuper == JSMN_NEG) { - parser->expected = (JSMN_OBJECT | JSMN_ARRAY); + parser->expected = JSMN_OBJECT | JSMN_ARRAY; } else { - parser->expected = (JSMN_DELIMITER | JSMN_CLOSE); + parser->expected = JSMN_DELIMITER | JSMN_CLOSE; } #endif break; @@ -494,14 +526,14 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, if (tokens != NULL && parser->toksuper != JSMN_NEG) { #ifdef JSMN_STRICT // If a DELIMITER wasn't expected or the previous token wasn't a KEY - if (!(parser->expected & JSMN_DELIMITER) || - !(tokens[parser->toknext - 1].type & JSMN_KEY)) { + if (!jsmn_is_expected(parser, JSMN_DELIMITER) || + !jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY)) { return JSMN_ERROR_INVAL; } parser->expected = JSMN_ANY_TYPE; #else // ':' is not a valid DELIMITER in an ARRAY - if (tokens[parser->toksuper].type & JSMN_ARRAY) { + if (jsmn_is_type(&tokens[parser->toksuper], JSMN_ARRAY)) { return JSMN_ERROR_INVAL; } #endif @@ -512,24 +544,24 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, if (tokens != NULL && parser->toksuper != JSMN_NEG) { #ifdef JSMN_STRICT // If a DELIMITER wasn't expected or the previous token was a KEY - if (!(parser->expected & JSMN_DELIMITER) || - tokens[parser->toknext - 1].type & JSMN_KEY) { + if (!jsmn_is_expected(parser, JSMN_DELIMITER) || + jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY)) { return JSMN_ERROR_INVAL; } // If this is in an OBJECT, a STRING KEY must follow a comma - if (tokens[parser->toksuper].type & JSMN_OBJECT) { + if (jsmn_is_type(&tokens[parser->toksuper], JSMN_OBJECT)) { parser->expected = JSMN_STRING; // else this is in an ARRAY which allows ANY_TYPE to follow } else { parser->expected = JSMN_ANY_TYPE; } #endif - if (!(tokens[parser->toksuper].type & (JSMN_OBJECT | JSMN_ARRAY))) { + if (!jsmn_is_type(&tokens[parser->toksuper], JSMN_OBJECT | JSMN_ARRAY)) { #ifdef JSMN_PARENT_LINKS parser->toksuper = tokens[parser->toksuper].parent; #else for (i = parser->toknext - 1; i != JSMN_NEG; i--) { - if (tokens[i].type & (JSMN_OBJECT | JSMN_ARRAY)) { + if (jsmn_is_type(&tokens[i], JSMN_OBJECT | JSMN_ARRAY)) { if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { parser->toksuper = i; break; From 98953298e204721a2d922dede7d662d47ef8d6f2 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Tue, 2 Jun 2020 23:30:14 -0500 Subject: [PATCH 06/20] Make strict mode default, add JSMN_PERMISSIVE for old default. --- Makefile | 12 ++++++------ jsmn.h | 41 ++++++++++++++++++++--------------------- test/tests.c | 10 +++++----- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 9ff174e3..f818d19c 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,18 @@ # You can put your build options here -include config.mk -test: test_default test_strict test_links test_strict_links +test: test_default test_permissive test_links test_permissive_links test_default: test/tests.c jsmn.h $(CC) $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ -test_strict: test/tests.c jsmn.h - $(CC) -DJSMN_STRICT=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ +test_permissive: test/tests.c jsmn.h + $(CC) -DJSMN_DEFINES=1 -DJSMN_PERMISSIVE=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ test_links: test/tests.c jsmn.h $(CC) -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ -test_strict_links: test/tests.c jsmn.h - $(CC) -DJSMN_STRICT=1 -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ +test_permissive_links: test/tests.c jsmn.h + $(CC) -DJSMN_DEFINES=1 -DJSMN_PERMISSIVE=1 -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ simple_example: example/simple.c jsmn.h @@ -31,7 +31,7 @@ clean: rm -f *.o example/*.o rm -f simple_example rm -f jsondump - rm -f test/test_default test/test_strict test/test_links test/test_strict_links + rm -f test/test_default test/test_permissive test/test_links test/test_permissive_links .PHONY: clean test diff --git a/jsmn.h b/jsmn.h index cf82151c..4f496d53 100644 --- a/jsmn.h +++ b/jsmn.h @@ -60,7 +60,7 @@ typedef enum { JSMN_STR_KEY = JSMN_STRING | JSMN_KEY, JSMN_STR_VAL = JSMN_STRING | JSMN_VALUE, JSMN_PRI_VAL = JSMN_PRIMITIVE | JSMN_VALUE, -#if !defined(JSMN_STRICT) +#ifdef JSMN_PERMISSIVE JSMN_OBJ_KEY = JSMN_OBJECT | JSMN_KEY, JSMN_ARR_KEY = JSMN_ARRAY | JSMN_KEY, JSMN_PRI_KEY = JSMN_PRIMITIVE | JSMN_KEY, @@ -226,7 +226,7 @@ static jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE // If a PRIMITIVE wasn't expected if (tokens != NULL && !jsmn_is_expected(parser, JSMN_PRIMITIVE)) { @@ -241,8 +241,8 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { -#ifndef JSMN_STRICT - // In strict mode primitive must be followed by "," or "}" or "]" +#ifdef JSMN_PERMISSIVE + // In permissive mode, PRIMITIVEs may be followed by ":" case ':': #endif case ' ': @@ -261,8 +261,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } } -#ifdef JSMN_STRICT - // In strict mode primitive must be followed by a comma/object/array +#ifndef JSMN_PERMISSIVE parser->pos = start; return JSMN_ERROR_PART; #endif @@ -278,7 +277,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE token->type |= JSMN_VALUE; parser->expected = JSMN_DELIMITER | JSMN_CLOSE; #endif @@ -299,7 +298,7 @@ static jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE // If a STRING wasn't expected if (tokens != NULL && !jsmn_is_expected(parser, JSMN_STRING)) { @@ -329,8 +328,8 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); -#ifdef JSMN_STRICT - // If the parent type is an object and the previous token is an object or value +#ifndef JSMN_PERMISSIVE + // If the parent type is an OBJECT and the previous token is an OBJECT or VALUE if (jsmn_is_type(&tokens[parser->toksuper], JSMN_OBJECT) && jsmn_is_type(&tokens[parser->toknext - 2], JSMN_OBJECT | JSMN_VALUE)) { token->type |= JSMN_KEY; @@ -417,7 +416,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, return JSMN_ERROR_NOMEM; } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE // If an OBJECT or ARRAY (respectively) wasn't expected if (!jsmn_is_expected(parser, token->type)) { return JSMN_ERROR_INVAL; @@ -447,7 +446,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, if (tokens == NULL) { break; } -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE if (!jsmn_is_expected(parser, JSMN_CLOSE)) { return JSMN_ERROR_INVAL; } @@ -499,7 +498,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, } } #endif -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE if (parser->toksuper == JSMN_NEG) { parser->expected = JSMN_OBJECT | JSMN_ARRAY; } else { @@ -524,7 +523,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; case ':': if (tokens != NULL && parser->toksuper != JSMN_NEG) { -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE // If a DELIMITER wasn't expected or the previous token wasn't a KEY if (!jsmn_is_expected(parser, JSMN_DELIMITER) || !jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY)) { @@ -542,7 +541,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; case ',': if (tokens != NULL && parser->toksuper != JSMN_NEG) { -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE // If a DELIMITER wasn't expected or the previous token was a KEY if (!jsmn_is_expected(parser, JSMN_DELIMITER) || jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY)) { @@ -572,8 +571,8 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, } } break; -#ifdef JSMN_STRICT - // In strict mode primitives are: numbers and booleans +#ifndef JSMN_PERMISSIVE + // rfc8259: PRIMITIVEs are numbers and booleans case '-': case '0': case '1': @@ -589,7 +588,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, case 'f': case 'n': #else - // In non-strict mode every unquoted value is a primitive + // In permissive mode every unquoted value is a PRIMITIVE default: #endif r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); @@ -602,8 +601,8 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, } break; -#ifdef JSMN_STRICT - // Unexpected char in strict mode +#ifndef JSMN_PERMISSIVE + // Unexpected char default: return JSMN_ERROR_INVAL; #endif @@ -612,7 +611,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, if (tokens != NULL) { for (i = parser->toknext - 1; i != JSMN_NEG; i--) { - // Unmatched opened object or array + // Unmatched opened OBJECT or ARRAY if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { return JSMN_ERROR_PART; } diff --git a/test/tests.c b/test/tests.c index ce9f77e8..82a088fb 100644 --- a/test/tests.c +++ b/test/tests.c @@ -30,7 +30,7 @@ int test_object(void) { JSMN_STRING, "a", 1, JSMN_PRIMITIVE, "0", JSMN_STRING, "b", 1, JSMN_STRING, "c", 0)); -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3)); check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); @@ -117,7 +117,7 @@ int test_partial_string(void) { } int test_partial_array(void) { -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE jsmnint_t r; unsigned long i; jsmn_parser p; @@ -168,7 +168,7 @@ int test_array_nomem(void) { } int test_unquoted_keys(void) { -#ifndef JSMN_STRICT +#ifdef JSMN_PERMISSIVE jsmnint_t r; jsmn_parser p; jsmntok_t tok[10]; @@ -279,7 +279,7 @@ int test_count(void) { } int test_nonstrict(void) { -#ifndef JSMN_STRICT +#ifdef JSMN_PERMISSIVE const char *js; js = "a: 0garbage"; check(parse(js, 2, 2, JSMN_PRIMITIVE, "a", JSMN_PRIMITIVE, "0garbage")); @@ -321,7 +321,7 @@ int test_object_key(void) { js = "{\"key\": 1}"; check(parse(js, 3, 3, JSMN_OBJECT, 0, 10, 1, JSMN_STRING, "key", 1, JSMN_PRIMITIVE, "1")); -#ifdef JSMN_STRICT +#ifndef JSMN_PERMISSIVE js = "{true: 1}"; check(parse(js, JSMN_ERROR_INVAL, 3)); js = "{1: 1}"; From 5287a5039dc0ab746d130c4158e3433a3d28abc1 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Wed, 3 Jun 2020 15:52:06 -0500 Subject: [PATCH 07/20] Extend the parser->expected functionality to PERMISSIVE mode. --- jsmn.h | 142 +++++++++++++++++++++++++++++++++++++++------------ test/tests.c | 10 ++-- 2 files changed, 116 insertions(+), 36 deletions(-) diff --git a/jsmn.h b/jsmn.h index 4f496d53..d11e5bfe 100644 --- a/jsmn.h +++ b/jsmn.h @@ -30,10 +30,12 @@ extern "C" { #endif -#ifdef JSMN_STATIC -#define JSMN_API static -#else -#define JSMN_API extern +#ifndef JSMN_API +# ifdef JSMN_STATIC +# define JSMN_API static +# else +# define JSMN_API extern +# endif #endif /** @@ -52,7 +54,8 @@ typedef enum { JSMN_CLOSE = 0x40, //!< Close OBJECT '}' or ARRAY ']' JSMN_DELIMITER = 0x80, //!< Colon ':' after KEY, Comma ',' after VALUE - // Combined elements for valid enum tests + // Combined elements + JSMN_CONTAINER = JSMN_OBJECT | JSMN_ARRAY, JSMN_ANY_TYPE = JSMN_OBJECT | JSMN_ARRAY | JSMN_STRING | JSMN_PRIMITIVE, JSMN_OBJ_VAL = JSMN_OBJECT | JSMN_VALUE, @@ -191,8 +194,14 @@ void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, */ static void jsmn_next_sibling(jsmn_parser *parser, jsmntok_t *tokens) { + jsmnint_t sibling; + // Start with parent's first child - jsmnint_t sibling = parser->toksuper + 1; + if (parser->toksuper != JSMN_NEG) { + sibling = parser->toksuper + 1; + } else { + sibling = 0; + } // If the first child is the current token if (sibling == parser->toknext - 1) @@ -226,12 +235,23 @@ static jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { -#ifndef JSMN_PERMISSIVE // If a PRIMITIVE wasn't expected if (tokens != NULL && !jsmn_is_expected(parser, JSMN_PRIMITIVE)) { return JSMN_ERROR_INVAL; } + +#ifdef JSMN_PERMISSIVE +# ifdef JSMN_PARENT_LINKS + // If either a PRIMITIVE or DELIMITER were expected and this PRIMITIVE is + // following a KEY/VALUE pair, a comma didn't separate the last element + // and this element so parser->toksuper needs to be fixed. + if (tokens != NULL && parser->toknext >= 2 && + jsmn_is_expected(parser, JSMN_DELIMITER) && + jsmn_is_type(&tokens[parser->toknext - 2], JSMN_KEY)) { + parser->toksuper = tokens[parser->toksuper].parent; + } +# endif #endif jsmntok_t *token; @@ -279,7 +299,32 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); #ifndef JSMN_PERMISSIVE token->type |= JSMN_VALUE; +#else + if (parser->toksuper != JSMN_NEG && + jsmn_is_type(&tokens[parser->toksuper], JSMN_KEY)) { + token->type |= JSMN_VALUE; + } +#endif parser->expected = JSMN_DELIMITER | JSMN_CLOSE; +#ifdef JSMN_PERMISSIVE +# ifdef JSMN_PARENT_LINKS + if (parser->toksuper != JSMN_NEG && + tokens[parser->toksuper].parent == JSMN_NEG) { + parser->expected |= JSMN_ANY_TYPE; + } +# else + if (parser->toksuper != JSMN_NEG) { + for (jsmnint_t i = parser->toksuper; i != JSMN_NEG; i--) { + if (jsmn_is_type(&tokens[i], JSMN_CONTAINER)) { + break; + } + if (i != 0) { + continue; + } + parser->expected |= JSMN_ANY_TYPE; + } + } +# endif #endif #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; @@ -298,12 +343,23 @@ static jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { -#ifndef JSMN_PERMISSIVE // If a STRING wasn't expected if (tokens != NULL && !jsmn_is_expected(parser, JSMN_STRING)) { return JSMN_ERROR_INVAL; } + +#ifdef JSMN_PERMISSIVE +# ifdef JSMN_PARENT_LINKS + // If both a PRIMITIVE and DELIMITER were expected and this STRING is + // following a KEY/VALUE pair, a comma didn't separate the last element + // and this element so parser->toksuper needs to be fixed. + if (tokens != NULL && parser->toknext >= 2 && + jsmn_is_expected(parser, JSMN_DELIMITER) && + jsmn_is_type(&tokens[parser->toknext - 2], JSMN_KEY)) { + parser->toksuper = tokens[parser->toksuper].parent; + } +# endif #endif jsmntok_t *token; @@ -338,6 +394,13 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, token->type |= JSMN_VALUE; parser->expected = JSMN_DELIMITER | JSMN_CLOSE; } +#else + // If the previous token is an KEY + if (parser->toknext >= 2 && + jsmn_is_type(&tokens[parser->toknext - 2], JSMN_KEY)) { + token->type |= JSMN_VALUE; + } + parser->expected = JSMN_ANY_TYPE | JSMN_DELIMITER | JSMN_CLOSE; #endif #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; @@ -416,18 +479,20 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, return JSMN_ERROR_NOMEM; } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); -#ifndef JSMN_PERMISSIVE // If an OBJECT or ARRAY (respectively) wasn't expected if (!jsmn_is_expected(parser, token->type)) { return JSMN_ERROR_INVAL; } token->type |= JSMN_VALUE; +#ifndef JSMN_PERMISSIVE if (jsmn_is_type(token, JSMN_OBJECT)) { parser->expected = JSMN_STRING | JSMN_CLOSE; } else { parser->expected = JSMN_ANY_TYPE | JSMN_CLOSE; } +#else + parser->expected = JSMN_ANY_TYPE | JSMN_CLOSE; #endif if (parser->toksuper != JSMN_NEG) { tokens[parser->toksuper].size++; @@ -446,11 +511,9 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, if (tokens == NULL) { break; } -#ifndef JSMN_PERMISSIVE if (!jsmn_is_expected(parser, JSMN_CLOSE)) { return JSMN_ERROR_INVAL; } -#endif type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); #ifdef JSMN_PARENT_LINKS if (parser->toknext < 1) { @@ -498,13 +561,16 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, } } #endif -#ifndef JSMN_PERMISSIVE if (parser->toksuper == JSMN_NEG) { - parser->expected = JSMN_OBJECT | JSMN_ARRAY; +#ifndef JSMN_PERMISSIVE + parser->expected = JSMN_CONTAINER; +#else + tokens[parser->toknext - 1].type |= JSMN_VALUE; + parser->expected = JSMN_ANY_TYPE; +#endif } else { parser->expected = JSMN_DELIMITER | JSMN_CLOSE; } -#endif break; case '\"': r = jsmn_parse_string(parser, js, len, tokens, num_tokens); @@ -522,29 +588,33 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, case '\r': break; case ':': - if (tokens != NULL && parser->toksuper != JSMN_NEG) { + // If a DELIMITER wasn't expected + if (!jsmn_is_expected(parser, JSMN_DELIMITER)) { + return JSMN_ERROR_INVAL; + } #ifndef JSMN_PERMISSIVE - // If a DELIMITER wasn't expected or the previous token wasn't a KEY - if (!jsmn_is_expected(parser, JSMN_DELIMITER) || - !jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY)) { - return JSMN_ERROR_INVAL; - } - parser->expected = JSMN_ANY_TYPE; + if (tokens != NULL && + // Only simple single allowed + (parser->toksuper == JSMN_NEG || + // If the previous token wasn't a KEY + !jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY))) { + return JSMN_ERROR_INVAL; + } #else - // ':' is not a valid DELIMITER in an ARRAY - if (jsmn_is_type(&tokens[parser->toksuper], JSMN_ARRAY)) { - return JSMN_ERROR_INVAL; - } + tokens[parser->toknext - 1].type |= JSMN_KEY; #endif - } + parser->expected = JSMN_ANY_TYPE; parser->toksuper = parser->toknext - 1; break; case ',': if (tokens != NULL && parser->toksuper != JSMN_NEG) { + // If a DELIMITER wasn't expected + if (!jsmn_is_expected(parser, JSMN_DELIMITER)) { + return JSMN_ERROR_INVAL; + } #ifndef JSMN_PERMISSIVE - // If a DELIMITER wasn't expected or the previous token was a KEY - if (!jsmn_is_expected(parser, JSMN_DELIMITER) || - jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY)) { + // If the previous token was a KEY + if (jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY)) { return JSMN_ERROR_INVAL; } // If this is in an OBJECT, a STRING KEY must follow a comma @@ -554,13 +624,17 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, } else { parser->expected = JSMN_ANY_TYPE; } +#else + // The previous token + tokens[parser->toknext - 1].type |= JSMN_VALUE; + parser->expected = JSMN_ANY_TYPE; #endif - if (!jsmn_is_type(&tokens[parser->toksuper], JSMN_OBJECT | JSMN_ARRAY)) { + if (!jsmn_is_type(&tokens[parser->toksuper], JSMN_CONTAINER)) { #ifdef JSMN_PARENT_LINKS parser->toksuper = tokens[parser->toksuper].parent; #else for (i = parser->toknext - 1; i != JSMN_NEG; i--) { - if (jsmn_is_type(&tokens[i], JSMN_OBJECT | JSMN_ARRAY)) { + if (jsmn_is_type(&tokens[i], JSMN_CONTAINER)) { if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { parser->toksuper = i; break; @@ -630,7 +704,11 @@ void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; parser->toksuper = JSMN_NEG; - parser->expected = (JSMN_OBJECT | JSMN_ARRAY); +#ifndef JSMN_PERMISSIVE + parser->expected = JSMN_CONTAINER; +#else + parser->expected = JSMN_ANY_TYPE; +#endif } #endif // JSMN_HEADER diff --git a/test/tests.c b/test/tests.c index 82a088fb..a7ea19e3 100644 --- a/test/tests.c +++ b/test/tests.c @@ -47,12 +47,15 @@ int test_object(void) { } int test_array(void) { - /* FIXME */ - /*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/ - /*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/ + check(parse("[10}", JSMN_ERROR_INVAL, 3)); + check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)); check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10")); check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); +#ifndef JSMN_PERMISSIVE check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3)); +#else + check(parse("[\"a\": 1]", 3, 3, JSMN_ARRAY, -1, -1, 1, JSMN_STRING, "a", 1, JSMN_PRIMITIVE, "1")); +#endif return 0; } @@ -292,7 +295,6 @@ int test_nonstrict(void) { /* nested {s don't cause a parse error. */ js = "\"key {1\": 1234"; check(parse(js, 2, 2, JSMN_STRING, "key {1", 1, JSMN_PRIMITIVE, "1234")); - #endif return 0; } From b595be1f1a3453eaa67ad1fd0b07849bdb517b79 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Wed, 3 Jun 2020 16:06:06 -0500 Subject: [PATCH 08/20] prepare for better primitive checks --- jsmn.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/jsmn.h b/jsmn.h index d11e5bfe..f43f22b8 100644 --- a/jsmn.h +++ b/jsmn.h @@ -68,6 +68,18 @@ typedef enum { JSMN_ARR_KEY = JSMN_ARRAY | JSMN_KEY, JSMN_PRI_KEY = JSMN_PRIMITIVE | JSMN_KEY, #endif + + JSMN_PRI_LITERAL = 0x0100, //!< true, false, null + + JSMN_PRI_ZERO = 0x0400, //!< 0 + JSMN_PRI_DIGIT = 0x0800, //!< 1 - 9 + JSMN_PRI_MINUS = 0x1000, //!< minus sign, '-' + JSMN_PRI_PLUS = 0x2000, //!< plus sign, '+' + JSMN_PRI_DECIMAL = 0x4000, //!< deminal point '.' + JSMN_PRI_EXPONENT = 0x8000, //!< exponent, 'e' or 'E' + + JSMN_PRI_NUMBER = JSMN_PRI_ZERO | JSMN_PRI_DIGIT, + JSMN_PRI_SIGN = JSMN_PRI_PLUS | JSMN_PRI_MINUS, } jsmntype_t; enum jsmnerr { From 96d72215e57e765cfaf168365a676ea7089f7077 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Thu, 4 Jun 2020 12:17:32 -0500 Subject: [PATCH 09/20] rfc 8259 primitive parsing After the parse, primitive tokens can be queried with JSMN_PRI_MINUS for negative numbers, JSMN_PRI_DECIMAL for fractions, and JSMN_PRI_EXPONENT for numbers with exponents. JSMN_PRI_LITERAL denotes 'true', 'false', or 'null'. --- jsmn.h | 249 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 178 insertions(+), 71 deletions(-) diff --git a/jsmn.h b/jsmn.h index f43f22b8..82a81432 100644 --- a/jsmn.h +++ b/jsmn.h @@ -147,24 +147,6 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens); -/** - * @brief Check if a token is of a certain type - * - * This is a helper function to avoid confusion with bitwise operators. - * - * This is a permissive function in that if the type you are testing for is a - * STRING that is also a KEY it will return true if the token is either a STRING - * or a KEY. If you want a strict test against a complex type, compare the - * return value with the complex type. - * e.g. if (jsmn_is_type(token, JSMN_CMPLX) == JSMN_CMPLX) ... - * - * @param[in] token pointer to a jsmn token - * @param[in] type jsmntype_t to compare - * @return jsmnint_t 0 if false, - */ -JSMN_API -jsmntype_t jsmn_is_type(const jsmntok_t *token, const jsmntype_t type); - #ifndef JSMN_HEADER /** * Allocates a fresh unused token from the token pool. @@ -228,18 +210,6 @@ void jsmn_next_sibling(jsmn_parser *parser, jsmntok_t *tokens) { } #endif -extern inline -jsmntype_t jsmn_is_type(const jsmntok_t *token, const jsmntype_t type) -{ - return (token->type & type); -} - -static inline -jsmntype_t jsmn_is_expected(const jsmn_parser *parser, const jsmntype_t type) -{ - return (parser->expected & type); -} - /** * Fills next available token with JSON primitive. */ @@ -249,41 +219,180 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t num_tokens) { // If a PRIMITIVE wasn't expected if (tokens != NULL && - !jsmn_is_expected(parser, JSMN_PRIMITIVE)) { + !(parser->expected & JSMN_PRIMITIVE)) { return JSMN_ERROR_INVAL; } -#ifdef JSMN_PERMISSIVE + jsmntok_t *token; + jsmnint_t start; + jsmntype_t type; + + start = parser->pos; + type = JSMN_PRIMITIVE; + +#ifndef JSMN_PERMISSIVE + if (js[parser->pos] == 't' || js[parser->pos] == 'f' || js[parser->pos] == 'n') { + char *literal = NULL; + jsmnint_t size = 0; + if (js[parser->pos] == 't') { + literal = "true"; + size = 4; + } else if (js[parser->pos] == 'f') { + literal = "false"; + size = 5; + } else if (js[parser->pos] == 'n') { + literal = "null"; + size = 4; + } + jsmnint_t i; + for (i = 1, parser->pos++; i < size; i++, parser->pos++) { + if (parser->pos >= len || js[parser->pos] == '\0') { + parser->pos = start; + return JSMN_ERROR_PART; + } else if (js[parser->pos] != literal[i]) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + type |= JSMN_PRI_LITERAL; + } else { + // If there are no extra JSMN_PRI_* flags + if ((parser->expected & 0xFF00) == 0) { + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_MINUS | JSMN_PRI_NUMBER; + } + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { + case '0': + if (!(parser->expected & JSMN_PRI_ZERO)) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + if (type & JSMN_PRI_EXPONENT) { + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + } else if (type & JSMN_PRI_DECIMAL) { + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER |JSMN_PRI_EXPONENT; + } else if (start == parser->pos || + (start + 1 == parser->pos && (type & JSMN_PRI_MINUS))) { + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + } else { + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + } + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!(parser->expected & JSMN_PRI_DIGIT)) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + if (type & JSMN_PRI_EXPONENT) { + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + } else if (type & JSMN_PRI_DECIMAL) { + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER | JSMN_PRI_EXPONENT; + } else { + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + } + break; + case '-': + if (!(parser->expected & JSMN_PRI_MINUS)) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + if (start == parser->pos) { + type |= JSMN_PRI_MINUS; + } + break; + case '+': + if (!(parser->expected & JSMN_PRI_PLUS)) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + break; + case '.': + if (!(parser->expected & JSMN_PRI_DECIMAL)) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + type |= JSMN_PRI_DECIMAL; + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + break; + case 'e': + case 'E': + if (!(parser->expected & JSMN_PRI_EXPONENT)) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + type |= JSMN_PRI_EXPONENT; + parser->expected = JSMN_PRIMITIVE | JSMN_PRI_SIGN | JSMN_PRI_NUMBER; + break; + default: + if (parser->pos >= len || js[parser->pos] == '\0') { + parser->pos = start; + return JSMN_ERROR_PART; + } else if (start + 1 == parser->pos && (type & JSMN_PRI_MINUS)) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + goto check_primitive_border; + } + } + } +check_primitive_border: + switch (js[parser->pos]) { + case ' ': + case '\t': + case '\n': + case '\r': + case ',': + case '}': + case ']': + goto found; + case '"': + case ':': + case '{': + case '[': + parser->pos = start; + return JSMN_ERROR_INVAL; + case '\0': + if (parser->toksuper != JSMN_NEG) { + parser->pos = start; + return JSMN_ERROR_PART; + } + goto found; + default: + parser->pos = start; + return JSMN_ERROR_PART; + } +#else # ifdef JSMN_PARENT_LINKS // If either a PRIMITIVE or DELIMITER were expected and this PRIMITIVE is // following a KEY/VALUE pair, a comma didn't separate the last element // and this element so parser->toksuper needs to be fixed. if (tokens != NULL && parser->toknext >= 2 && - jsmn_is_expected(parser, JSMN_DELIMITER) && - jsmn_is_type(&tokens[parser->toknext - 2], JSMN_KEY)) { + (parser->expected & JSMN_DELIMITER) && + (tokens[parser->toknext - 2].type & JSMN_KEY)) { parser->toksuper = tokens[parser->toksuper].parent; } # endif -#endif - - jsmntok_t *token; - jsmnint_t start; - - start = parser->pos; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { -#ifdef JSMN_PERMISSIVE - // In permissive mode, PRIMITIVEs may be followed by ":" - case ':': -#endif case ' ': case '\t': case '\n': case '\r': case ',': - case ']': case '}': + case ']': + case ':': goto found; default: // to quiet a warning from gcc break; @@ -293,9 +402,6 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } } -#ifndef JSMN_PERMISSIVE - parser->pos = start; - return JSMN_ERROR_PART; #endif found: @@ -308,12 +414,12 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, parser->pos = start; return JSMN_ERROR_NOMEM; } - jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); + jsmn_fill_token(token, type, start, parser->pos); #ifndef JSMN_PERMISSIVE token->type |= JSMN_VALUE; #else if (parser->toksuper != JSMN_NEG && - jsmn_is_type(&tokens[parser->toksuper], JSMN_KEY)) { + (tokens[parser->toksuper].type & JSMN_KEY)) { token->type |= JSMN_VALUE; } #endif @@ -326,8 +432,9 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } # else if (parser->toksuper != JSMN_NEG) { - for (jsmnint_t i = parser->toksuper; i != JSMN_NEG; i--) { - if (jsmn_is_type(&tokens[i], JSMN_CONTAINER)) { + jsmnint_t i; + for (i = parser->toksuper; i != JSMN_NEG; i--) { + if (tokens[i].type & JSMN_CONTAINER) { break; } if (i != 0) { @@ -357,7 +464,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t num_tokens) { // If a STRING wasn't expected if (tokens != NULL && - !jsmn_is_expected(parser, JSMN_STRING)) { + !(parser->expected & JSMN_STRING)) { return JSMN_ERROR_INVAL; } @@ -367,8 +474,8 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, // following a KEY/VALUE pair, a comma didn't separate the last element // and this element so parser->toksuper needs to be fixed. if (tokens != NULL && parser->toknext >= 2 && - jsmn_is_expected(parser, JSMN_DELIMITER) && - jsmn_is_type(&tokens[parser->toknext - 2], JSMN_KEY)) { + (parser->expected & JSMN_DELIMITER) && + (tokens[parser->toknext - 2].type & JSMN_KEY)) { parser->toksuper = tokens[parser->toksuper].parent; } # endif @@ -398,8 +505,8 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); #ifndef JSMN_PERMISSIVE // If the parent type is an OBJECT and the previous token is an OBJECT or VALUE - if (jsmn_is_type(&tokens[parser->toksuper], JSMN_OBJECT) && - jsmn_is_type(&tokens[parser->toknext - 2], JSMN_OBJECT | JSMN_VALUE)) { + if ((tokens[parser->toksuper].type & JSMN_OBJECT) && + (tokens[parser->toknext - 2].type & (JSMN_OBJECT | JSMN_VALUE))) { token->type |= JSMN_KEY; parser->expected = JSMN_DELIMITER; } else { @@ -409,7 +516,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, #else // If the previous token is an KEY if (parser->toknext >= 2 && - jsmn_is_type(&tokens[parser->toknext - 2], JSMN_KEY)) { + (tokens[parser->toknext - 2].type & JSMN_KEY)) { token->type |= JSMN_VALUE; } parser->expected = JSMN_ANY_TYPE | JSMN_DELIMITER | JSMN_CLOSE; @@ -492,13 +599,13 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); // If an OBJECT or ARRAY (respectively) wasn't expected - if (!jsmn_is_expected(parser, token->type)) { + if (!(parser->expected & token->type)) { return JSMN_ERROR_INVAL; } token->type |= JSMN_VALUE; #ifndef JSMN_PERMISSIVE - if (jsmn_is_type(token, JSMN_OBJECT)) { + if (token->type & JSMN_OBJECT) { parser->expected = JSMN_STRING | JSMN_CLOSE; } else { parser->expected = JSMN_ANY_TYPE | JSMN_CLOSE; @@ -523,7 +630,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, if (tokens == NULL) { break; } - if (!jsmn_is_expected(parser, JSMN_CLOSE)) { + if (!(parser->expected & JSMN_CLOSE)) { return JSMN_ERROR_INVAL; } type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); @@ -534,7 +641,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, token = &tokens[parser->toknext - 1]; for (;;) { if (token->start != JSMN_NEG && token->end == JSMN_NEG) { - if (!jsmn_is_type(token, type)) { + if (!(token->type & type)) { return JSMN_ERROR_INVAL; } token->end = parser->pos + 1; @@ -542,7 +649,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; } if (token->parent == JSMN_NEG) { - if (!jsmn_is_type(token, type) || parser->toksuper == JSMN_NEG) { + if (!(token->type & type) || parser->toksuper == JSMN_NEG) { return JSMN_ERROR_INVAL; } break; @@ -553,7 +660,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, for (i = parser->toknext - 1; i != JSMN_NEG; i--) { token = &tokens[i]; if (token->start != JSMN_NEG && token->end == JSMN_NEG) { - if (!jsmn_is_type(token, type)) { + if (!(token->type & type)) { return JSMN_ERROR_INVAL; } parser->toksuper = JSMN_NEG; @@ -601,7 +708,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; case ':': // If a DELIMITER wasn't expected - if (!jsmn_is_expected(parser, JSMN_DELIMITER)) { + if (!(parser->expected & JSMN_DELIMITER)) { return JSMN_ERROR_INVAL; } #ifndef JSMN_PERMISSIVE @@ -609,7 +716,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, // Only simple single allowed (parser->toksuper == JSMN_NEG || // If the previous token wasn't a KEY - !jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY))) { + !(tokens[parser->toknext - 1].type & JSMN_KEY))) { return JSMN_ERROR_INVAL; } #else @@ -621,16 +728,16 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, case ',': if (tokens != NULL && parser->toksuper != JSMN_NEG) { // If a DELIMITER wasn't expected - if (!jsmn_is_expected(parser, JSMN_DELIMITER)) { + if (!(parser->expected & JSMN_DELIMITER)) { return JSMN_ERROR_INVAL; } #ifndef JSMN_PERMISSIVE // If the previous token was a KEY - if (jsmn_is_type(&tokens[parser->toknext - 1], JSMN_KEY)) { + if (tokens[parser->toknext - 1].type & JSMN_KEY) { return JSMN_ERROR_INVAL; } // If this is in an OBJECT, a STRING KEY must follow a comma - if (jsmn_is_type(&tokens[parser->toksuper], JSMN_OBJECT)) { + if (tokens[parser->toksuper].type & JSMN_OBJECT) { parser->expected = JSMN_STRING; // else this is in an ARRAY which allows ANY_TYPE to follow } else { @@ -641,12 +748,12 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, tokens[parser->toknext - 1].type |= JSMN_VALUE; parser->expected = JSMN_ANY_TYPE; #endif - if (!jsmn_is_type(&tokens[parser->toksuper], JSMN_CONTAINER)) { + if (!(tokens[parser->toksuper].type & JSMN_CONTAINER)) { #ifdef JSMN_PARENT_LINKS parser->toksuper = tokens[parser->toksuper].parent; #else for (i = parser->toknext - 1; i != JSMN_NEG; i--) { - if (jsmn_is_type(&tokens[i], JSMN_CONTAINER)) { + if (tokens[i].type & JSMN_CONTAINER) { if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { parser->toksuper = i; break; From 3c00c19a06e8b965bb9f109b560700b59f60d9da Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Thu, 4 Jun 2020 12:19:42 -0500 Subject: [PATCH 10/20] c89 compliance This undoes my previous comment preference, but I would rather keep c89 compliance. The Makefile not also compiles with the c89 standard. Removed some flags that have no use in this branch. --- Makefile | 6 ++-- jsmn.h | 86 ++++++++++++++++++++++++++++---------------------------- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index f818d19c..aa6da9d4 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,20 @@ # You can put your build options here -include config.mk +CFLAGS:=${CFLAGS} -std=c89 + test: test_default test_permissive test_links test_permissive_links test_default: test/tests.c jsmn.h $(CC) $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ test_permissive: test/tests.c jsmn.h - $(CC) -DJSMN_DEFINES=1 -DJSMN_PERMISSIVE=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ + $(CC) -DJSMN_PERMISSIVE=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ test_links: test/tests.c jsmn.h $(CC) -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ test_permissive_links: test/tests.c jsmn.h - $(CC) -DJSMN_DEFINES=1 -DJSMN_PERMISSIVE=1 -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ + $(CC) -DJSMN_PERMISSIVE=1 -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ ./test/$@ simple_example: example/simple.c jsmn.h diff --git a/jsmn.h b/jsmn.h index 82a81432..a0c2ea71 100644 --- a/jsmn.h +++ b/jsmn.h @@ -54,7 +54,7 @@ typedef enum { JSMN_CLOSE = 0x40, //!< Close OBJECT '}' or ARRAY ']' JSMN_DELIMITER = 0x80, //!< Colon ':' after KEY, Comma ',' after VALUE - // Combined elements + /* Combined elements */ JSMN_CONTAINER = JSMN_OBJECT | JSMN_ARRAY, JSMN_ANY_TYPE = JSMN_OBJECT | JSMN_ARRAY | JSMN_STRING | JSMN_PRIMITIVE, @@ -190,22 +190,22 @@ static void jsmn_next_sibling(jsmn_parser *parser, jsmntok_t *tokens) { jsmnint_t sibling; - // Start with parent's first child + /* Start with parent's first child */ if (parser->toksuper != JSMN_NEG) { sibling = parser->toksuper + 1; } else { sibling = 0; } - // If the first child is the current token + /* If the first child is the current token */ if (sibling == parser->toknext - 1) return; - // Loop until we find previous sibling + /* Loop until we find previous sibling */ while (tokens[sibling].next_sibling != JSMN_NEG) sibling = tokens[sibling].next_sibling; - // Set previous sibling's next_sibling to current token + /* Set previous sibling's next_sibling to current token */ tokens[sibling].next_sibling = parser->toknext - 1; } #endif @@ -217,7 +217,7 @@ static jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { - // If a PRIMITIVE wasn't expected + /* If a PRIMITIVE wasn't expected */ if (tokens != NULL && !(parser->expected & JSMN_PRIMITIVE)) { return JSMN_ERROR_INVAL; @@ -256,7 +256,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } type |= JSMN_PRI_LITERAL; } else { - // If there are no extra JSMN_PRI_* flags + /* If there are no extra JSMN_PRI_* flags */ if ((parser->expected & 0xFF00) == 0) { parser->expected = JSMN_PRIMITIVE | JSMN_PRI_MINUS | JSMN_PRI_NUMBER; } @@ -373,9 +373,9 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } #else # ifdef JSMN_PARENT_LINKS - // If either a PRIMITIVE or DELIMITER were expected and this PRIMITIVE is - // following a KEY/VALUE pair, a comma didn't separate the last element - // and this element so parser->toksuper needs to be fixed. + /* If either a PRIMITIVE or DELIMITER were expected and this PRIMITIVE is * + * following a KEY/VALUE pair, a comma didn't separate the last element * + * and this element so parser->toksuper needs to be fixed. */ if (tokens != NULL && parser->toknext >= 2 && (parser->expected & JSMN_DELIMITER) && (tokens[parser->toknext - 2].type & JSMN_KEY)) { @@ -394,7 +394,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, case ']': case ':': goto found; - default: // to quiet a warning from gcc + default: /* to quiet a warning from gcc */ break; } if (js[parser->pos] < 32 || js[parser->pos] >= 127) { @@ -462,7 +462,7 @@ static jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { - // If a STRING wasn't expected + /* If a STRING wasn't expected */ if (tokens != NULL && !(parser->expected & JSMN_STRING)) { return JSMN_ERROR_INVAL; @@ -470,9 +470,9 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, #ifdef JSMN_PERMISSIVE # ifdef JSMN_PARENT_LINKS - // If both a PRIMITIVE and DELIMITER were expected and this STRING is - // following a KEY/VALUE pair, a comma didn't separate the last element - // and this element so parser->toksuper needs to be fixed. + /* If both a PRIMITIVE and DELIMITER were expected and this STRING is * + * following a KEY/VALUE pair, a comma didn't separate the last element * + * and this element so parser->toksuper needs to be fixed. */ if (tokens != NULL && parser->toknext >= 2 && (parser->expected & JSMN_DELIMITER) && (tokens[parser->toknext - 2].type & JSMN_KEY)) { @@ -488,11 +488,11 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, parser->pos++; - // Skip starting quote + /* Skip starting quote */ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { c = js[parser->pos]; - // Quote: end of string + /* Quote: end of string */ if (c == '\"') { if (tokens == NULL) { return JSMN_SUCCESS; @@ -504,7 +504,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, } jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); #ifndef JSMN_PERMISSIVE - // If the parent type is an OBJECT and the previous token is an OBJECT or VALUE + /* If the parent type is an OBJECT and the previous token is an OBJECT or VALUE */ if ((tokens[parser->toksuper].type & JSMN_OBJECT) && (tokens[parser->toknext - 2].type & (JSMN_OBJECT | JSMN_VALUE))) { token->type |= JSMN_KEY; @@ -514,7 +514,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, parser->expected = JSMN_DELIMITER | JSMN_CLOSE; } #else - // If the previous token is an KEY + /* If the previous token is an KEY */ if (parser->toknext >= 2 && (tokens[parser->toknext - 2].type & JSMN_KEY)) { token->type |= JSMN_VALUE; @@ -530,11 +530,11 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_SUCCESS; } - // Backslash: Quoted symbol expected + /* Backslash: Quoted symbol expected */ if (c == '\\' && parser->pos + 1 < len) { parser->pos++; switch (js[parser->pos]) { - // Allowed escaped symbols + /* Allowed escaped symbols */ case '\"': case '\\': case '/': @@ -544,14 +544,14 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, case 'r': case 't': break; - // Allows escaped symbol \uXXXX + /* Allows escaped symbol \uXXXX */ case 'u': parser->pos++; for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { - // If it isn't a hex character we have an error - if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || // 0-9 - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || // A-F - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { // a-f + /* If it isn't a hex character we have an error */ + if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ parser->pos = start; return JSMN_ERROR_INVAL; } @@ -559,7 +559,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, } parser->pos--; break; - // Unexpected symbol + /* Unexpected symbol */ default: parser->pos = start; return JSMN_ERROR_INVAL; @@ -598,7 +598,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, return JSMN_ERROR_NOMEM; } token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - // If an OBJECT or ARRAY (respectively) wasn't expected + /* If an OBJECT or ARRAY (respectively) wasn't expected */ if (!(parser->expected & token->type)) { return JSMN_ERROR_INVAL; } @@ -668,7 +668,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; } } - // Error if unmatched closing bracket + /* Error if unmatched closing bracket */ if (i == JSMN_NEG) { return JSMN_ERROR_INVAL; } @@ -707,15 +707,15 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, case '\r': break; case ':': - // If a DELIMITER wasn't expected + /* If a DELIMITER wasn't expected */ if (!(parser->expected & JSMN_DELIMITER)) { return JSMN_ERROR_INVAL; } #ifndef JSMN_PERMISSIVE if (tokens != NULL && - // Only simple single allowed + /* Only simple single allowed */ (parser->toksuper == JSMN_NEG || - // If the previous token wasn't a KEY + /* If the previous token wasn't a KEY */ !(tokens[parser->toknext - 1].type & JSMN_KEY))) { return JSMN_ERROR_INVAL; } @@ -727,24 +727,24 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; case ',': if (tokens != NULL && parser->toksuper != JSMN_NEG) { - // If a DELIMITER wasn't expected + /* If a DELIMITER wasn't expected */ if (!(parser->expected & JSMN_DELIMITER)) { return JSMN_ERROR_INVAL; } #ifndef JSMN_PERMISSIVE - // If the previous token was a KEY + /* If the previous token was a KEY */ if (tokens[parser->toknext - 1].type & JSMN_KEY) { return JSMN_ERROR_INVAL; } - // If this is in an OBJECT, a STRING KEY must follow a comma + /* If this is in an OBJECT, a STRING KEY must follow a comma */ if (tokens[parser->toksuper].type & JSMN_OBJECT) { parser->expected = JSMN_STRING; - // else this is in an ARRAY which allows ANY_TYPE to follow + /* else this is in an ARRAY which allows ANY_TYPE to follow */ } else { parser->expected = JSMN_ANY_TYPE; } #else - // The previous token + /* The previous token */ tokens[parser->toknext - 1].type |= JSMN_VALUE; parser->expected = JSMN_ANY_TYPE; #endif @@ -765,7 +765,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, } break; #ifndef JSMN_PERMISSIVE - // rfc8259: PRIMITIVEs are numbers and booleans + /* rfc8259: PRIMITIVEs are numbers and booleans */ case '-': case '0': case '1': @@ -781,7 +781,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, case 'f': case 'n': #else - // In permissive mode every unquoted value is a PRIMITIVE + /* In permissive mode every unquoted value is a PRIMITIVE */ default: #endif r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); @@ -795,7 +795,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, break; #ifndef JSMN_PERMISSIVE - // Unexpected char + /* Unexpected char */ default: return JSMN_ERROR_INVAL; #endif @@ -804,7 +804,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, if (tokens != NULL) { for (i = parser->toknext - 1; i != JSMN_NEG; i--) { - // Unmatched opened OBJECT or ARRAY + /* Unmatched opened OBJECT or ARRAY */ if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { return JSMN_ERROR_PART; } @@ -830,10 +830,10 @@ void jsmn_init(jsmn_parser *parser) { #endif } -#endif // JSMN_HEADER +#endif /* JSMN_HEADER */ #ifdef __cplusplus } #endif -#endif // JSMN_H +#endif /* JSMN_H */ From 53e8d57f99f6f69db1b5955e521045ff9c73c366 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Sun, 7 Jun 2020 03:25:33 -0500 Subject: [PATCH 11/20] Stronger parsing validation, break jsmn_parse apart --- jsmn.h | 725 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 412 insertions(+), 313 deletions(-) diff --git a/jsmn.h b/jsmn.h index a0c2ea71..752fcfa9 100644 --- a/jsmn.h +++ b/jsmn.h @@ -41,52 +41,86 @@ extern "C" { /** * JSON type identifier. Basic types are: */ -typedef enum { - JSMN_UNDEFINED = 0x00, - JSMN_OBJECT = 0x01, //!< Object - JSMN_ARRAY = 0x02, //!< Array - JSMN_STRING = 0x04, //!< String - JSMN_PRIMITIVE = 0x08, //!< Other primitive: number, boolean (true/false) or null - - JSMN_KEY = 0x10, //!< is a key - JSMN_VALUE = 0x20, //!< is a value - - JSMN_CLOSE = 0x40, //!< Close OBJECT '}' or ARRAY ']' - JSMN_DELIMITER = 0x80, //!< Colon ':' after KEY, Comma ',' after VALUE - - /* Combined elements */ - JSMN_CONTAINER = JSMN_OBJECT | JSMN_ARRAY, - JSMN_ANY_TYPE = JSMN_OBJECT | JSMN_ARRAY | JSMN_STRING | JSMN_PRIMITIVE, - - JSMN_OBJ_VAL = JSMN_OBJECT | JSMN_VALUE, - JSMN_ARR_VAL = JSMN_ARRAY | JSMN_VALUE, - JSMN_STR_KEY = JSMN_STRING | JSMN_KEY, - JSMN_STR_VAL = JSMN_STRING | JSMN_VALUE, - JSMN_PRI_VAL = JSMN_PRIMITIVE | JSMN_VALUE, +typedef enum __attribute__((packed)) { + JSMN_UNDEFINED = 0x0000, + JSMN_OBJECT = 0x0001, //!< Object + JSMN_ARRAY = 0x0002, //!< Array + JSMN_STRING = 0x0004, //!< String + JSMN_PRIMITIVE = 0x0008, //!< Other primitive: number, boolean (true/false) or null + + JSMN_KEY = 0x0010, //!< is a key + JSMN_VALUE = 0x0020, //!< is a value + + /* Complex elements */ + JSMN_CONTAINER = JSMN_OBJECT | JSMN_ARRAY, + JSMN_ANY_TYPE = JSMN_OBJECT | JSMN_ARRAY | JSMN_STRING | JSMN_PRIMITIVE, + + JSMN_OBJ_VAL = JSMN_OBJECT | JSMN_VALUE, + JSMN_ARR_VAL = JSMN_ARRAY | JSMN_VALUE, + JSMN_STR_KEY = JSMN_STRING | JSMN_KEY, + JSMN_STR_VAL = JSMN_STRING | JSMN_VALUE, + JSMN_PRI_VAL = JSMN_PRIMITIVE | JSMN_VALUE, #ifdef JSMN_PERMISSIVE - JSMN_OBJ_KEY = JSMN_OBJECT | JSMN_KEY, - JSMN_ARR_KEY = JSMN_ARRAY | JSMN_KEY, - JSMN_PRI_KEY = JSMN_PRIMITIVE | JSMN_KEY, + JSMN_OBJ_KEY = JSMN_OBJECT | JSMN_KEY, + JSMN_ARR_KEY = JSMN_ARRAY | JSMN_KEY, + JSMN_PRI_KEY = JSMN_PRIMITIVE | JSMN_KEY, #endif - JSMN_PRI_LITERAL = 0x0100, //!< true, false, null + /* Primitive extension */ + JSMN_PRI_LITERAL = 0x0040, //!< true, false, null + JSMN_PRI_INTEGER = 0x0080, //!< 0, 1 - 9 + JSMN_PRI_SIGN = 0x0100, //!< minus sign, '-' or plus sign, '+' + JSMN_PRI_DECIMAL = 0x0200, //!< deminal point '.' + JSMN_PRI_EXPONENT = 0x0400, //!< exponent, 'e' or 'E' - JSMN_PRI_ZERO = 0x0400, //!< 0 - JSMN_PRI_DIGIT = 0x0800, //!< 1 - 9 - JSMN_PRI_MINUS = 0x1000, //!< minus sign, '-' - JSMN_PRI_PLUS = 0x2000, //!< plus sign, '+' - JSMN_PRI_DECIMAL = 0x4000, //!< deminal point '.' - JSMN_PRI_EXPONENT = 0x8000, //!< exponent, 'e' or 'E' + JSMN_PRI_MINUS = JSMN_PRI_SIGN, - JSMN_PRI_NUMBER = JSMN_PRI_ZERO | JSMN_PRI_DIGIT, - JSMN_PRI_SIGN = JSMN_PRI_PLUS | JSMN_PRI_MINUS, + /* Parsing validation, expectations, and state information */ + JSMN_CLOSE = 0x0800, //!< Close OBJECT '}' or ARRAY ']' + JSMN_DELIMITER = 0x1000, //!< Colon ':' after KEY, Comma ',' after VALUE + JSMN_PREV_KEY = 0x2000, //!< Previous token is an KEY + JSMN_PREV_VAL = 0x4000, //!< Previous token is a VALUE + JSMN_INSD_OBJ = 0x8000, //!< Inside an OBJECT + + JSMN_COLON = JSMN_DELIMITER | JSMN_PREV_KEY, + JSMN_COMMA = JSMN_DELIMITER | JSMN_PREV_VAL, + + /* Parsing rules */ + JSMN_ROOT_INIT = JSMN_ANY_TYPE | JSMN_VALUE, +#ifndef JSMN_PERMISSIVE + JSMN_ROOT = JSMN_ANY_TYPE | JSMN_VALUE, + JSMN_OPEN_OBJECT = JSMN_STRING | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, + JSMN_AFTR_OBJ_KEY = JSMN_VALUE | JSMN_INSD_OBJ | JSMN_COLON, + JSMN_AFTR_OBJ_VAL = JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, + JSMN_OPEN_ARRAY = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_CLOSE, + JSMN_AFTR_ARR_VAL = JSMN_VALUE | JSMN_CLOSE | JSMN_COMMA, + JSMN_AFTR_CLOSE = JSMN_CLOSE | JSMN_COMMA, + JSMN_AFTR_COLON = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_INSD_OBJ | JSMN_PREV_KEY, + JSMN_AFTR_COMMA_O = JSMN_STRING | JSMN_KEY | JSMN_INSD_OBJ | JSMN_PREV_VAL, + JSMN_AFTR_COMMA_A = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_PREV_VAL, +#else + JSMN_ROOT = JSMN_ANY_TYPE | JSMN_COLON | JSMN_COMMA, + JSMN_ROOT_AFTR_O = JSMN_ANY_TYPE | JSMN_COMMA, + JSMN_OPEN_OBJECT = JSMN_ANY_TYPE | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, + JSMN_AFTR_OBJ_KEY = JSMN_VALUE | JSMN_INSD_OBJ | JSMN_COLON, + JSMN_AFTR_OBJ_VAL = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, + JSMN_OPEN_ARRAY = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_CLOSE, + JSMN_AFTR_ARR_VAL = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_COLON | JSMN_COMMA, + JSMN_AFTR_CLOSE = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_COMMA, + JSMN_AFTR_COLON = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_INSD_OBJ | JSMN_PREV_KEY, + JSMN_AFTR_COLON_R = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_PREV_KEY, + JSMN_AFTR_COMMA_O = JSMN_ANY_TYPE | JSMN_KEY | JSMN_INSD_OBJ | JSMN_PREV_VAL, + JSMN_AFTR_COMMA_A = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_PREV_VAL, + JSMN_AFTR_COMMA_R = JSMN_ANY_TYPE | JSMN_PREV_VAL, +#endif } jsmntype_t; enum jsmnerr { JSMN_SUCCESS = 0, - JSMN_ERROR_NOMEM = -1, //!< Not enough tokens were provided - JSMN_ERROR_INVAL = -2, //!< Invalid character inside JSON string - JSMN_ERROR_PART = -3, //!< The string is not a full JSON packet, more bytes expected + JSMN_ERROR_NOMEM = -1, //!< Not enough tokens were provided + JSMN_ERROR_INVAL = -2, //!< Invalid character inside JSON string + JSMN_ERROR_PART = -3, //!< The string is not a full JSON packet, more bytes expected + JSMN_ERROR_LEN = -4, //!< Input data too long }; typedef unsigned int jsmnint_t; @@ -96,15 +130,15 @@ typedef unsigned int jsmnint_t; * JSON token description. */ typedef struct jsmntok_t { - jsmntype_t type; //!< type (object, array, string etc.) - jsmnint_t start; //!< start position in JSON data string - jsmnint_t end; //!< end position in JSON data string - jsmnint_t size; //!< number of children + jsmntype_t type; //!< type (object, array, string etc.) + jsmnint_t start; //!< start position in JSON data string + jsmnint_t end; //!< end position in JSON data string + jsmnint_t size; //!< number of children #ifdef JSMN_PARENT_LINKS - jsmnint_t parent; //!< parent id + jsmnint_t parent; //!< parent id #endif #ifdef JSMN_NEXT_SIBLING - jsmnint_t next_sibling; //!< next sibling id + jsmnint_t next_sibling; //!< next sibling id #endif } jsmntok_t; @@ -117,7 +151,9 @@ typedef struct jsmntok_t { typedef struct jsmn_parser { jsmnint_t pos; //!< offset in the JSON string jsmnint_t toknext; //!< next token to allocate + //!< when tokens == NULL, keeps track of container types to a depth of (sizeof(jsmnint_t) * 8) jsmnint_t toksuper; //!< superior token node, e.g. parent object or array + //!< when tokens == NULL, toksuper represents container depth jsmntype_t expected; //!< Expected jsmn type(s) } jsmn_parser; @@ -154,10 +190,11 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, const size_t num_tokens) { - jsmntok_t *tok; if (parser->toknext >= num_tokens) { return NULL; } + + jsmntok_t *tok; tok = &tokens[parser->toknext++]; tok->start = tok->end = JSMN_NEG; tok->size = 0; @@ -218,14 +255,13 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { /* If a PRIMITIVE wasn't expected */ - if (tokens != NULL && - !(parser->expected & JSMN_PRIMITIVE)) { + if (!(parser->expected & JSMN_PRIMITIVE)) { return JSMN_ERROR_INVAL; } - jsmntok_t *token; jsmnint_t start; jsmntype_t type; + jsmntype_t expected; start = parser->pos; type = JSMN_PRIMITIVE; @@ -256,26 +292,23 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } type |= JSMN_PRI_LITERAL; } else { - /* If there are no extra JSMN_PRI_* flags */ - if ((parser->expected & 0xFF00) == 0) { - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_MINUS | JSMN_PRI_NUMBER; - } + expected = JSMN_PRIMITIVE | JSMN_PRI_MINUS | JSMN_PRI_INTEGER; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { case '0': - if (!(parser->expected & JSMN_PRI_ZERO)) { + if (!(expected & JSMN_PRI_INTEGER)) { parser->pos = start; return JSMN_ERROR_INVAL; } if (type & JSMN_PRI_EXPONENT) { - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; } else if (type & JSMN_PRI_DECIMAL) { - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER |JSMN_PRI_EXPONENT; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT; } else if (start == parser->pos || (start + 1 == parser->pos && (type & JSMN_PRI_MINUS))) { - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + expected = JSMN_PRIMITIVE | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; } else { - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; } break; case '1': @@ -287,57 +320,57 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, case '7': case '8': case '9': - if (!(parser->expected & JSMN_PRI_DIGIT)) { + if (!(expected & JSMN_PRI_INTEGER)) { parser->pos = start; return JSMN_ERROR_INVAL; } if (type & JSMN_PRI_EXPONENT) { - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; } else if (type & JSMN_PRI_DECIMAL) { - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER | JSMN_PRI_EXPONENT; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT; } else { - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; } break; case '-': - if (!(parser->expected & JSMN_PRI_MINUS)) { + if (!(expected & JSMN_PRI_MINUS)) { parser->pos = start; return JSMN_ERROR_INVAL; } - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; if (start == parser->pos) { type |= JSMN_PRI_MINUS; } break; case '+': - if (!(parser->expected & JSMN_PRI_PLUS)) { + if (!(expected & JSMN_PRI_SIGN) && start + 2 < parser->pos) { parser->pos = start; return JSMN_ERROR_INVAL; } - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; break; case '.': - if (!(parser->expected & JSMN_PRI_DECIMAL)) { + if (!(expected & JSMN_PRI_DECIMAL)) { parser->pos = start; return JSMN_ERROR_INVAL; } type |= JSMN_PRI_DECIMAL; - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_NUMBER; + expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; break; case 'e': case 'E': - if (!(parser->expected & JSMN_PRI_EXPONENT)) { + if (!(expected & JSMN_PRI_EXPONENT)) { parser->pos = start; return JSMN_ERROR_INVAL; } type |= JSMN_PRI_EXPONENT; - parser->expected = JSMN_PRIMITIVE | JSMN_PRI_SIGN | JSMN_PRI_NUMBER; + expected = JSMN_PRIMITIVE | JSMN_PRI_SIGN | JSMN_PRI_INTEGER; break; default: if (parser->pos >= len || js[parser->pos] == '\0') { parser->pos = start; return JSMN_ERROR_PART; - } else if (start + 1 == parser->pos && (type & JSMN_PRI_MINUS)) { + } else if (start + 1 == parser->pos && type & JSMN_PRI_MINUS) { parser->pos = start; return JSMN_ERROR_INVAL; } @@ -372,17 +405,6 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_PART; } #else -# ifdef JSMN_PARENT_LINKS - /* If either a PRIMITIVE or DELIMITER were expected and this PRIMITIVE is * - * following a KEY/VALUE pair, a comma didn't separate the last element * - * and this element so parser->toksuper needs to be fixed. */ - if (tokens != NULL && parser->toknext >= 2 && - (parser->expected & JSMN_DELIMITER) && - (tokens[parser->toknext - 2].type & JSMN_KEY)) { - parser->toksuper = tokens[parser->toksuper].parent; - } -# endif - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { case ' ': @@ -405,53 +427,76 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, #endif found: + expected = parser->expected; + if (parser->toksuper != JSMN_NEG) { + // OBJECT KEY, strict query + if ((parser->expected & (JSMN_KEY | JSMN_INSD_OBJ)) == (JSMN_KEY | JSMN_INSD_OBJ)) { + parser->expected = JSMN_AFTR_OBJ_KEY; + type |= JSMN_KEY | JSMN_INSD_OBJ; + // OBJECT VALUE, VALUE is implicit + } else if (parser->expected & JSMN_INSD_OBJ) { + parser->expected = JSMN_AFTR_OBJ_VAL; + type |= JSMN_VALUE | JSMN_INSD_OBJ; +#ifdef JSMN_PERMISSIVE + // OBJECT VALUE at the ROOT level + } else if (parser->expected == JSMN_AFTR_COLON_R) { + parser->expected = JSMN_ROOT_AFTR_O; + type |= JSMN_VALUE; +#endif + // ARRAY VALUE, VALUE is implicit + } else { + parser->expected = JSMN_AFTR_ARR_VAL; + type |= JSMN_VALUE; + } + } else { + parser->expected = JSMN_ROOT; + type |= JSMN_VALUE; + } + if (tokens == NULL) { parser->pos--; return JSMN_SUCCESS; } + + jsmntok_t *token; token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { + parser->expected = expected; parser->pos = start; return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, type, start, parser->pos); -#ifndef JSMN_PERMISSIVE - token->type |= JSMN_VALUE; -#else - if (parser->toksuper != JSMN_NEG && - (tokens[parser->toksuper].type & JSMN_KEY)) { - token->type |= JSMN_VALUE; - } -#endif - parser->expected = JSMN_DELIMITER | JSMN_CLOSE; -#ifdef JSMN_PERMISSIVE -# ifdef JSMN_PARENT_LINKS - if (parser->toksuper != JSMN_NEG && - tokens[parser->toksuper].parent == JSMN_NEG) { - parser->expected |= JSMN_ANY_TYPE; - } -# else - if (parser->toksuper != JSMN_NEG) { - jsmnint_t i; - for (i = parser->toksuper; i != JSMN_NEG; i--) { - if (tokens[i].type & JSMN_CONTAINER) { - break; - } - if (i != 0) { - continue; - } - parser->expected |= JSMN_ANY_TYPE; - } - } -# endif -#endif #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif #ifdef JSMN_NEXT_SIBLING jsmn_next_sibling(parser, tokens); #endif + + if (parser->toksuper != JSMN_NEG) { + tokens[parser->toksuper].size++; + + if (!(tokens[parser->toksuper].type & JSMN_CONTAINER)) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + jsmnint_t i; + for (i = parser->toksuper; i != JSMN_NEG; i--) { + if (tokens[i].type & JSMN_CONTAINER && tokens[i].end == JSMN_NEG) { + parser->toksuper = i; + break; + } + } +# ifdef JSMN_PERMISSIVE + if (i == JSMN_NEG) { + parser->toksuper = i; + } +# endif +#endif + } + } parser->pos--; + return JSMN_SUCCESS; } @@ -463,70 +508,95 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { /* If a STRING wasn't expected */ - if (tokens != NULL && - !(parser->expected & JSMN_STRING)) { + if (!(parser->expected & JSMN_STRING)) { return JSMN_ERROR_INVAL; } -#ifdef JSMN_PERMISSIVE -# ifdef JSMN_PARENT_LINKS - /* If both a PRIMITIVE and DELIMITER were expected and this STRING is * - * following a KEY/VALUE pair, a comma didn't separate the last element * - * and this element so parser->toksuper needs to be fixed. */ - if (tokens != NULL && parser->toknext >= 2 && - (parser->expected & JSMN_DELIMITER) && - (tokens[parser->toknext - 2].type & JSMN_KEY)) { - parser->toksuper = tokens[parser->toksuper].parent; + if (len >= JSMN_NEG) { + return JSMN_ERROR_LEN; } -# endif -#endif - - jsmntok_t *token; - char c; - jsmnint_t i, start = parser->pos; + jsmnint_t start; + start = parser->pos; + /* Skip starting quote */ parser->pos++; - /* Skip starting quote */ + char c; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { c = js[parser->pos]; /* Quote: end of string */ if (c == '\"') { + jsmntype_t expected = parser->expected; + jsmntype_t type; + if (parser->toksuper != JSMN_NEG) { + // OBJECT KEY, strict query + if ((parser->expected & (JSMN_INSD_OBJ | JSMN_KEY)) == (JSMN_INSD_OBJ | JSMN_KEY)) { + parser->expected = JSMN_AFTR_OBJ_KEY; + type = JSMN_STRING | JSMN_KEY | JSMN_INSD_OBJ; + // OBJECT VALUE, VALUE is implicit + } else if (parser->expected & JSMN_INSD_OBJ) { + parser->expected = JSMN_AFTR_OBJ_VAL; + type = JSMN_STRING | JSMN_VALUE | JSMN_INSD_OBJ; +#ifdef JSMN_PERMISSIVE + // OBJECT VALUE at the ROOT level + } else if (parser->expected == JSMN_AFTR_COLON_R) { + parser->expected = JSMN_ROOT_AFTR_O; + type = JSMN_STRING | JSMN_VALUE; +#endif + // ARRAY VALUE, VALUE is implicit + } else { + parser->expected = JSMN_AFTR_ARR_VAL; + type = JSMN_STRING | JSMN_VALUE; + } + } else { + parser->expected = JSMN_ROOT; + type = JSMN_STRING | JSMN_VALUE; + } + if (tokens == NULL) { return JSMN_SUCCESS; } + + jsmntok_t *token; token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { + parser->expected = expected; parser->pos = start; return JSMN_ERROR_NOMEM; } - jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); -#ifndef JSMN_PERMISSIVE - /* If the parent type is an OBJECT and the previous token is an OBJECT or VALUE */ - if ((tokens[parser->toksuper].type & JSMN_OBJECT) && - (tokens[parser->toknext - 2].type & (JSMN_OBJECT | JSMN_VALUE))) { - token->type |= JSMN_KEY; - parser->expected = JSMN_DELIMITER; - } else { - token->type |= JSMN_VALUE; - parser->expected = JSMN_DELIMITER | JSMN_CLOSE; - } -#else - /* If the previous token is an KEY */ - if (parser->toknext >= 2 && - (tokens[parser->toknext - 2].type & JSMN_KEY)) { - token->type |= JSMN_VALUE; - } - parser->expected = JSMN_ANY_TYPE | JSMN_DELIMITER | JSMN_CLOSE; -#endif + jsmn_fill_token(token, type, start + 1, parser->pos); #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif #ifdef JSMN_NEXT_SIBLING jsmn_next_sibling(parser, tokens); #endif + + if (parser->toksuper != JSMN_NEG) { + tokens[parser->toksuper].size++; + + if (!(tokens[parser->toksuper].type & JSMN_CONTAINER)) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + jsmnint_t i; + for (i = parser->toksuper; i != JSMN_NEG; i--) { + if (tokens[i].type & JSMN_CONTAINER && tokens[i].end == JSMN_NEG) { + parser->toksuper = i; + break; + } + } +# ifdef JSMN_PERMISSIVE + if (i == JSMN_NEG) { + parser->toksuper = i; + } +# endif +#endif + } + } + return JSMN_SUCCESS; } @@ -547,6 +617,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, /* Allows escaped symbol \uXXXX */ case 'u': parser->pos++; + jsmnint_t i; for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { /* If it isn't a hex character we have an error */ if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ @@ -570,6 +641,181 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_PART; } +static +jsmnint_t jsmn_parse_container_open(jsmn_parser *parser, const char c, + jsmntok_t *tokens, const size_t num_tokens) { + /* If an OBJECT or ARRAY wasn't expected */ + if (!(parser->expected & JSMN_CONTAINER)) { + return JSMN_ERROR_INVAL; + } + + jsmntype_t type; + if (c == '{') { + parser->expected = JSMN_OPEN_OBJECT; + type = JSMN_OBJECT | JSMN_VALUE; + } else { + parser->expected = JSMN_OPEN_ARRAY; + type = JSMN_ARRAY | JSMN_VALUE; + } + + if (tokens == NULL) { + parser->toksuper++; + if (parser->toksuper < (sizeof(jsmnint_t) * 8) && + parser->expected & JSMN_INSD_OBJ) { + parser->toknext |= (1 << parser->toksuper); + } + return JSMN_SUCCESS; + } + + if (parser->toksuper != JSMN_NEG && + tokens[parser->toksuper].type & JSMN_INSD_OBJ) { + type |= JSMN_INSD_OBJ; + } + + jsmntok_t *token; + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, type, parser->pos, JSMN_NEG); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif +#ifdef JSMN_NEXT_SIBLING + jsmn_next_sibling(parser, tokens); +#endif + + if (parser->toksuper != JSMN_NEG) { + tokens[parser->toksuper].size++; + } + parser->toksuper = parser->toknext - 1; + + return JSMN_SUCCESS; +} + +static +jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, + jsmntok_t *tokens) { + /* If an OBJECT or ARRAY close wasn't expected */ + if (!(parser->expected & JSMN_CLOSE)) { + return JSMN_ERROR_INVAL; + } + + if (tokens == NULL) { + if (parser->toksuper < (sizeof(jsmnint_t) * 8)) { + parser->toknext &= ~(1 << parser->toksuper); + } + parser->toksuper--; + } else { + jsmntype_t type; + jsmntok_t *token; + + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); + token = &tokens[parser->toksuper]; + if (!(token->type & type) || token->end != JSMN_NEG) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; +#ifdef JSMN_PARENT_LINKS + if (token->type & JSMN_INSD_OBJ) { + parser->toksuper = tokens[token->parent].parent; + } else { + parser->toksuper = token->parent; + } +#else + jsmnint_t i; + for (i = parser->toksuper - 1; i != JSMN_NEG; i--) { + if (tokens[i].type & JSMN_CONTAINER && tokens[i].end == JSMN_NEG) { + parser->toksuper = i; + break; + } + } + if (i == JSMN_NEG) { + parser->toksuper = i; + } +#endif + } + + if (parser->toksuper != JSMN_NEG) { + parser->expected = JSMN_AFTR_CLOSE; + } else { + parser->expected = JSMN_ROOT; + } + + return JSMN_SUCCESS; +} + +static +jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) { + /* If a COLON wasn't expected, strict check because it is a complex enum */ + if (!((parser->expected & JSMN_COLON) == JSMN_COLON)) { + return JSMN_ERROR_INVAL; + } + + if (parser->toksuper != JSMN_NEG) { + parser->expected = JSMN_AFTR_COLON; +#ifdef JSMN_PERMISSIVE + } else { + parser->expected = JSMN_AFTR_COLON_R; +#endif + } + + if (tokens == NULL) { + return JSMN_SUCCESS; + } + +#ifdef JSMN_PERMISSIVE + tokens[parser->toknext - 1].type &= ~JSMN_VALUE; + tokens[parser->toknext - 1].type |= JSMN_KEY; +#endif + + parser->toksuper = parser->toknext - 1; + + return JSMN_SUCCESS; +} + +static +jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) { + /* If a COMMA wasn't expected, strict check because it is a complex enum */ + if (!((parser->expected & JSMN_COMMA) == JSMN_COMMA)) { + return JSMN_ERROR_INVAL; + } + + jsmntype_t type = JSMN_UNDEFINED; + if (tokens == NULL) { + if (parser->toksuper < (sizeof(jsmnint_t) * 8) && + parser->toknext & (1 << parser->toksuper)) { + type = JSMN_INSD_OBJ; + } + } else { + if (parser->toksuper != JSMN_NEG) { + type = tokens[parser->toksuper].type; + } + } + + if (parser->toksuper != JSMN_NEG) { + if (type & (JSMN_OBJECT | JSMN_INSD_OBJ)) { + parser->expected = JSMN_AFTR_COMMA_O; + } else { + parser->expected = JSMN_AFTR_COMMA_A; + } +#ifdef JSMN_PERMISSIVE + } else { + parser->expected = JSMN_AFTR_COMMA_R; +#endif + } + + if (tokens == NULL) { + return JSMN_SUCCESS; + } + +#ifdef JSMN_PERMISSIVE + tokens[parser->toknext - 1].type |= JSMN_VALUE; +#endif + + return JSMN_SUCCESS; +} + /** * Parse JSON string and fill tokens. */ @@ -579,116 +825,25 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, const size_t num_tokens) { jsmnint_t r; jsmnint_t i; - jsmntok_t *token; jsmnint_t count = parser->toknext; char c; - jsmntype_t type; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { c = js[parser->pos]; switch (c) { case '{': case '[': - count++; - if (tokens == NULL) { - break; - } - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - return JSMN_ERROR_NOMEM; - } - token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); - /* If an OBJECT or ARRAY (respectively) wasn't expected */ - if (!(parser->expected & token->type)) { - return JSMN_ERROR_INVAL; - } - - token->type |= JSMN_VALUE; -#ifndef JSMN_PERMISSIVE - if (token->type & JSMN_OBJECT) { - parser->expected = JSMN_STRING | JSMN_CLOSE; - } else { - parser->expected = JSMN_ANY_TYPE | JSMN_CLOSE; - } -#else - parser->expected = JSMN_ANY_TYPE | JSMN_CLOSE; -#endif - if (parser->toksuper != JSMN_NEG) { - tokens[parser->toksuper].size++; -#ifdef JSMN_PARENT_LINKS - token->parent = parser->toksuper; -#endif -#ifdef JSMN_NEXT_SIBLING - jsmn_next_sibling(parser, tokens); -#endif + r = jsmn_parse_container_open(parser, c, tokens, num_tokens); + if (r != JSMN_SUCCESS) { + return r; } - token->start = parser->pos; - parser->toksuper = parser->toknext - 1; + count++; break; case '}': case ']': - if (tokens == NULL) { - break; - } - if (!(parser->expected & JSMN_CLOSE)) { - return JSMN_ERROR_INVAL; - } - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PARENT_LINKS - if (parser->toknext < 1) { - return JSMN_ERROR_INVAL; - } - token = &tokens[parser->toknext - 1]; - for (;;) { - if (token->start != JSMN_NEG && token->end == JSMN_NEG) { - if (!(token->type & type)) { - return JSMN_ERROR_INVAL; - } - token->end = parser->pos + 1; - parser->toksuper = token->parent; - break; - } - if (token->parent == JSMN_NEG) { - if (!(token->type & type) || parser->toksuper == JSMN_NEG) { - return JSMN_ERROR_INVAL; - } - break; - } - token = &tokens[token->parent]; - } -#else - for (i = parser->toknext - 1; i != JSMN_NEG; i--) { - token = &tokens[i]; - if (token->start != JSMN_NEG && token->end == JSMN_NEG) { - if (!(token->type & type)) { - return JSMN_ERROR_INVAL; - } - parser->toksuper = JSMN_NEG; - token->end = parser->pos + 1; - break; - } - } - /* Error if unmatched closing bracket */ - if (i == JSMN_NEG) { - return JSMN_ERROR_INVAL; - } - for (; i != JSMN_NEG; i--) { - token = &tokens[i]; - if (token->start != JSMN_NEG && token->end == JSMN_NEG) { - parser->toksuper = i; - break; - } - } -#endif - if (parser->toksuper == JSMN_NEG) { -#ifndef JSMN_PERMISSIVE - parser->expected = JSMN_CONTAINER; -#else - tokens[parser->toknext - 1].type |= JSMN_VALUE; - parser->expected = JSMN_ANY_TYPE; -#endif - } else { - parser->expected = JSMN_DELIMITER | JSMN_CLOSE; + r = jsmn_parse_container_close(parser, c, tokens); + if (r != JSMN_SUCCESS) { + return r; } break; case '\"': @@ -697,73 +852,24 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, return r; } count++; - if (tokens != NULL && parser->toksuper != JSMN_NEG) { - tokens[parser->toksuper].size++; - } - break; - case ' ': - case '\t': - case '\n': - case '\r': break; case ':': - /* If a DELIMITER wasn't expected */ - if (!(parser->expected & JSMN_DELIMITER)) { - return JSMN_ERROR_INVAL; - } -#ifndef JSMN_PERMISSIVE - if (tokens != NULL && - /* Only simple single allowed */ - (parser->toksuper == JSMN_NEG || - /* If the previous token wasn't a KEY */ - !(tokens[parser->toknext - 1].type & JSMN_KEY))) { - return JSMN_ERROR_INVAL; + r = jsmn_parse_colon(parser, tokens); + if (r != JSMN_SUCCESS) { + return r; } -#else - tokens[parser->toknext - 1].type |= JSMN_KEY; -#endif - parser->expected = JSMN_ANY_TYPE; - parser->toksuper = parser->toknext - 1; break; case ',': - if (tokens != NULL && parser->toksuper != JSMN_NEG) { - /* If a DELIMITER wasn't expected */ - if (!(parser->expected & JSMN_DELIMITER)) { - return JSMN_ERROR_INVAL; - } -#ifndef JSMN_PERMISSIVE - /* If the previous token was a KEY */ - if (tokens[parser->toknext - 1].type & JSMN_KEY) { - return JSMN_ERROR_INVAL; - } - /* If this is in an OBJECT, a STRING KEY must follow a comma */ - if (tokens[parser->toksuper].type & JSMN_OBJECT) { - parser->expected = JSMN_STRING; - /* else this is in an ARRAY which allows ANY_TYPE to follow */ - } else { - parser->expected = JSMN_ANY_TYPE; - } -#else - /* The previous token */ - tokens[parser->toknext - 1].type |= JSMN_VALUE; - parser->expected = JSMN_ANY_TYPE; -#endif - if (!(tokens[parser->toksuper].type & JSMN_CONTAINER)) { -#ifdef JSMN_PARENT_LINKS - parser->toksuper = tokens[parser->toksuper].parent; -#else - for (i = parser->toknext - 1; i != JSMN_NEG; i--) { - if (tokens[i].type & JSMN_CONTAINER) { - if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { - parser->toksuper = i; - break; - } - } - } -#endif - } + r = jsmn_parse_comma(parser, tokens); + if (r != JSMN_SUCCESS) { + return r; } break; + case ' ': + case '\t': + case '\n': + case '\r': + break; #ifndef JSMN_PERMISSIVE /* rfc8259: PRIMITIVEs are numbers and booleans */ case '-': @@ -789,9 +895,6 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, return r; } count++; - if (tokens != NULL && parser->toksuper != JSMN_NEG) { - tokens[parser->toksuper].size++; - } break; #ifndef JSMN_PERMISSIVE @@ -823,11 +926,7 @@ void jsmn_init(jsmn_parser *parser) { parser->pos = 0; parser->toknext = 0; parser->toksuper = JSMN_NEG; -#ifndef JSMN_PERMISSIVE - parser->expected = JSMN_CONTAINER; -#else - parser->expected = JSMN_ANY_TYPE; -#endif + parser->expected = JSMN_ROOT_INIT; } #endif /* JSMN_HEADER */ From e213f2fd2716d92edadc6845f1aaaee6d2f3b4f0 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Sun, 7 Jun 2020 04:54:50 -0500 Subject: [PATCH 12/20] Validate closing containers even when `tokens == NULL`. --- jsmn.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jsmn.h b/jsmn.h index 752fcfa9..8db85cee 100644 --- a/jsmn.h +++ b/jsmn.h @@ -703,6 +703,12 @@ jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, if (tokens == NULL) { if (parser->toksuper < (sizeof(jsmnint_t) * 8)) { + jsmntype_t type; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); + if ((((parser->toknext & 1 << parser->toksuper) == 1) && !(type & JSMN_OBJECT)) || + (((parser->toknext & 1 << parser->toksuper) == 0) && !(type & JSMN_ARRAY))) { + return JSMN_ERROR_INVAL; + } parser->toknext &= ~(1 << parser->toksuper); } parser->toksuper--; From b5d5a905f60f7f9198be4d09a27c3856babdaf1e Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Sun, 7 Jun 2020 04:55:28 -0500 Subject: [PATCH 13/20] Pack jsmntype_t only when JSMN_SHORT_TOKENS is defined. --- jsmn.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jsmn.h b/jsmn.h index 8db85cee..d7e65dc2 100644 --- a/jsmn.h +++ b/jsmn.h @@ -38,10 +38,19 @@ extern "C" { # endif #endif +#ifdef JSMN_SHORT_TOKENS +typedef unsigned short jsmnint_t; +# define packed __attribute__((packed)) +#else +typedef unsigned int jsmnint_t; +# define packed +#endif +#define JSMN_NEG ((jsmnint_t)-1) + /** * JSON type identifier. Basic types are: */ -typedef enum __attribute__((packed)) { +typedef enum packed { JSMN_UNDEFINED = 0x0000, JSMN_OBJECT = 0x0001, //!< Object JSMN_ARRAY = 0x0002, //!< Array @@ -123,9 +132,6 @@ enum jsmnerr { JSMN_ERROR_LEN = -4, //!< Input data too long }; -typedef unsigned int jsmnint_t; -#define JSMN_NEG ((jsmnint_t)-1) - /** * JSON token description. */ From 51c8ce466fd98498289fbdbfdca13b71d5a1815f Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Sun, 7 Jun 2020 07:21:30 -0500 Subject: [PATCH 14/20] c89 comments again, removed loop that validation takes care of. --- Makefile | 2 +- jsmn.h | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index aa6da9d4..7170789b 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ clean: rm -f *.o example/*.o rm -f simple_example rm -f jsondump - rm -f test/test_default test/test_permissive test/test_links test/test_permissive_links + rm -f test/test_* .PHONY: clean test diff --git a/jsmn.h b/jsmn.h index d7e65dc2..cd3e6736 100644 --- a/jsmn.h +++ b/jsmn.h @@ -435,21 +435,21 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, found: expected = parser->expected; if (parser->toksuper != JSMN_NEG) { - // OBJECT KEY, strict query + /* OBJECT KEY, strict query */ if ((parser->expected & (JSMN_KEY | JSMN_INSD_OBJ)) == (JSMN_KEY | JSMN_INSD_OBJ)) { parser->expected = JSMN_AFTR_OBJ_KEY; type |= JSMN_KEY | JSMN_INSD_OBJ; - // OBJECT VALUE, VALUE is implicit + /* OBJECT VALUE, VALUE is implicit */ } else if (parser->expected & JSMN_INSD_OBJ) { parser->expected = JSMN_AFTR_OBJ_VAL; type |= JSMN_VALUE | JSMN_INSD_OBJ; #ifdef JSMN_PERMISSIVE - // OBJECT VALUE at the ROOT level + /* OBJECT VALUE at the ROOT level */ } else if (parser->expected == JSMN_AFTR_COLON_R) { parser->expected = JSMN_ROOT_AFTR_O; type |= JSMN_VALUE; #endif - // ARRAY VALUE, VALUE is implicit + /* ARRAY VALUE, VALUE is implicit */ } else { parser->expected = JSMN_AFTR_ARR_VAL; type |= JSMN_VALUE; @@ -537,21 +537,21 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, jsmntype_t expected = parser->expected; jsmntype_t type; if (parser->toksuper != JSMN_NEG) { - // OBJECT KEY, strict query + /* OBJECT KEY, strict query */ if ((parser->expected & (JSMN_INSD_OBJ | JSMN_KEY)) == (JSMN_INSD_OBJ | JSMN_KEY)) { parser->expected = JSMN_AFTR_OBJ_KEY; type = JSMN_STRING | JSMN_KEY | JSMN_INSD_OBJ; - // OBJECT VALUE, VALUE is implicit + /* OBJECT VALUE, VALUE is implicit */ } else if (parser->expected & JSMN_INSD_OBJ) { parser->expected = JSMN_AFTR_OBJ_VAL; type = JSMN_STRING | JSMN_VALUE | JSMN_INSD_OBJ; #ifdef JSMN_PERMISSIVE - // OBJECT VALUE at the ROOT level + /* OBJECT VALUE at the ROOT level */ } else if (parser->expected == JSMN_AFTR_COLON_R) { parser->expected = JSMN_ROOT_AFTR_O; type = JSMN_STRING | JSMN_VALUE; #endif - // ARRAY VALUE, VALUE is implicit + /* ARRAY VALUE, VALUE is implicit */ } else { parser->expected = JSMN_AFTR_ARR_VAL; type = JSMN_STRING | JSMN_VALUE; @@ -836,7 +836,6 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { jsmnint_t r; - jsmnint_t i; jsmnint_t count = parser->toknext; char c; @@ -917,13 +916,8 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, } } - if (tokens != NULL) { - for (i = parser->toknext - 1; i != JSMN_NEG; i--) { - /* Unmatched opened OBJECT or ARRAY */ - if (tokens[i].start != JSMN_NEG && tokens[i].end == JSMN_NEG) { - return JSMN_ERROR_PART; - } - } + if (parser->toksuper != JSMN_NEG) { + return JSMN_ERROR_PART; } return count; From 0f792d70a905a3244f633a15ea7b4879cd28fef4 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Wed, 10 Jun 2020 00:33:50 -0500 Subject: [PATCH 15/20] Adding 300+ validation checks from JSONTestSuite Updated jsmn.h to pass all available tests. Modified testutil.h to better support jsmnint_t. Remove a warning when compiling the tests because invalid characters are in the file for testing. --- Makefile | 2 +- jsmn.h | 77 ++- test/tests.c | 1454 +++++++++++++++++++++++++++++++++++++++++++++-- test/testutil.h | 32 +- 4 files changed, 1462 insertions(+), 103 deletions(-) diff --git a/Makefile b/Makefile index 7170789b..c9bf1474 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # You can put your build options here -include config.mk -CFLAGS:=${CFLAGS} -std=c89 +CFLAGS:=${CFLAGS} -std=c89 -Wno-invalid-source-encoding test: test_default test_permissive test_links test_permissive_links test_default: test/tests.c jsmn.h diff --git a/jsmn.h b/jsmn.h index cd3e6736..d6315d15 100644 --- a/jsmn.h +++ b/jsmn.h @@ -31,11 +31,11 @@ extern "C" { #endif #ifndef JSMN_API -# ifdef JSMN_STATIC -# define JSMN_API static -# else -# define JSMN_API extern -# endif +# ifdef JSMN_STATIC +# define JSMN_API static +# else +# define JSMN_API extern +# endif #endif #ifdef JSMN_SHORT_TOKENS @@ -97,7 +97,11 @@ typedef enum packed { /* Parsing rules */ JSMN_ROOT_INIT = JSMN_ANY_TYPE | JSMN_VALUE, #ifndef JSMN_PERMISSIVE +#ifndef JSMN_MULTIPLE_JSON + JSMN_ROOT = JSMN_UNDEFINED, +#else JSMN_ROOT = JSMN_ANY_TYPE | JSMN_VALUE, +#endif JSMN_OPEN_OBJECT = JSMN_STRING | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, JSMN_AFTR_OBJ_KEY = JSMN_VALUE | JSMN_INSD_OBJ | JSMN_COLON, JSMN_AFTR_OBJ_VAL = JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, @@ -288,7 +292,9 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } jsmnint_t i; for (i = 1, parser->pos++; i < size; i++, parser->pos++) { - if (parser->pos >= len || js[parser->pos] == '\0') { + if (parser->pos >= len || js[parser->pos] == '\0' || + (parser->toksuper != JSMN_NEG && js[parser->pos] == ',') || + js[parser->pos] == (parser->expected & JSMN_INSD_OBJ ? '}' : ']')) { parser->pos = start; return JSMN_ERROR_PART; } else if (js[parser->pos] != literal[i]) { @@ -298,7 +304,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } type |= JSMN_PRI_LITERAL; } else { - expected = JSMN_PRIMITIVE | JSMN_PRI_MINUS | JSMN_PRI_INTEGER; + expected = JSMN_PRI_MINUS | JSMN_PRI_INTEGER; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { switch (js[parser->pos]) { case '0': @@ -307,14 +313,14 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } if (type & JSMN_PRI_EXPONENT) { - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; + expected = JSMN_PRI_INTEGER | JSMN_CLOSE; } else if (type & JSMN_PRI_DECIMAL) { - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT; + expected = JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT | JSMN_CLOSE; } else if (start == parser->pos || (start + 1 == parser->pos && (type & JSMN_PRI_MINUS))) { - expected = JSMN_PRIMITIVE | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + expected = JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; } else { - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; } break; case '1': @@ -331,11 +337,11 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } if (type & JSMN_PRI_EXPONENT) { - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; + expected = JSMN_PRI_INTEGER | JSMN_CLOSE; } else if (type & JSMN_PRI_DECIMAL) { - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT; + expected = JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT | JSMN_CLOSE; } else { - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT; + expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; } break; case '-': @@ -343,17 +349,17 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, parser->pos = start; return JSMN_ERROR_INVAL; } - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; + expected = JSMN_PRI_INTEGER; if (start == parser->pos) { type |= JSMN_PRI_MINUS; } break; case '+': - if (!(expected & JSMN_PRI_SIGN) && start + 2 < parser->pos) { + if (!(expected & JSMN_PRI_SIGN)) { parser->pos = start; return JSMN_ERROR_INVAL; } - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; + expected = JSMN_PRI_INTEGER; break; case '.': if (!(expected & JSMN_PRI_DECIMAL)) { @@ -361,7 +367,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } type |= JSMN_PRI_DECIMAL; - expected = JSMN_PRIMITIVE | JSMN_PRI_INTEGER; + expected = JSMN_PRI_INTEGER; break; case 'e': case 'E': @@ -370,13 +376,10 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } type |= JSMN_PRI_EXPONENT; - expected = JSMN_PRIMITIVE | JSMN_PRI_SIGN | JSMN_PRI_INTEGER; + expected = JSMN_PRI_SIGN | JSMN_PRI_INTEGER; break; default: - if (parser->pos >= len || js[parser->pos] == '\0') { - parser->pos = start; - return JSMN_ERROR_PART; - } else if (start + 1 == parser->pos && type & JSMN_PRI_MINUS) { + if (!(expected & JSMN_CLOSE)) { parser->pos = start; return JSMN_ERROR_INVAL; } @@ -385,6 +388,10 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } } check_primitive_border: + if (parser->pos == len && js[parser->pos] != '\0') { + parser->pos = start; + return JSMN_ERROR_PART; + } switch (js[parser->pos]) { case ' ': case '\t': @@ -408,7 +415,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, goto found; default: parser->pos = start; - return JSMN_ERROR_PART; + return JSMN_ERROR_INVAL; } #else for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { @@ -642,6 +649,12 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } } + + /* Actual form feed (0x0C), new line (0x0A), carraige return (0x0D), or tab (0x09) not allowed */ + else if (c == '\f' || c == '\n' || c == '\r' || c == '\t') { + parser->pos = start; + return JSMN_ERROR_INVAL; + } } parser->pos = start; return JSMN_ERROR_PART; @@ -702,7 +715,7 @@ jsmnint_t jsmn_parse_container_open(jsmn_parser *parser, const char c, static jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, jsmntok_t *tokens) { - /* If an OBJECT or ARRAY close wasn't expected */ + /* If an OBJECT or ARRAY CLOSE wasn't expected */ if (!(parser->expected & JSMN_CLOSE)) { return JSMN_ERROR_INVAL; } @@ -730,7 +743,11 @@ jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, token->end = parser->pos + 1; #ifdef JSMN_PARENT_LINKS if (token->type & JSMN_INSD_OBJ) { - parser->toksuper = tokens[token->parent].parent; + if (tokens[token->parent].type & JSMN_CONTAINER) { + parser->toksuper = token->parent; + } else { + parser->toksuper = tokens[token->parent].parent; + } } else { parser->toksuper = token->parent; } @@ -759,7 +776,7 @@ jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, static jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) { - /* If a COLON wasn't expected, strict check because it is a complex enum */ + /* If a COLON wasn't expected; strict check because it is a complex enum */ if (!((parser->expected & JSMN_COLON) == JSMN_COLON)) { return JSMN_ERROR_INVAL; } @@ -788,7 +805,7 @@ jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) { static jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) { - /* If a COMMA wasn't expected, strict check because it is a complex enum */ + /* If a COMMA wasn't expected; strict check because it is a complex enum */ if (!((parser->expected & JSMN_COMMA) == JSMN_COMMA)) { return JSMN_ERROR_INVAL; } @@ -876,6 +893,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, return r; } break; + /* Valid whitespace */ case ' ': case '\t': case '\n': @@ -920,6 +938,9 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, return JSMN_ERROR_PART; } + if (count == 0) { + return JSMN_ERROR_INVAL; + } return count; } diff --git a/test/tests.c b/test/tests.c index a7ea19e3..7fdb0e1a 100644 --- a/test/tests.c +++ b/test/tests.c @@ -6,6 +6,1328 @@ #include "test.h" #include "testutil.h" +int test_jsmn_test_suite_i_(void) { + /* i_number_double_huge_neg_exp.json */ + check(parse("[123.456e-789]", 2, 2, + JSMN_ARRAY, 0, 14, 1, + JSMN_PRIMITIVE, "123.456e-789")); + + /* i_number_huge_exp.json */ + check(parse("[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]", 2, 2, + JSMN_ARRAY, 0, 137, 1, + JSMN_PRIMITIVE, "0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006")); + + /* i_number_neg_int_huge_exp.json */ + check(parse("[-1e+9999]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_PRIMITIVE, "-1e+9999")); + + /* i_number_pos_double_huge_exp.json */ + check(parse("[1.5e+9999]", 2, 2, + JSMN_ARRAY, 0, 11, 1, + JSMN_PRIMITIVE, "1.5e+9999")); + + /* i_number_real_neg_overflow.json */ + check(parse("[-123123e100000]", 2, 2, + JSMN_ARRAY, 0, 16, 1, + JSMN_PRIMITIVE, "-123123e100000")); + + /* i_number_real_pos_overflow.json */ + check(parse("[123123e100000]", 2, 2, + JSMN_ARRAY, 0, 15, 1, + JSMN_PRIMITIVE, "123123e100000")); + + /* i_number_real_underflow.json */ + check(parse("[123e-10000000]", 2, 2, + JSMN_ARRAY, 0, 15, 1, + JSMN_PRIMITIVE, "123e-10000000")); + + /* i_number_too_big_neg_int.json */ + check(parse("[-123123123123123123123123123123]", 2, 2, + JSMN_ARRAY, 0, 33, 1, + JSMN_PRIMITIVE, "-123123123123123123123123123123")); + + /* i_number_too_big_pos_int.json */ + check(parse("[100000000000000000000]", 2, 2, + JSMN_ARRAY, 0, 23, 1, + JSMN_PRIMITIVE, "100000000000000000000")); + + /* i_number_very_big_negative_int.json */ + check(parse("[-237462374673276894279832749832423479823246327846]", 2, 2, + JSMN_ARRAY, 0, 51, 1, + JSMN_PRIMITIVE, "-237462374673276894279832749832423479823246327846")); + + /* i_object_key_lone_2nd_surrogate.json */ + check(parse("{\"\\uDFAA\":0}", 3, 3, + JSMN_OBJECT, 0, 12, 1, + JSMN_STRING, "\\uDFAA", 1, + JSMN_PRIMITIVE, "0")); + + /* i_string_1st_surrogate_but_2nd_missing.json */ + check(parse("[\"\\uDADA\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\uDADA", 0)); + + /* i_string_1st_valid_surrogate_2nd_invalid.json */ + check(parse("[\"\\uD888\\u1234\"]", 2, 2, + JSMN_ARRAY, 0, 16, 1, + JSMN_STRING, "\\uD888\\u1234", 0)); + + /* i_string_incomplete_surrogate_and_escape_valid.json */ + check(parse("[\"\\uD800\\n\"]", 2, 2, + JSMN_ARRAY, 0, 12, 1, + JSMN_STRING, "\\uD800\\n", 0)); + + /* i_string_incomplete_surrogate_pair.json */ + check(parse("[\"\\uDd1ea\"]", 2, 2, + JSMN_ARRAY, 0, 11, 1, + JSMN_STRING, "\\uDd1ea", 0)); + + /* i_string_incomplete_surrogates_escape_valid.json */ + check(parse("[\"\\uD800\\uD800\\n\"]", 2, 2, + JSMN_ARRAY, 0, 18, 1, + JSMN_STRING, "\\uD800\\uD800\\n", 0)); + + /* i_string_invalid_lonely_surrogate.json */ + check(parse("[\"\\ud800\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\ud800", 0)); + + /* i_string_invalid_surrogate.json */ + check(parse("[\"\\ud800abc\"]", 2, 2, + JSMN_ARRAY, 0, 13, 1, + JSMN_STRING, "\\ud800abc", 0)); + + /* i_string_invalid_utf-8.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 5, 1, + JSMN_STRING, "", 0)); + + /* i_string_inverted_surrogates_U+1D11E.json */ + check(parse("[\"\\uDd1e\\uD834\"]", 2, 2, + JSMN_ARRAY, 0, 16, 1, + JSMN_STRING, "\\uDd1e\\uD834", 0)); + + /* i_string_iso_latin_1.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 5, 1, + JSMN_STRING, "", 0)); + + /* i_string_lone_second_surrogate.json */ + check(parse("[\"\\uDFAA\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\uDFAA", 0)); + + /* i_string_lone_utf8_continuation_byte.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 5, 1, + JSMN_STRING, "", 0)); + + /* i_string_not_in_unicode_range.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 8, 1, + JSMN_STRING, "", 0)); + + /* i_string_overlong_sequence_2_bytes.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_STRING, "", 0)); + + /* i_string_overlong_sequence_6_bytes.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "", 0)); + + /* i_string_overlong_sequence_6_bytes_null.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "", 0)); + + /* i_string_truncated-utf-8.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_STRING, "", 0)); + + /* i_string_utf16BE_no_BOM.json failed to parse. */ + + /* i_string_utf16LE_no_BOM.json failed to parse. */ + + /* i_string_UTF-16LE_with_BOM.json failed to parse. */ + + /* i_string_UTF-8_invalid_sequence.json */ + check(parse("[\"日ш\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "日ш", 0)); + + /* i_string_UTF8_surrogate_U+D800.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_STRING, "", 0)); + + /* i_structure_500_nested_arrays.json */ + check(parse("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", 500, 500, + JSMN_ARRAY, 0,1000, 1, JSMN_ARRAY, 1, 999, 1, JSMN_ARRAY, 2, 998, 1, JSMN_ARRAY, 3, 997, 1, JSMN_ARRAY, 4, 996, 1, + JSMN_ARRAY, 5, 995, 1, JSMN_ARRAY, 6, 994, 1, JSMN_ARRAY, 7, 993, 1, JSMN_ARRAY, 8, 992, 1, JSMN_ARRAY, 9, 991, 1, + JSMN_ARRAY, 10, 990, 1, JSMN_ARRAY, 11, 989, 1, JSMN_ARRAY, 12, 988, 1, JSMN_ARRAY, 13, 987, 1, JSMN_ARRAY, 14, 986, 1, + JSMN_ARRAY, 15, 985, 1, JSMN_ARRAY, 16, 984, 1, JSMN_ARRAY, 17, 983, 1, JSMN_ARRAY, 18, 982, 1, JSMN_ARRAY, 19, 981, 1, + JSMN_ARRAY, 20, 980, 1, JSMN_ARRAY, 21, 979, 1, JSMN_ARRAY, 22, 978, 1, JSMN_ARRAY, 23, 977, 1, JSMN_ARRAY, 24, 976, 1, + JSMN_ARRAY, 25, 975, 1, JSMN_ARRAY, 26, 974, 1, JSMN_ARRAY, 27, 973, 1, JSMN_ARRAY, 28, 972, 1, JSMN_ARRAY, 29, 971, 1, + JSMN_ARRAY, 30, 970, 1, JSMN_ARRAY, 31, 969, 1, JSMN_ARRAY, 32, 968, 1, JSMN_ARRAY, 33, 967, 1, JSMN_ARRAY, 34, 966, 1, + JSMN_ARRAY, 35, 965, 1, JSMN_ARRAY, 36, 964, 1, JSMN_ARRAY, 37, 963, 1, JSMN_ARRAY, 38, 962, 1, JSMN_ARRAY, 39, 961, 1, + JSMN_ARRAY, 40, 960, 1, JSMN_ARRAY, 41, 959, 1, JSMN_ARRAY, 42, 958, 1, JSMN_ARRAY, 43, 957, 1, JSMN_ARRAY, 44, 956, 1, + JSMN_ARRAY, 45, 955, 1, JSMN_ARRAY, 46, 954, 1, JSMN_ARRAY, 47, 953, 1, JSMN_ARRAY, 48, 952, 1, JSMN_ARRAY, 49, 951, 1, + JSMN_ARRAY, 50, 950, 1, JSMN_ARRAY, 51, 949, 1, JSMN_ARRAY, 52, 948, 1, JSMN_ARRAY, 53, 947, 1, JSMN_ARRAY, 54, 946, 1, + JSMN_ARRAY, 55, 945, 1, JSMN_ARRAY, 56, 944, 1, JSMN_ARRAY, 57, 943, 1, JSMN_ARRAY, 58, 942, 1, JSMN_ARRAY, 59, 941, 1, + JSMN_ARRAY, 60, 940, 1, JSMN_ARRAY, 61, 939, 1, JSMN_ARRAY, 62, 938, 1, JSMN_ARRAY, 63, 937, 1, JSMN_ARRAY, 64, 936, 1, + JSMN_ARRAY, 65, 935, 1, JSMN_ARRAY, 66, 934, 1, JSMN_ARRAY, 67, 933, 1, JSMN_ARRAY, 68, 932, 1, JSMN_ARRAY, 69, 931, 1, + JSMN_ARRAY, 70, 930, 1, JSMN_ARRAY, 71, 929, 1, JSMN_ARRAY, 72, 928, 1, JSMN_ARRAY, 73, 927, 1, JSMN_ARRAY, 74, 926, 1, + JSMN_ARRAY, 75, 925, 1, JSMN_ARRAY, 76, 924, 1, JSMN_ARRAY, 77, 923, 1, JSMN_ARRAY, 78, 922, 1, JSMN_ARRAY, 79, 921, 1, + JSMN_ARRAY, 80, 920, 1, JSMN_ARRAY, 81, 919, 1, JSMN_ARRAY, 82, 918, 1, JSMN_ARRAY, 83, 917, 1, JSMN_ARRAY, 84, 916, 1, + JSMN_ARRAY, 85, 915, 1, JSMN_ARRAY, 86, 914, 1, JSMN_ARRAY, 87, 913, 1, JSMN_ARRAY, 88, 912, 1, JSMN_ARRAY, 89, 911, 1, + JSMN_ARRAY, 90, 910, 1, JSMN_ARRAY, 91, 909, 1, JSMN_ARRAY, 92, 908, 1, JSMN_ARRAY, 93, 907, 1, JSMN_ARRAY, 94, 906, 1, + JSMN_ARRAY, 95, 905, 1, JSMN_ARRAY, 96, 904, 1, JSMN_ARRAY, 97, 903, 1, JSMN_ARRAY, 98, 902, 1, JSMN_ARRAY, 99, 901, 1, + JSMN_ARRAY, 100, 900, 1, JSMN_ARRAY, 101, 899, 1, JSMN_ARRAY, 102, 898, 1, JSMN_ARRAY, 103, 897, 1, JSMN_ARRAY, 104, 896, 1, + JSMN_ARRAY, 105, 895, 1, JSMN_ARRAY, 106, 894, 1, JSMN_ARRAY, 107, 893, 1, JSMN_ARRAY, 108, 892, 1, JSMN_ARRAY, 109, 891, 1, + JSMN_ARRAY, 110, 890, 1, JSMN_ARRAY, 111, 889, 1, JSMN_ARRAY, 112, 888, 1, JSMN_ARRAY, 113, 887, 1, JSMN_ARRAY, 114, 886, 1, + JSMN_ARRAY, 115, 885, 1, JSMN_ARRAY, 116, 884, 1, JSMN_ARRAY, 117, 883, 1, JSMN_ARRAY, 118, 882, 1, JSMN_ARRAY, 119, 881, 1, + JSMN_ARRAY, 120, 880, 1, JSMN_ARRAY, 121, 879, 1, JSMN_ARRAY, 122, 878, 1, JSMN_ARRAY, 123, 877, 1, JSMN_ARRAY, 124, 876, 1, + JSMN_ARRAY, 125, 875, 1, JSMN_ARRAY, 126, 874, 1, JSMN_ARRAY, 127, 873, 1, JSMN_ARRAY, 128, 872, 1, JSMN_ARRAY, 129, 871, 1, + JSMN_ARRAY, 130, 870, 1, JSMN_ARRAY, 131, 869, 1, JSMN_ARRAY, 132, 868, 1, JSMN_ARRAY, 133, 867, 1, JSMN_ARRAY, 134, 866, 1, + JSMN_ARRAY, 135, 865, 1, JSMN_ARRAY, 136, 864, 1, JSMN_ARRAY, 137, 863, 1, JSMN_ARRAY, 138, 862, 1, JSMN_ARRAY, 139, 861, 1, + JSMN_ARRAY, 140, 860, 1, JSMN_ARRAY, 141, 859, 1, JSMN_ARRAY, 142, 858, 1, JSMN_ARRAY, 143, 857, 1, JSMN_ARRAY, 144, 856, 1, + JSMN_ARRAY, 145, 855, 1, JSMN_ARRAY, 146, 854, 1, JSMN_ARRAY, 147, 853, 1, JSMN_ARRAY, 148, 852, 1, JSMN_ARRAY, 149, 851, 1, + JSMN_ARRAY, 150, 850, 1, JSMN_ARRAY, 151, 849, 1, JSMN_ARRAY, 152, 848, 1, JSMN_ARRAY, 153, 847, 1, JSMN_ARRAY, 154, 846, 1, + JSMN_ARRAY, 155, 845, 1, JSMN_ARRAY, 156, 844, 1, JSMN_ARRAY, 157, 843, 1, JSMN_ARRAY, 158, 842, 1, JSMN_ARRAY, 159, 841, 1, + JSMN_ARRAY, 160, 840, 1, JSMN_ARRAY, 161, 839, 1, JSMN_ARRAY, 162, 838, 1, JSMN_ARRAY, 163, 837, 1, JSMN_ARRAY, 164, 836, 1, + JSMN_ARRAY, 165, 835, 1, JSMN_ARRAY, 166, 834, 1, JSMN_ARRAY, 167, 833, 1, JSMN_ARRAY, 168, 832, 1, JSMN_ARRAY, 169, 831, 1, + JSMN_ARRAY, 170, 830, 1, JSMN_ARRAY, 171, 829, 1, JSMN_ARRAY, 172, 828, 1, JSMN_ARRAY, 173, 827, 1, JSMN_ARRAY, 174, 826, 1, + JSMN_ARRAY, 175, 825, 1, JSMN_ARRAY, 176, 824, 1, JSMN_ARRAY, 177, 823, 1, JSMN_ARRAY, 178, 822, 1, JSMN_ARRAY, 179, 821, 1, + JSMN_ARRAY, 180, 820, 1, JSMN_ARRAY, 181, 819, 1, JSMN_ARRAY, 182, 818, 1, JSMN_ARRAY, 183, 817, 1, JSMN_ARRAY, 184, 816, 1, + JSMN_ARRAY, 185, 815, 1, JSMN_ARRAY, 186, 814, 1, JSMN_ARRAY, 187, 813, 1, JSMN_ARRAY, 188, 812, 1, JSMN_ARRAY, 189, 811, 1, + JSMN_ARRAY, 190, 810, 1, JSMN_ARRAY, 191, 809, 1, JSMN_ARRAY, 192, 808, 1, JSMN_ARRAY, 193, 807, 1, JSMN_ARRAY, 194, 806, 1, + JSMN_ARRAY, 195, 805, 1, JSMN_ARRAY, 196, 804, 1, JSMN_ARRAY, 197, 803, 1, JSMN_ARRAY, 198, 802, 1, JSMN_ARRAY, 199, 801, 1, + JSMN_ARRAY, 200, 800, 1, JSMN_ARRAY, 201, 799, 1, JSMN_ARRAY, 202, 798, 1, JSMN_ARRAY, 203, 797, 1, JSMN_ARRAY, 204, 796, 1, + JSMN_ARRAY, 205, 795, 1, JSMN_ARRAY, 206, 794, 1, JSMN_ARRAY, 207, 793, 1, JSMN_ARRAY, 208, 792, 1, JSMN_ARRAY, 209, 791, 1, + JSMN_ARRAY, 210, 790, 1, JSMN_ARRAY, 211, 789, 1, JSMN_ARRAY, 212, 788, 1, JSMN_ARRAY, 213, 787, 1, JSMN_ARRAY, 214, 786, 1, + JSMN_ARRAY, 215, 785, 1, JSMN_ARRAY, 216, 784, 1, JSMN_ARRAY, 217, 783, 1, JSMN_ARRAY, 218, 782, 1, JSMN_ARRAY, 219, 781, 1, + JSMN_ARRAY, 220, 780, 1, JSMN_ARRAY, 221, 779, 1, JSMN_ARRAY, 222, 778, 1, JSMN_ARRAY, 223, 777, 1, JSMN_ARRAY, 224, 776, 1, + JSMN_ARRAY, 225, 775, 1, JSMN_ARRAY, 226, 774, 1, JSMN_ARRAY, 227, 773, 1, JSMN_ARRAY, 228, 772, 1, JSMN_ARRAY, 229, 771, 1, + JSMN_ARRAY, 230, 770, 1, JSMN_ARRAY, 231, 769, 1, JSMN_ARRAY, 232, 768, 1, JSMN_ARRAY, 233, 767, 1, JSMN_ARRAY, 234, 766, 1, + JSMN_ARRAY, 235, 765, 1, JSMN_ARRAY, 236, 764, 1, JSMN_ARRAY, 237, 763, 1, JSMN_ARRAY, 238, 762, 1, JSMN_ARRAY, 239, 761, 1, + JSMN_ARRAY, 240, 760, 1, JSMN_ARRAY, 241, 759, 1, JSMN_ARRAY, 242, 758, 1, JSMN_ARRAY, 243, 757, 1, JSMN_ARRAY, 244, 756, 1, + JSMN_ARRAY, 245, 755, 1, JSMN_ARRAY, 246, 754, 1, JSMN_ARRAY, 247, 753, 1, JSMN_ARRAY, 248, 752, 1, JSMN_ARRAY, 249, 751, 1, + JSMN_ARRAY, 250, 750, 1, JSMN_ARRAY, 251, 749, 1, JSMN_ARRAY, 252, 748, 1, JSMN_ARRAY, 253, 747, 1, JSMN_ARRAY, 254, 746, 1, + JSMN_ARRAY, 255, 745, 1, JSMN_ARRAY, 256, 744, 1, JSMN_ARRAY, 257, 743, 1, JSMN_ARRAY, 258, 742, 1, JSMN_ARRAY, 259, 741, 1, + JSMN_ARRAY, 260, 740, 1, JSMN_ARRAY, 261, 739, 1, JSMN_ARRAY, 262, 738, 1, JSMN_ARRAY, 263, 737, 1, JSMN_ARRAY, 264, 736, 1, + JSMN_ARRAY, 265, 735, 1, JSMN_ARRAY, 266, 734, 1, JSMN_ARRAY, 267, 733, 1, JSMN_ARRAY, 268, 732, 1, JSMN_ARRAY, 269, 731, 1, + JSMN_ARRAY, 270, 730, 1, JSMN_ARRAY, 271, 729, 1, JSMN_ARRAY, 272, 728, 1, JSMN_ARRAY, 273, 727, 1, JSMN_ARRAY, 274, 726, 1, + JSMN_ARRAY, 275, 725, 1, JSMN_ARRAY, 276, 724, 1, JSMN_ARRAY, 277, 723, 1, JSMN_ARRAY, 278, 722, 1, JSMN_ARRAY, 279, 721, 1, + JSMN_ARRAY, 280, 720, 1, JSMN_ARRAY, 281, 719, 1, JSMN_ARRAY, 282, 718, 1, JSMN_ARRAY, 283, 717, 1, JSMN_ARRAY, 284, 716, 1, + JSMN_ARRAY, 285, 715, 1, JSMN_ARRAY, 286, 714, 1, JSMN_ARRAY, 287, 713, 1, JSMN_ARRAY, 288, 712, 1, JSMN_ARRAY, 289, 711, 1, + JSMN_ARRAY, 290, 710, 1, JSMN_ARRAY, 291, 709, 1, JSMN_ARRAY, 292, 708, 1, JSMN_ARRAY, 293, 707, 1, JSMN_ARRAY, 294, 706, 1, + JSMN_ARRAY, 295, 705, 1, JSMN_ARRAY, 296, 704, 1, JSMN_ARRAY, 297, 703, 1, JSMN_ARRAY, 298, 702, 1, JSMN_ARRAY, 299, 701, 1, + JSMN_ARRAY, 300, 700, 1, JSMN_ARRAY, 301, 699, 1, JSMN_ARRAY, 302, 698, 1, JSMN_ARRAY, 303, 697, 1, JSMN_ARRAY, 304, 696, 1, + JSMN_ARRAY, 305, 695, 1, JSMN_ARRAY, 306, 694, 1, JSMN_ARRAY, 307, 693, 1, JSMN_ARRAY, 308, 692, 1, JSMN_ARRAY, 309, 691, 1, + JSMN_ARRAY, 310, 690, 1, JSMN_ARRAY, 311, 689, 1, JSMN_ARRAY, 312, 688, 1, JSMN_ARRAY, 313, 687, 1, JSMN_ARRAY, 314, 686, 1, + JSMN_ARRAY, 315, 685, 1, JSMN_ARRAY, 316, 684, 1, JSMN_ARRAY, 317, 683, 1, JSMN_ARRAY, 318, 682, 1, JSMN_ARRAY, 319, 681, 1, + JSMN_ARRAY, 320, 680, 1, JSMN_ARRAY, 321, 679, 1, JSMN_ARRAY, 322, 678, 1, JSMN_ARRAY, 323, 677, 1, JSMN_ARRAY, 324, 676, 1, + JSMN_ARRAY, 325, 675, 1, JSMN_ARRAY, 326, 674, 1, JSMN_ARRAY, 327, 673, 1, JSMN_ARRAY, 328, 672, 1, JSMN_ARRAY, 329, 671, 1, + JSMN_ARRAY, 330, 670, 1, JSMN_ARRAY, 331, 669, 1, JSMN_ARRAY, 332, 668, 1, JSMN_ARRAY, 333, 667, 1, JSMN_ARRAY, 334, 666, 1, + JSMN_ARRAY, 335, 665, 1, JSMN_ARRAY, 336, 664, 1, JSMN_ARRAY, 337, 663, 1, JSMN_ARRAY, 338, 662, 1, JSMN_ARRAY, 339, 661, 1, + JSMN_ARRAY, 340, 660, 1, JSMN_ARRAY, 341, 659, 1, JSMN_ARRAY, 342, 658, 1, JSMN_ARRAY, 343, 657, 1, JSMN_ARRAY, 344, 656, 1, + JSMN_ARRAY, 345, 655, 1, JSMN_ARRAY, 346, 654, 1, JSMN_ARRAY, 347, 653, 1, JSMN_ARRAY, 348, 652, 1, JSMN_ARRAY, 349, 651, 1, + JSMN_ARRAY, 350, 650, 1, JSMN_ARRAY, 351, 649, 1, JSMN_ARRAY, 352, 648, 1, JSMN_ARRAY, 353, 647, 1, JSMN_ARRAY, 354, 646, 1, + JSMN_ARRAY, 355, 645, 1, JSMN_ARRAY, 356, 644, 1, JSMN_ARRAY, 357, 643, 1, JSMN_ARRAY, 358, 642, 1, JSMN_ARRAY, 359, 641, 1, + JSMN_ARRAY, 360, 640, 1, JSMN_ARRAY, 361, 639, 1, JSMN_ARRAY, 362, 638, 1, JSMN_ARRAY, 363, 637, 1, JSMN_ARRAY, 364, 636, 1, + JSMN_ARRAY, 365, 635, 1, JSMN_ARRAY, 366, 634, 1, JSMN_ARRAY, 367, 633, 1, JSMN_ARRAY, 368, 632, 1, JSMN_ARRAY, 369, 631, 1, + JSMN_ARRAY, 370, 630, 1, JSMN_ARRAY, 371, 629, 1, JSMN_ARRAY, 372, 628, 1, JSMN_ARRAY, 373, 627, 1, JSMN_ARRAY, 374, 626, 1, + JSMN_ARRAY, 375, 625, 1, JSMN_ARRAY, 376, 624, 1, JSMN_ARRAY, 377, 623, 1, JSMN_ARRAY, 378, 622, 1, JSMN_ARRAY, 379, 621, 1, + JSMN_ARRAY, 380, 620, 1, JSMN_ARRAY, 381, 619, 1, JSMN_ARRAY, 382, 618, 1, JSMN_ARRAY, 383, 617, 1, JSMN_ARRAY, 384, 616, 1, + JSMN_ARRAY, 385, 615, 1, JSMN_ARRAY, 386, 614, 1, JSMN_ARRAY, 387, 613, 1, JSMN_ARRAY, 388, 612, 1, JSMN_ARRAY, 389, 611, 1, + JSMN_ARRAY, 390, 610, 1, JSMN_ARRAY, 391, 609, 1, JSMN_ARRAY, 392, 608, 1, JSMN_ARRAY, 393, 607, 1, JSMN_ARRAY, 394, 606, 1, + JSMN_ARRAY, 395, 605, 1, JSMN_ARRAY, 396, 604, 1, JSMN_ARRAY, 397, 603, 1, JSMN_ARRAY, 398, 602, 1, JSMN_ARRAY, 399, 601, 1, + JSMN_ARRAY, 400, 600, 1, JSMN_ARRAY, 401, 599, 1, JSMN_ARRAY, 402, 598, 1, JSMN_ARRAY, 403, 597, 1, JSMN_ARRAY, 404, 596, 1, + JSMN_ARRAY, 405, 595, 1, JSMN_ARRAY, 406, 594, 1, JSMN_ARRAY, 407, 593, 1, JSMN_ARRAY, 408, 592, 1, JSMN_ARRAY, 409, 591, 1, + JSMN_ARRAY, 410, 590, 1, JSMN_ARRAY, 411, 589, 1, JSMN_ARRAY, 412, 588, 1, JSMN_ARRAY, 413, 587, 1, JSMN_ARRAY, 414, 586, 1, + JSMN_ARRAY, 415, 585, 1, JSMN_ARRAY, 416, 584, 1, JSMN_ARRAY, 417, 583, 1, JSMN_ARRAY, 418, 582, 1, JSMN_ARRAY, 419, 581, 1, + JSMN_ARRAY, 420, 580, 1, JSMN_ARRAY, 421, 579, 1, JSMN_ARRAY, 422, 578, 1, JSMN_ARRAY, 423, 577, 1, JSMN_ARRAY, 424, 576, 1, + JSMN_ARRAY, 425, 575, 1, JSMN_ARRAY, 426, 574, 1, JSMN_ARRAY, 427, 573, 1, JSMN_ARRAY, 428, 572, 1, JSMN_ARRAY, 429, 571, 1, + JSMN_ARRAY, 430, 570, 1, JSMN_ARRAY, 431, 569, 1, JSMN_ARRAY, 432, 568, 1, JSMN_ARRAY, 433, 567, 1, JSMN_ARRAY, 434, 566, 1, + JSMN_ARRAY, 435, 565, 1, JSMN_ARRAY, 436, 564, 1, JSMN_ARRAY, 437, 563, 1, JSMN_ARRAY, 438, 562, 1, JSMN_ARRAY, 439, 561, 1, + JSMN_ARRAY, 440, 560, 1, JSMN_ARRAY, 441, 559, 1, JSMN_ARRAY, 442, 558, 1, JSMN_ARRAY, 443, 557, 1, JSMN_ARRAY, 444, 556, 1, + JSMN_ARRAY, 445, 555, 1, JSMN_ARRAY, 446, 554, 1, JSMN_ARRAY, 447, 553, 1, JSMN_ARRAY, 448, 552, 1, JSMN_ARRAY, 449, 551, 1, + JSMN_ARRAY, 450, 550, 1, JSMN_ARRAY, 451, 549, 1, JSMN_ARRAY, 452, 548, 1, JSMN_ARRAY, 453, 547, 1, JSMN_ARRAY, 454, 546, 1, + JSMN_ARRAY, 455, 545, 1, JSMN_ARRAY, 456, 544, 1, JSMN_ARRAY, 457, 543, 1, JSMN_ARRAY, 458, 542, 1, JSMN_ARRAY, 459, 541, 1, + JSMN_ARRAY, 460, 540, 1, JSMN_ARRAY, 461, 539, 1, JSMN_ARRAY, 462, 538, 1, JSMN_ARRAY, 463, 537, 1, JSMN_ARRAY, 464, 536, 1, + JSMN_ARRAY, 465, 535, 1, JSMN_ARRAY, 466, 534, 1, JSMN_ARRAY, 467, 533, 1, JSMN_ARRAY, 468, 532, 1, JSMN_ARRAY, 469, 531, 1, + JSMN_ARRAY, 470, 530, 1, JSMN_ARRAY, 471, 529, 1, JSMN_ARRAY, 472, 528, 1, JSMN_ARRAY, 473, 527, 1, JSMN_ARRAY, 474, 526, 1, + JSMN_ARRAY, 475, 525, 1, JSMN_ARRAY, 476, 524, 1, JSMN_ARRAY, 477, 523, 1, JSMN_ARRAY, 478, 522, 1, JSMN_ARRAY, 479, 521, 1, + JSMN_ARRAY, 480, 520, 1, JSMN_ARRAY, 481, 519, 1, JSMN_ARRAY, 482, 518, 1, JSMN_ARRAY, 483, 517, 1, JSMN_ARRAY, 484, 516, 1, + JSMN_ARRAY, 485, 515, 1, JSMN_ARRAY, 486, 514, 1, JSMN_ARRAY, 487, 513, 1, JSMN_ARRAY, 488, 512, 1, JSMN_ARRAY, 489, 511, 1, + JSMN_ARRAY, 490, 510, 1, JSMN_ARRAY, 491, 509, 1, JSMN_ARRAY, 492, 508, 1, JSMN_ARRAY, 493, 507, 1, JSMN_ARRAY, 494, 506, 1, + JSMN_ARRAY, 495, 505, 1, JSMN_ARRAY, 496, 504, 1, JSMN_ARRAY, 497, 503, 1, JSMN_ARRAY, 498, 502, 1, JSMN_ARRAY, 499, 501, 0)); + + /* i_structure_UTF-8_BOM_empty_object.json failed to parse. */ + + return 0; +} + +int test_jsmn_test_suite_n_(void) { +#ifndef JSMN_PERMISSIVE + /* n_array_1_true_without_comma.json */ + check(query("[1 true]", JSMN_ERROR_INVAL)); + + /* n_array_a_invalid_utf8.json */ + check(query("[a]", JSMN_ERROR_INVAL)); + + /* n_array_colon_instead_of_comma.json */ + check(query("[\"\": 1]", JSMN_ERROR_INVAL)); + + /* n_array_comma_after_close.json */ + check(query("[\"\"],", JSMN_ERROR_INVAL)); + + /* n_array_comma_and_number.json */ + check(query("[,1]", JSMN_ERROR_INVAL)); + + /* n_array_double_comma.json */ + check(query("[1,,2]", JSMN_ERROR_INVAL)); + + /* n_array_double_extra_comma.json */ + check(query("[\"x\",,]", JSMN_ERROR_INVAL)); + + /* n_array_extra_close.json */ + check(query("[\"x\"]]", JSMN_ERROR_INVAL)); + + /* n_array_extra_comma.json */ + check(query("[\"\",]", JSMN_ERROR_INVAL)); + + /* n_array_incomplete_invalid_value.json */ + check(query("[x", JSMN_ERROR_INVAL)); + + /* n_array_incomplete.json */ + check(query("[\"x\"", JSMN_ERROR_PART)); + + /* n_array_inner_array_no_comma.json */ + check(query("[3[4]]", JSMN_ERROR_INVAL)); + + /* n_array_invalid_utf8.json */ + check(query("[]", JSMN_ERROR_INVAL)); + + /* n_array_items_separated_by_semicolon.json */ + check(query("[1:2]", JSMN_ERROR_INVAL)); + + /* n_array_just_comma.json */ + check(query("[,]", JSMN_ERROR_INVAL)); + + /* n_array_just_minus.json */ + check(query("[-]", JSMN_ERROR_INVAL)); + + /* n_array_missing_value.json */ + check(query("[ , \"\"]", JSMN_ERROR_INVAL)); + + /* n_array_newlines_unclosed.json */ + check(query("[\"a\",\n4\n,1,", JSMN_ERROR_PART)); + + /* n_array_number_and_comma.json */ + check(query("[1,]", JSMN_ERROR_INVAL)); + + /* n_array_number_and_several_commas.json */ + check(query("[1,,]", JSMN_ERROR_INVAL)); + + /* n_array_spaces_vertical_tab_formfeed.json */ + check(query("[\" a\"\f]", JSMN_ERROR_INVAL)); + + /* n_array_star_inside.json */ + check(query("[*]", JSMN_ERROR_INVAL)); + + /* n_array_unclosed.json */ + check(query("[\"\"", JSMN_ERROR_PART)); + + /* n_array_unclosed_trailing_comma.json */ + check(query("[1,", JSMN_ERROR_PART)); + + /* n_array_unclosed_with_new_lines.json */ + check(query("[1,\n1\n,1", JSMN_ERROR_PART)); + + /* n_array_unclosed_with_object_inside.json */ + check(query("[{}", JSMN_ERROR_PART)); + + /* n_incomplete_false.json */ + check(query("[fals]", JSMN_ERROR_PART)); + + /* n_incomplete_null.json */ + check(query("[nul]", JSMN_ERROR_PART)); + + /* n_incomplete_true.json */ + check(query("[tru]", JSMN_ERROR_PART)); + + /* n_multidigit_number_then_00.json has a null byte in it. */ + + /* n_number_0.1.2.json */ + check(query("[0.1.2]", JSMN_ERROR_INVAL)); + + /* n_number_-01.json */ + check(query("[-01]", JSMN_ERROR_INVAL)); + + /* n_number_0.3e+.json */ + check(query("[0.3e+]", JSMN_ERROR_INVAL)); + + /* n_number_0.3e.json */ + check(query("[0.3e]", JSMN_ERROR_INVAL)); + + /* n_number_0_capital_E+.json */ + check(query("[0E+]", JSMN_ERROR_INVAL)); + + /* n_number_0_capital_E.json */ + check(query("[0E]", JSMN_ERROR_INVAL)); + + /* n_number_0.e1.json */ + check(query("[0.e1]", JSMN_ERROR_INVAL)); + + /* n_number_0e+.json */ + check(query("[0e+]", JSMN_ERROR_INVAL)); + + /* n_number_0e.json */ + check(query("[0e]", JSMN_ERROR_INVAL)); + + /* n_number_1_000.json */ + check(query("[1 000.0]", JSMN_ERROR_INVAL)); + + /* n_number_1.0e+.json */ + check(query("[1.0e+]", JSMN_ERROR_INVAL)); + + /* n_number_1.0e-.json */ + check(query("[1.0e-]", JSMN_ERROR_INVAL)); + + /* n_number_1.0e.json */ + check(query("[1.0e]", JSMN_ERROR_INVAL)); + + /* n_number_-1.0..json */ + check(query("[-1.0.]", JSMN_ERROR_INVAL)); + + /* n_number_1eE2.json */ + check(query("[1eE2]", JSMN_ERROR_INVAL)); + + /* n_number_+1.json */ + check(query("[+1]", JSMN_ERROR_INVAL)); + + /* n_number_.-1.json */ + check(query("[.-1]", JSMN_ERROR_INVAL)); + + /* n_number_2.e+3.json */ + check(query("[2.e+3]", JSMN_ERROR_INVAL)); + + /* n_number_2.e-3.json */ + check(query("[2.e-3]", JSMN_ERROR_INVAL)); + + /* n_number_2.e3.json */ + check(query("[2.e3]", JSMN_ERROR_INVAL)); + + /* n_number_.2e-3.json */ + check(query("[.2e-3]", JSMN_ERROR_INVAL)); + + /* n_number_-2..json */ + check(query("[-2.]", JSMN_ERROR_INVAL)); + + /* n_number_9.e+.json */ + check(query("[9.e+]", JSMN_ERROR_INVAL)); + + /* n_number_expression.json */ + check(query("[1+2]", JSMN_ERROR_INVAL)); + + /* n_number_hex_1_digit.json */ + check(query("[0x1]", JSMN_ERROR_INVAL)); + + /* n_number_hex_2_digits.json */ + check(query("[0x42]", JSMN_ERROR_INVAL)); + + /* n_number_infinity.json */ + check(query("[Infinity]", JSMN_ERROR_INVAL)); + + /* n_number_+Inf.json */ + check(query("[+Inf]", JSMN_ERROR_INVAL)); + + /* n_number_Inf.json */ + check(query("[Inf]", JSMN_ERROR_INVAL)); + + /* n_number_invalid+-.json */ + check(query("[0e+-1]", JSMN_ERROR_INVAL)); + + /* n_number_invalid-negative-real.json */ + check(query("[-123.123foo]", JSMN_ERROR_INVAL)); + + /* n_number_invalid-utf-8-in-bigger-int.json */ + check(query("[123]", JSMN_ERROR_INVAL)); + + /* n_number_invalid-utf-8-in-exponent.json */ + check(query("[1e1]", JSMN_ERROR_INVAL)); + + /* n_number_invalid-utf-8-in-int.json */ + check(query("[0]", JSMN_ERROR_INVAL)); + + /* n_number_++.json */ + check(query("[++1234]", JSMN_ERROR_INVAL)); + + /* n_number_minus_infinity.json */ + check(query("[-Infinity]", JSMN_ERROR_INVAL)); + + /* n_number_minus_sign_with_trailing_garbage.json */ + check(query("[-foo]", JSMN_ERROR_INVAL)); + + /* n_number_minus_space_1.json */ + check(query("[- 1]", JSMN_ERROR_INVAL)); + + /* n_number_-NaN.json */ + check(query("[-NaN]", JSMN_ERROR_INVAL)); + + /* n_number_NaN.json */ + check(query("[NaN]", JSMN_ERROR_INVAL)); + + /* n_number_neg_int_starting_with_zero.json */ + check(query("[-012]", JSMN_ERROR_INVAL)); + + /* n_number_neg_real_without_int_part.json */ + check(query("[-.123]", JSMN_ERROR_INVAL)); + + /* n_number_neg_with_garbage_at_end.json */ + check(query("[-1x]", JSMN_ERROR_INVAL)); + + /* n_number_real_garbage_after_e.json */ + check(query("[1ea]", JSMN_ERROR_INVAL)); + + /* n_number_real_with_invalid_utf8_after_e.json */ + check(query("[1e]", JSMN_ERROR_INVAL)); + + /* n_number_real_without_fractional_part.json */ + check(query("[1.]", JSMN_ERROR_INVAL)); + + /* n_number_starting_with_dot.json */ + check(query("[.123]", JSMN_ERROR_INVAL)); + + /* n_number_U+FF11_fullwidth_digit_one.json */ + check(query("[1]", JSMN_ERROR_INVAL)); + + /* n_number_with_alpha_char.json */ + check(query("[1.8011670033376514H-308]", JSMN_ERROR_INVAL)); + + /* n_number_with_alpha.json */ + check(query("[1.2a-3]", JSMN_ERROR_INVAL)); + + /* n_number_with_leading_zero.json */ + check(query("[012]", JSMN_ERROR_INVAL)); + + /* n_object_bad_value.json */ + check(query("[\"x\", truth]", JSMN_ERROR_INVAL)); + + /* n_object_bracket_key.json */ + check(query("{[: \"x\"}", JSMN_ERROR_INVAL)); + + /* n_object_comma_instead_of_colon.json */ + check(query("{\"x\", null}", JSMN_ERROR_INVAL)); + + /* n_object_double_colon.json */ + check(query("{\"x\"::\"b\"}", JSMN_ERROR_INVAL)); + + /* n_object_emoji.json */ + check(query("{🇨🇭}", JSMN_ERROR_INVAL)); + + /* n_object_garbage_at_end.json */ + check(query("{\"a\":\"a\" 123}", JSMN_ERROR_INVAL)); + + /* n_object_key_with_single_quotes.json */ + check(query("{key: 'value'}", JSMN_ERROR_INVAL)); + + /* n_object_lone_continuation_byte_in_key_and_trailing_comma.json */ + check(query("{\"\":\"0\",}", JSMN_ERROR_INVAL)); + + /* n_object_missing_colon.json */ + check(query("{\"a\" b}", JSMN_ERROR_INVAL)); + + /* n_object_missing_key.json */ + check(query("{:\"b\"}", JSMN_ERROR_INVAL)); + + /* n_object_missing_semicolon.json */ + check(query("{\"a\" \"b\"}", JSMN_ERROR_INVAL)); + + /* n_object_missing_value.json */ + check(query("{\"a\":", JSMN_ERROR_PART)); + + /* n_object_no-colon.json */ + check(query("{\"a\"", JSMN_ERROR_PART)); + + /* n_object_non_string_key_but_huge_number_instead.json */ + check(query("{9999E9999:1}", JSMN_ERROR_INVAL)); + + /* n_object_non_string_key.json */ + check(query("{1:1}", JSMN_ERROR_INVAL)); + + /* n_object_repeated_null_null.json */ + check(query("{null:null,null:null}", JSMN_ERROR_INVAL)); + + /* n_object_several_trailing_commas.json */ + check(query("{\"id\":0,,,,,}", JSMN_ERROR_INVAL)); + + /* n_object_single_quote.json */ + check(query("{'a':0}", JSMN_ERROR_INVAL)); + + /* n_object_trailing_comma.json */ + check(query("{\"id\":0,}", JSMN_ERROR_INVAL)); + + /* n_object_trailing_comment.json */ + check(query("{\"a\":\"b\"}/**/", JSMN_ERROR_INVAL)); + + /* n_object_trailing_comment_open.json */ + check(query("{\"a\":\"b\"}/**//", JSMN_ERROR_INVAL)); + + /* n_object_trailing_comment_slash_open_incomplete.json */ + check(query("{\"a\":\"b\"}/", JSMN_ERROR_INVAL)); + + /* n_object_trailing_comment_slash_open.json */ + check(query("{\"a\":\"b\"}//", JSMN_ERROR_INVAL)); + + /* n_object_two_commas_in_a_row.json */ + check(query("{\"a\":\"b\",,\"c\":\"d\"}", JSMN_ERROR_INVAL)); + + /* n_object_unquoted_key.json */ + check(query("{a: \"b\"}", JSMN_ERROR_INVAL)); + + /* n_object_unterminated-value.json */ + check(query("{\"a\":\"a", JSMN_ERROR_PART)); + + /* n_object_with_single_string.json */ + check(query("{ \"foo\" : \"bar\", \"a\" }", JSMN_ERROR_INVAL)); + + /* n_object_with_trailing_garbage.json */ + check(query("{\"a\":\"b\"}#", JSMN_ERROR_INVAL)); + + /* n_single_space.json */ + check(query(" ", JSMN_ERROR_INVAL)); + + /* n_string_1_surrogate_then_escape.json */ + check(query("[\"\\uD800\\\"]", JSMN_ERROR_PART)); + + /* n_string_1_surrogate_then_escape_u1.json */ + check(query("[\"\\uD800\\u1\"]", JSMN_ERROR_INVAL)); + + /* n_string_1_surrogate_then_escape_u1x.json */ + check(query("[\"\\uD800\\u1x\"]", JSMN_ERROR_INVAL)); + + /* n_string_1_surrogate_then_escape_u.json */ + check(query("[\"\\uD800\\u\"]", JSMN_ERROR_INVAL)); + + /* n_string_accentuated_char_no_quotes.json */ + check(query("[é]", JSMN_ERROR_INVAL)); + + /* n_string_backslash_00.json has a null byte in it. */ + + /* n_string_escaped_backslash_bad.json */ + check(query("[\"\\\\\\\"]", JSMN_ERROR_PART)); + + /* n_string_escaped_ctrl_char_tab.json has a null byte in it. */ + + /* n_string_escaped_emoji.json */ + check(query("[\"\\🌀\"]", JSMN_ERROR_INVAL)); + + /* n_string_escape_x.json */ + check(query("[\"\\x00\"]", JSMN_ERROR_INVAL)); + + /* n_string_incomplete_escaped_character.json */ + check(query("[\"\\u00A\"]", JSMN_ERROR_INVAL)); + + /* n_string_incomplete_escape.json */ + check(query("[\"\\\"]", JSMN_ERROR_PART)); + + /* n_string_incomplete_surrogate_escape_invalid.json */ + check(query("[\"\\uD800\\uD800\\x\"]", JSMN_ERROR_INVAL)); + + /* n_string_incomplete_surrogate.json */ + check(query("[\"\\uD834\\uDd\"]", JSMN_ERROR_INVAL)); + + /* n_string_invalid_backslash_esc.json */ + check(query("[\"\\a\"]", JSMN_ERROR_INVAL)); + + /* n_string_invalid_unicode_escape.json */ + check(query("[\"\\uqqqq\"]", JSMN_ERROR_INVAL)); + + /* n_string_invalid_utf8_after_escape.json */ + check(query("[\"\\\"]", JSMN_ERROR_INVAL)); + + /* n_string_invalid-utf-8-in-escape.json */ + check(query("[\"\\u\"]", JSMN_ERROR_INVAL)); + + /* n_string_leading_uescaped_thinspace.json */ + check(query("[\\u0020\"asd\"]", JSMN_ERROR_INVAL)); + + /* n_string_no_quotes_with_bad_escape.json */ + check(query("[\\n]", JSMN_ERROR_INVAL)); + + /* n_string_single_doublequote.json */ + check(query("\"", JSMN_ERROR_PART)); + + /* n_string_single_quote.json */ + check(query("['single quote']", JSMN_ERROR_INVAL)); + + /* n_string_single_string_no_double_quotes.json */ + check(query("abc", JSMN_ERROR_INVAL)); + + /* n_string_start_escape_unclosed.json */ + check(query("[\"\\", JSMN_ERROR_PART)); + + /* n_string_unescaped_crtl_char.json has a null byte in it. */ + + /* n_string_unescaped_newline.json */ + check(query("[\"new\nline\"]", JSMN_ERROR_INVAL)); + + /* n_string_unescaped_tab.json */ + check(query("[\"\t\"]", JSMN_ERROR_INVAL)); + + /* n_string_unicode_CapitalU.json */ + check(query("\"\\UA66D\"", JSMN_ERROR_INVAL)); + + /* n_string_with_trailing_garbage.json */ + check(query("\"\"x", JSMN_ERROR_INVAL)); + + /* n_structure_angle_bracket_..json */ + check(query("<.>", JSMN_ERROR_INVAL)); + + /* n_structure_angle_bracket_null.json */ + check(query("[]", JSMN_ERROR_INVAL)); + + /* n_structure_array_trailing_garbage.json */ + check(query("[1]x", JSMN_ERROR_INVAL)); + + /* n_structure_array_with_extra_array_close.json */ + check(query("[1]]", JSMN_ERROR_INVAL)); + + /* n_structure_array_with_unclosed_string.json */ + check(query("[\"asd]", JSMN_ERROR_PART)); + + /* n_structure_ascii-unicode-identifier.json */ + check(query("aå", JSMN_ERROR_INVAL)); + + /* n_structure_capitalized_True.json */ + check(query("[True]", JSMN_ERROR_INVAL)); + + /* n_structure_close_unopened_array.json */ + check(query("1]", JSMN_ERROR_INVAL)); + + /* n_structure_comma_instead_of_closing_brace.json */ + check(query("{\"x\": true,", JSMN_ERROR_PART)); + + /* n_structure_double_array.json */ + check(query("[][]", JSMN_ERROR_INVAL)); + + /* n_structure_end_array.json */ + check(query("]", JSMN_ERROR_INVAL)); + + /* n_structure_incomplete_UTF8_BOM.json */ + check(query("{}", JSMN_ERROR_INVAL)); + + /* n_structure_lone-invalid-utf-8.json */ + check(query("", JSMN_ERROR_INVAL)); + + /* n_structure_lone-open-bracket.json */ + check(query("[", JSMN_ERROR_PART)); + + /* n_structure_no_data.json */ + check(query("", JSMN_ERROR_INVAL)); + + /* n_structure_null-byte-outside-string.json has a null byte in it. */ + + /* n_structure_number_with_trailing_garbage.json */ + check(query("2@", JSMN_ERROR_INVAL)); + + /* n_structure_object_followed_by_closing_object.json */ + check(query("{}}", JSMN_ERROR_INVAL)); + + /* n_structure_object_unclosed_no_value.json */ + check(query("{\"\":", JSMN_ERROR_PART)); + + /* n_structure_object_with_comment.json */ + check(query("{\"a\":/*comment*/\"b\"}", JSMN_ERROR_INVAL)); + + /* n_structure_object_with_trailing_garbage.json */ + check(query("{\"a\": true} \"x\"", JSMN_ERROR_INVAL)); + + /* n_structure_open_array_apostrophe.json */ + check(query("['", JSMN_ERROR_INVAL)); + + /* n_structure_open_array_comma.json */ + check(query("[,", JSMN_ERROR_INVAL)); + + /* n_structure_open_array_object.json is too big. */ + + /* n_structure_open_array_open_object.json */ + check(query("[{", JSMN_ERROR_PART)); + + /* n_structure_open_array_open_string.json */ + check(query("[\"a", JSMN_ERROR_PART)); + + /* n_structure_open_array_string.json */ + check(query("[\"a\"", JSMN_ERROR_PART)); + + /* n_structure_open_object_close_array.json */ + check(query("{]", JSMN_ERROR_INVAL)); + + /* n_structure_open_object_comma.json */ + check(query("{,", JSMN_ERROR_INVAL)); + + /* n_structure_open_object.json */ + check(query("{", JSMN_ERROR_PART)); + + /* n_structure_open_object_open_array.json */ + check(query("{[", JSMN_ERROR_INVAL)); + + /* n_structure_open_object_open_string.json */ + check(query("{\"a", JSMN_ERROR_PART)); + + /* n_structure_open_object_string_with_apostrophes.json */ + check(query("{'a'", JSMN_ERROR_INVAL)); + + /* n_structure_open_open.json */ + check(query("[\"\\{[\"\\{[\"\\{[\"\\{", JSMN_ERROR_INVAL)); + + /* n_structure_single_eacute.json */ + check(query("", JSMN_ERROR_INVAL)); + + /* n_structure_single_star.json */ + check(query("*", JSMN_ERROR_INVAL)); + + /* n_structure_trailing_#.json */ + check(query("{\"a\":\"b\"}#{}", JSMN_ERROR_INVAL)); + + /* n_structure_U+2060_word_joined.json */ + check(query("[⁠]", JSMN_ERROR_INVAL)); + + /* n_structure_uescaped_LF_before_string.json */ + check(query("[\\u000A\"\"]", JSMN_ERROR_INVAL)); + + /* n_structure_unclosed_array.json */ + check(query("[1", JSMN_ERROR_PART)); + + /* n_structure_unclosed_array_partial_null.json */ + check(query("[ false, nul", JSMN_ERROR_PART)); + + /* n_structure_unclosed_array_unfinished_false.json */ + check(query("[ true, fals", JSMN_ERROR_PART)); + + /* n_structure_unclosed_array_unfinished_true.json */ + check(query("[ false, tru", JSMN_ERROR_PART)); + + /* n_structure_unclosed_object.json */ + check(query("{\"asd\":\"asd\"", JSMN_ERROR_PART)); + + /* n_structure_unicode-identifier.json */ + check(query("å", JSMN_ERROR_INVAL)); + + /* n_structure_UTF8_BOM_no_data.json */ + check(query("", JSMN_ERROR_INVAL)); + + /* n_structure_whitespace_formfeed.json */ + check(query("[\f]", JSMN_ERROR_INVAL)); + + /* n_structure_whitespace_U+2060_word_joiner.json */ + check(query("[⁠]", JSMN_ERROR_INVAL)); + +#endif /* JSMN_PERMISSIVE */ + return 0; +} + +int test_jsmn_test_suite_y_(void) { + /* y_array_arraysWithSpaces.json */ + check(parse("[[] ]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_ARRAY, 1, 3, 0)); + + /* y_array_empty.json */ + check(parse("[]", 1, 1, + JSMN_ARRAY, 0, 2, 0)); + + /* y_array_empty-string.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 4, 1, + JSMN_STRING, "", 0)); + + /* y_array_false.json */ + check(parse("[false]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_PRIMITIVE, "false")); + + /* y_array_heterogeneous.json */ + check(parse("[null, 1, \"1\", {}]", 5, 5, + JSMN_ARRAY, 0, 18, 4, + JSMN_PRIMITIVE, "null", + JSMN_PRIMITIVE, "1", + JSMN_STRING, "1", 0, + JSMN_OBJECT, 15, 17, 0)); + + /* y_array_null.json */ + check(parse("[null]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "null")); + + /* y_array_with_1_and_newline.json */ + check(parse("[1\n]", 2, 2, + JSMN_ARRAY, 0, 4, 1, + JSMN_PRIMITIVE, "1")); + + /* y_array_with_leading_space.json */ + check(parse(" [1]", 2, 2, + JSMN_ARRAY, 1, 4, 1, + JSMN_PRIMITIVE, "1")); + + /* y_array_with_several_null.json */ + check(parse("[1,null,null,null,2]", 6, 6, + JSMN_ARRAY, 0, 20, 5, + JSMN_PRIMITIVE, "1", + JSMN_PRIMITIVE, "null", + JSMN_PRIMITIVE, "null", + JSMN_PRIMITIVE, "null", + JSMN_PRIMITIVE, "2")); + + /* y_array_with_trailing_space.json */ + check(parse("[2] ", 2, 2, + JSMN_ARRAY, 0, 3, 1, + JSMN_PRIMITIVE, "2")); + + /* y_number_0e+1.json */ + check(parse("[0e+1]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "0e+1")); + + /* y_number_0e1.json */ + check(parse("[0e1]", 2, 2, + JSMN_ARRAY, 0, 5, 1, + JSMN_PRIMITIVE, "0e1")); + + /* y_number_after_space.json */ + check(parse("[ 4]", 2, 2, + JSMN_ARRAY, 0, 4, 1, + JSMN_PRIMITIVE, "4")); + + /* y_number_double_close_to_zero.json */ + check(parse("[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]", 2, 2, + JSMN_ARRAY, 0, 83, 1, + JSMN_PRIMITIVE, "-0.000000000000000000000000000000000000000000000000000000000000000000000000000001")); + + /* y_number_int_with_exp.json */ + check(parse("[20e1]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "20e1")); + + /* y_number.json */ + check(parse("[123e65]", 2, 2, + JSMN_ARRAY, 0, 8, 1, + JSMN_PRIMITIVE, "123e65")); + + /* y_number_minus_zero.json */ + check(parse("[-0]", 2, 2, + JSMN_ARRAY, 0, 4, 1, + JSMN_PRIMITIVE, "-0")); + + /* y_number_negative_int.json */ + check(parse("[-123]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "-123")); + + /* y_number_negative_one.json */ + check(parse("[-1]", 2, 2, + JSMN_ARRAY, 0, 4, 1, + JSMN_PRIMITIVE, "-1")); + + /* y_number_negative_zero.json */ + check(parse("[-0]", 2, 2, + JSMN_ARRAY, 0, 4, 1, + JSMN_PRIMITIVE, "-0")); + + /* y_number_real_capital_e.json */ + check(parse("[1E22]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "1E22")); + + /* y_number_real_capital_e_neg_exp.json */ + check(parse("[1E-2]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "1E-2")); + + /* y_number_real_capital_e_pos_exp.json */ + check(parse("[1E+2]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "1E+2")); + + /* y_number_real_exponent.json */ + check(parse("[123e45]", 2, 2, + JSMN_ARRAY, 0, 8, 1, + JSMN_PRIMITIVE, "123e45")); + + /* y_number_real_fraction_exponent.json */ + check(parse("[123.456e78]", 2, 2, + JSMN_ARRAY, 0, 12, 1, + JSMN_PRIMITIVE, "123.456e78")); + + /* y_number_real_neg_exp.json */ + check(parse("[1e-2]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "1e-2")); + + /* y_number_real_pos_exponent.json */ + check(parse("[1e+2]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "1e+2")); + + /* y_number_simple_int.json */ + check(parse("[123]", 2, 2, + JSMN_ARRAY, 0, 5, 1, + JSMN_PRIMITIVE, "123")); + + /* y_number_simple_real.json */ + check(parse("[123.456789]", 2, 2, + JSMN_ARRAY, 0, 12, 1, + JSMN_PRIMITIVE, "123.456789")); + + /* y_object_basic.json */ + check(parse("{\"asd\":\"sdf\"}", 3, 3, + JSMN_OBJECT, 0, 13, 1, + JSMN_STRING, "asd", 1, + JSMN_STRING, "sdf", 0)); + + /* y_object_duplicated_key_and_value.json */ + check(parse("{\"a\":\"b\",\"a\":\"b\"}", 5, 5, + JSMN_OBJECT, 0, 17, 2, + JSMN_STRING, "a", 1, + JSMN_STRING, "b", 0, + JSMN_STRING, "a", 1, + JSMN_STRING, "b", 0)); + + /* y_object_duplicated_key.json */ + check(parse("{\"a\":\"b\",\"a\":\"c\"}", 5, 5, + JSMN_OBJECT, 0, 17, 2, + JSMN_STRING, "a", 1, + JSMN_STRING, "b", 0, + JSMN_STRING, "a", 1, + JSMN_STRING, "c", 0)); + + /* y_object_empty.json */ + check(parse("{}", 1, 1, + JSMN_OBJECT, 0, 2, 0)); + + /* y_object_empty_key.json */ + check(parse("{\"\":0}", 3, 3, + JSMN_OBJECT, 0, 6, 1, + JSMN_STRING, "", 1, + JSMN_PRIMITIVE, "0")); + + /* y_object_escaped_null_in_key.json */ + check(parse("{\"foo\\u0000bar\": 42}", 3, 3, + JSMN_OBJECT, 0, 20, 1, + JSMN_STRING, "foo\\u0000bar", 1, + JSMN_PRIMITIVE, "42")); + + /* y_object_extreme_numbers.json */ + check(parse("{ \"min\": -1.0e+28, \"max\": 1.0e+28 }", 5, 5, + JSMN_OBJECT, 0, 35, 2, + JSMN_STRING, "min", 1, + JSMN_PRIMITIVE, "-1.0e+28", + JSMN_STRING, "max", 1, + JSMN_PRIMITIVE, "1.0e+28")); + + /* y_object.json */ + check(parse("{\"asd\":\"sdf\", \"dfg\":\"fgh\"}", 5, 5, + JSMN_OBJECT, 0, 26, 2, + JSMN_STRING, "asd", 1, + JSMN_STRING, "sdf", 0, + JSMN_STRING, "dfg", 1, + JSMN_STRING, "fgh", 0)); + + /* y_object_long_strings.json */ + check(parse("{\"x\":[{\"id\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}], \"id\": \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}", 8, 8, + JSMN_OBJECT, 0, 108, 2, + JSMN_STRING, "x", 1, + JSMN_ARRAY, 5, 57, 1, + JSMN_OBJECT, 6, 56, 1, + JSMN_STRING, "id", 1, + JSMN_STRING, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 0, + JSMN_STRING, "id", 1, + JSMN_STRING, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 0)); + + /* y_object_simple.json */ + check(parse("{\"a\":[]}", 3, 3, + JSMN_OBJECT, 0, 8, 1, + JSMN_STRING, "a", 1, + JSMN_ARRAY, 5, 7, 0)); + + /* y_object_string_unicode.json */ + check(parse("{\"title\":\"\\u041f\\u043e\\u043b\\u0442\\u043e\\u0440\\u0430 \\u0417\\u0435\\u043c\\u043b\\u0435\\u043a\\u043e\\u043f\\u0430\" }", 3, 3, + JSMN_OBJECT, 0, 110, 1, + JSMN_STRING, "title", 1, + JSMN_STRING, "\\u041f\\u043e\\u043b\\u0442\\u043e\\u0440\\u0430 \\u0417\\u0435\\u043c\\u043b\\u0435\\u043a\\u043e\\u043f\\u0430", 0)); + + /* y_object_with_newlines.json */ + check(parse("{\n\"a\": \"b\"\n}", 3, 3, + JSMN_OBJECT, 0, 12, 1, + JSMN_STRING, "a", 1, + JSMN_STRING, "b", 0)); + + /* y_string_1_2_3_bytes_UTF-8_sequences.json */ + check(parse("[\"\\u0060\\u012a\\u12AB\"]", 2, 2, + JSMN_ARRAY, 0, 22, 1, + JSMN_STRING, "\\u0060\\u012a\\u12AB", 0)); + + /* y_string_accepted_surrogate_pair.json */ + check(parse("[\"\\uD801\\udc37\"]", 2, 2, + JSMN_ARRAY, 0, 16, 1, + JSMN_STRING, "\\uD801\\udc37", 0)); + + /* y_string_accepted_surrogate_pairs.json */ + check(parse("[\"\\ud83d\\ude39\\ud83d\\udc8d\"]", 2, 2, + JSMN_ARRAY, 0, 28, 1, + JSMN_STRING, "\\ud83d\\ude39\\ud83d\\udc8d", 0)); + + /* y_string_allowed_escapes.json */ + check(parse("[\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"]", 2, 2, + JSMN_ARRAY, 0, 20, 1, + JSMN_STRING, "\\\"\\\\\\/\\b\\f\\n\\r\\t", 0)); + + /* y_string_backslash_and_u_escaped_zero.json */ + check(parse("[\"\\\\u0000\"]", 2, 2, + JSMN_ARRAY, 0, 11, 1, + JSMN_STRING, "\\\\u0000", 0)); + + /* y_string_backslash_doublequotes.json */ + check(parse("[\"\\\"\"]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_STRING, "\\\"", 0)); + + /* y_string_comments.json */ + check(parse("[\"a/*b*/c/*d//e\"]", 2, 2, + JSMN_ARRAY, 0, 17, 1, + JSMN_STRING, "a/*b*/c/*d//e", 0)); + + /* y_string_double_escape_a.json */ + check(parse("[\"\\\\a\"]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_STRING, "\\\\a", 0)); + + /* y_string_double_escape_n.json */ + check(parse("[\"\\\\n\"]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_STRING, "\\\\n", 0)); + + /* y_string_escaped_control_character.json */ + check(parse("[\"\\u0012\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u0012", 0)); + + /* y_string_escaped_noncharacter.json */ + check(parse("[\"\\uFFFF\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\uFFFF", 0)); + + /* y_string_in_array.json */ + check(parse("[\"asd\"]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_STRING, "asd", 0)); + + /* y_string_in_array_with_leading_space.json */ + check(parse("[ \"asd\"]", 2, 2, + JSMN_ARRAY, 0, 8, 1, + JSMN_STRING, "asd", 0)); + + /* y_string_last_surrogates_1_and_2.json */ + check(parse("[\"\\uDBFF\\uDFFF\"]", 2, 2, + JSMN_ARRAY, 0, 16, 1, + JSMN_STRING, "\\uDBFF\\uDFFF", 0)); + + /* y_string_nbsp_uescaped.json */ + check(parse("[\"new\\u00A0line\"]", 2, 2, + JSMN_ARRAY, 0, 17, 1, + JSMN_STRING, "new\\u00A0line", 0)); + + /* y_string_nonCharacterInUTF-8_U+10FFFF.json */ + check(parse("[\"􏿿\"]", 2, 2, + JSMN_ARRAY, 0, 8, 1, + JSMN_STRING, "􏿿", 0)); + + /* y_string_nonCharacterInUTF-8_U+FFFF.json */ + check(parse("[\"￿\"]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_STRING, "￿", 0)); + + /* y_string_null_escape.json */ + check(parse("[\"\\u0000\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u0000", 0)); + + /* y_string_one-byte-utf-8.json */ + check(parse("[\"\\u002c\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u002c", 0)); + + /* y_string_pi.json */ + check(parse("[\"π\"]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_STRING, "π", 0)); + + /* y_string_reservedCharacterInUTF-8_U+1BFFF.json */ + check(parse("[\"𛿿\"]", 2, 2, + JSMN_ARRAY, 0, 8, 1, + JSMN_STRING, "𛿿", 0)); + + /* y_string_simple_ascii.json */ + check(parse("[\"asd \"]", 2, 2, + JSMN_ARRAY, 0, 8, 1, + JSMN_STRING, "asd ", 0)); + + /* y_string_space.json */ + check(parse("\" \"", 1, 1, + JSMN_STRING, " ", 0)); + + /* y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json */ + check(parse("[\"\\uD834\\uDd1e\"]", 2, 2, + JSMN_ARRAY, 0, 16, 1, + JSMN_STRING, "\\uD834\\uDd1e", 0)); + + /* y_string_three-byte-utf-8.json */ + check(parse("[\"\\u0821\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u0821", 0)); + + /* y_string_two-byte-utf-8.json */ + check(parse("[\"\\u0123\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u0123", 0)); + + /* y_string_u+2028_line_sep.json */ + check(parse("[\"
\"]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_STRING, "
", 0)); + + /* y_string_u+2029_par_sep.json */ + check(parse("[\"
\"]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_STRING, "
", 0)); + + /* y_string_uescaped_newline.json */ + check(parse("[\"new\\u000Aline\"]", 2, 2, + JSMN_ARRAY, 0, 17, 1, + JSMN_STRING, "new\\u000Aline", 0)); + + /* y_string_uEscape.json */ + check(parse("[\"\\u0061\\u30af\\u30EA\\u30b9\"]", 2, 2, + JSMN_ARRAY, 0, 28, 1, + JSMN_STRING, "\\u0061\\u30af\\u30EA\\u30b9", 0)); + + /* y_string_unescaped_char_delete.json */ + check(parse("[\"\"]", 2, 2, + JSMN_ARRAY, 0, 5, 1, + JSMN_STRING, "", 0)); + + /* y_string_unicode_2.json */ + check(parse("[\"⍂㈴⍂\"]", 2, 2, + JSMN_ARRAY, 0, 13, 1, + JSMN_STRING, "⍂㈴⍂", 0)); + + /* y_string_unicodeEscapedBackslash.json */ + check(parse("[\"\\u005C\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u005C", 0)); + + /* y_string_unicode_escaped_double_quote.json */ + check(parse("[\"\\u0022\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u0022", 0)); + + /* y_string_unicode.json */ + check(parse("[\"\\uA66D\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\uA66D", 0)); + + /* y_string_unicode_U+10FFFE_nonchar.json */ + check(parse("[\"\\uDBFF\\uDFFE\"]", 2, 2, + JSMN_ARRAY, 0, 16, 1, + JSMN_STRING, "\\uDBFF\\uDFFE", 0)); + + /* y_string_unicode_U+1FFFE_nonchar.json */ + check(parse("[\"\\uD83F\\uDFFE\"]", 2, 2, + JSMN_ARRAY, 0, 16, 1, + JSMN_STRING, "\\uD83F\\uDFFE", 0)); + + /* y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json */ + check(parse("[\"\\u200B\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u200B", 0)); + + /* y_string_unicode_U+2064_invisible_plus.json */ + check(parse("[\"\\u2064\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\u2064", 0)); + + /* y_string_unicode_U+FDD0_nonchar.json */ + check(parse("[\"\\uFDD0\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\uFDD0", 0)); + + /* y_string_unicode_U+FFFE_nonchar.json */ + check(parse("[\"\\uFFFE\"]", 2, 2, + JSMN_ARRAY, 0, 10, 1, + JSMN_STRING, "\\uFFFE", 0)); + + /* y_string_utf8.json */ + check(parse("[\"€𝄞\"]", 2, 2, + JSMN_ARRAY, 0, 11, 1, + JSMN_STRING, "€𝄞", 0)); + + /* y_string_with_del_character.json */ + check(parse("[\"aa\"]", 2, 2, + JSMN_ARRAY, 0, 7, 1, + JSMN_STRING, "aa", 0)); + + /* y_structure_lonely_false.json */ + check(parse("false", 1, 1, + JSMN_PRIMITIVE, "false")); + + /* y_structure_lonely_int.json */ + check(parse("42", 1, 1, + JSMN_PRIMITIVE, "42")); + + /* y_structure_lonely_negative_real.json */ + check(parse("-0.1", 1, 1, + JSMN_PRIMITIVE, "-0.1")); + + /* y_structure_lonely_null.json */ + check(parse("null", 1, 1, + JSMN_PRIMITIVE, "null")); + + /* y_structure_lonely_string.json */ + check(parse("\"asd\"", 1, 1, + JSMN_STRING, "asd", 0)); + + /* y_structure_lonely_true.json */ + check(parse("true", 1, 1, + JSMN_PRIMITIVE, "true")); + + /* y_structure_string_empty.json */ + check(parse("\"\"", 1, 1, + JSMN_STRING, "", 0)); + + /* y_structure_trailing_newline.json */ + check(parse("[\"a\"]\n", 2, 2, + JSMN_ARRAY, 0, 5, 1, + JSMN_STRING, "a", 0)); + + /* y_structure_true_in_array.json */ + check(parse("[true]", 2, 2, + JSMN_ARRAY, 0, 6, 1, + JSMN_PRIMITIVE, "true")); + + /* y_structure_whitespace_array.json */ + check(parse(" [] ", 1, 1, + JSMN_ARRAY, 1, 3, 0)); + + return 0; +} + + int test_empty(void) { check(parse("{}", 1, 1, JSMN_OBJECT, 0, 2, 0)); check(parse("[]", 1, 1, JSMN_ARRAY, 0, 2, 0)); @@ -31,28 +1353,37 @@ int test_object(void) { JSMN_STRING, "c", 0)); #ifndef JSMN_PERMISSIVE - check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5)); - check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2)); - check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4)); - check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4)); - check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4)); - check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4)); - check(parse("{,}", JSMN_ERROR_INVAL, 4)); + check(query("{\"a\"\n0}", JSMN_ERROR_INVAL)); + check(query("{\"a\", 0}", JSMN_ERROR_INVAL)); + check(query("{\"a\": {2}}", JSMN_ERROR_INVAL)); + check(query("{\"a\": {2: 3}}", JSMN_ERROR_INVAL)); + check(query("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL)); + check(query("{\"a\"}", JSMN_ERROR_INVAL)); + check(query("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL)); + check(query("{\"a\",\"b\":1}", JSMN_ERROR_INVAL)); + check(query("{\"a\":1,}", JSMN_ERROR_INVAL)); + check(query("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL)); + check(query("{,}", JSMN_ERROR_INVAL)); + + check(query("{\"a\":}", JSMN_ERROR_INVAL)); + check(query("{\"a\" \"b\"}", JSMN_ERROR_INVAL)); + check(query("{\"a\" ::::: \"b\"}", JSMN_ERROR_INVAL)); + check(query("{\"a\": [1 \"b\"]}", JSMN_ERROR_INVAL)); + check(query("{\"a\"\"\"}", JSMN_ERROR_INVAL)); + check(query("{\"a\":1\"\"}", JSMN_ERROR_INVAL)); + check(query("{\"a\":1\"b\":1}", JSMN_ERROR_INVAL)); + check(query("{\"a\":\"b\", \"c\":\"d\", {\"e\": \"f\"}}", JSMN_ERROR_INVAL)); #endif return 0; } int test_array(void) { - check(parse("[10}", JSMN_ERROR_INVAL, 3)); - check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)); + check(query("[10}", JSMN_ERROR_INVAL)); + check(query("[1,,3]", JSMN_ERROR_INVAL)); check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10")); - check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3)); + check(query("{\"a\": 1]", JSMN_ERROR_INVAL)); #ifndef JSMN_PERMISSIVE - check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3)); + check(query("[\"a\": 1]", JSMN_ERROR_INVAL)); #else check(parse("[\"a\": 1]", 3, 3, JSMN_ARRAY, -1, -1, 1, JSMN_STRING, "a", 1, JSMN_PRIMITIVE, "1")); #endif @@ -91,15 +1422,15 @@ int test_string(void) { JSMN_STRING, "a", 1, JSMN_ARRAY, -1, -1, 1, JSMN_STRING, "\\u0280", 0)); - check(parse("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL, 3)); - check(parse("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL, 3)); - check(parse("{{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4)); + check(query("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL)); + check(query("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL)); + check(query("{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL)); return 0; } int test_partial_string(void) { jsmnint_t r; - unsigned long i; + size_t i; jsmn_parser p; jsmntok_t tok[5]; const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}"; @@ -113,7 +1444,7 @@ int test_partial_string(void) { JSMN_STRING, "va\\\\ue", 0, JSMN_STRING, "y", 1, JSMN_STRING, "value y", 0)); } else { - check(r == JSMN_ERROR_PART); + check(r == (jsmnint_t)JSMN_ERROR_PART); } } return 0; @@ -136,7 +1467,7 @@ int test_partial_array(void) { JSMN_PRIMITIVE, "true", JSMN_ARRAY, -1, -1, 2, JSMN_PRIMITIVE, "123", JSMN_STRING, "hello", 0)); } else { - check(r == JSMN_ERROR_PART); + check(r == (jsmnint_t)JSMN_ERROR_PART); } } #endif @@ -144,7 +1475,7 @@ int test_partial_array(void) { } int test_array_nomem(void) { - int i; + jsmnint_t i; jsmnint_t r; jsmn_parser p; jsmntok_t toksmall[10], toklarge[10]; @@ -157,7 +1488,7 @@ int test_array_nomem(void) { memset(toksmall, 0, sizeof(toksmall)); memset(toklarge, 0, sizeof(toklarge)); r = jsmn_parse(&p, js, strlen(js), toksmall, i); - check(r == JSMN_ERROR_NOMEM); + check(r == (jsmnint_t)JSMN_ERROR_NOMEM); memcpy(toklarge, toksmall, sizeof(toksmall)); @@ -189,12 +1520,7 @@ int test_unquoted_keys(void) { } int test_issue_22(void) { - jsmnint_t r; - jsmn_parser p; - jsmntok_t tokens[128]; - const char *js; - - js = + const char *js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, " "\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", " "\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], " @@ -205,16 +1531,18 @@ int test_issue_22(void) { "\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 " "}], " "\"tilewidth\":32, \"version\":1, \"width\":10 }"; - jsmn_init(&p); - r = jsmn_parse(&p, js, strlen(js), tokens, 128); - check(r >= 0); + check(query(js, 61)); return 0; } int test_issue_27(void) { const char *js = "{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", "; - check(parse(js, JSMN_ERROR_PART, 8)); +#if !defined(JSMN_PERMISSIVE) && !defined(JSMN_MULTIPLE_JSON) + check(query(js, JSMN_ERROR_INVAL)); +#else + check(query(js, JSMN_ERROR_PART)); +#endif return 0; } @@ -235,48 +1563,43 @@ int test_input_length(void) { } int test_count(void) { - jsmn_parser p; const char *js; js = "{}"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + check(query(js, 1)); js = "[]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1); + check(query(js, 1)); js = "[[]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2); + check(query(js, 2)); js = "[[], []]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + check(query(js, 3)); js = "[[], []]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3); + check(query(js, 3)); js = "[[], [[]], [[], []]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + check(query(js, 7)); js = "[\"a\", [[], []]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + check(query(js, 5)); js = "[[], \"[], [[]]\", [[]]]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5); + check(query(js, 5)); js = "[1, 2, 3]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4); + check(query(js, 4)); js = "[1, 2, [3, \"a\"], null]"; - jsmn_init(&p); - check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7); + check(query(js, 7)); + + js = "[}"; + check(query(js, JSMN_ERROR_INVAL)); + + js = "{]"; + check(query(js, JSMN_ERROR_INVAL)); return 0; } @@ -302,18 +1625,18 @@ int test_nonstrict(void) { int test_unmatched_brackets(void) { const char *js; js = "\"key 1\": 1234}"; - check(parse(js, JSMN_ERROR_INVAL, 2)); + check(query(js, JSMN_ERROR_INVAL)); js = "{\"key 1\": 1234"; - check(parse(js, JSMN_ERROR_PART, 3)); + check(query(js, JSMN_ERROR_PART)); js = "{\"key 1\": 1234}}"; - check(parse(js, JSMN_ERROR_INVAL, 3)); + check(query(js, JSMN_ERROR_INVAL)); js = "\"key 1\"}: 1234"; - check(parse(js, JSMN_ERROR_INVAL, 3)); + check(query(js, JSMN_ERROR_INVAL)); js = "{\"key {1\": 1234}"; check(parse(js, 3, 3, JSMN_OBJECT, 0, 16, 1, JSMN_STRING, "key {1", 1, JSMN_PRIMITIVE, "1234")); js = "{\"key 1\":{\"key 2\": 1234}"; - check(parse(js, JSMN_ERROR_PART, 5)); + check(query(js, JSMN_ERROR_PART)); return 0; } @@ -325,13 +1648,13 @@ int test_object_key(void) { JSMN_PRIMITIVE, "1")); #ifndef JSMN_PERMISSIVE js = "{true: 1}"; - check(parse(js, JSMN_ERROR_INVAL, 3)); + check(query(js, JSMN_ERROR_INVAL)); js = "{1: 1}"; - check(parse(js, JSMN_ERROR_INVAL, 3)); + check(query(js, JSMN_ERROR_INVAL)); js = "{{\"key\": 1}: 2}"; - check(parse(js, JSMN_ERROR_INVAL, 5)); + check(query(js, JSMN_ERROR_INVAL)); js = "{[1,2]: 2}"; - check(parse(js, JSMN_ERROR_INVAL, 5)); + check(query(js, JSMN_ERROR_INVAL)); #endif return 0; } @@ -354,6 +1677,11 @@ int main(void) { test(test_nonstrict, "test for non-strict mode"); test(test_unmatched_brackets, "test for unmatched brackets"); test(test_object_key, "test for key type"); + + test(test_jsmn_test_suite_i_, "test jsmn test suite implementation"); + test(test_jsmn_test_suite_n_, "test jsmn test suite should fail"); + test(test_jsmn_test_suite_y_, "test jsmn test suite should pass"); + printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed); return (test_failed > 0); } diff --git a/test/testutil.h b/test/testutil.h index 936ad2f1..9143787d 100644 --- a/test/testutil.h +++ b/test/testutil.h @@ -3,25 +3,25 @@ #include "../jsmn.h" -static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok, +static int vtokeq(const char *s, const jsmntok_t *t, const size_t numtok, va_list ap) { if (numtok > 0) { - unsigned long i; - int start, end, size; + size_t i; + jsmnint_t start, end, size; jsmntype_t type; char *value; - size = -1; + size = JSMN_NEG; value = NULL; for (i = 0; i < numtok; i++) { type = va_arg(ap, jsmntype_t); if (type == JSMN_STRING) { value = va_arg(ap, char *); size = va_arg(ap, int); - start = end = -1; + start = end = JSMN_NEG; } else if (type == JSMN_PRIMITIVE) { value = va_arg(ap, char *); - start = end = size = -1; + start = end = size = JSMN_NEG; } else { start = va_arg(ap, int); end = va_arg(ap, int); @@ -32,7 +32,7 @@ static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok, printf("token %lu type is %d, not %d\n", i, t[i].type, type); return 0; } - if (start != -1 && end != -1) { + if (start != JSMN_NEG && end != JSMN_NEG) { if (t[i].start != start) { printf("token %lu start is %d, not %d\n", i, t[i].start, start); return 0; @@ -42,14 +42,14 @@ static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok, return 0; } } - if (size != -1 && t[i].size != size) { + if (size != JSMN_NEG && t[i].size != size) { printf("token %lu size is %d, not %d\n", i, t[i].size, size); return 0; } if (s != NULL && value != NULL) { const char *p = s + t[i].start; - if (strlen(value) != (unsigned long)(t[i].end - t[i].start) || + if (strlen(value) != (size_t)(t[i].end - t[i].start) || strncmp(p, value, t[i].end - t[i].start) != 0) { printf("token %lu value is %.*s, not %s\n", i, t[i].end - t[i].start, s + t[i].start, value); @@ -61,7 +61,7 @@ static int vtokeq(const char *s, jsmntok_t *t, unsigned long numtok, return 1; } -static int tokeq(const char *s, jsmntok_t *tokens, unsigned long numtok, ...) { +static int tokeq(const char *s, const jsmntok_t *tokens, const size_t numtok, ...) { int ok; va_list args; va_start(args, numtok); @@ -70,7 +70,17 @@ static int tokeq(const char *s, jsmntok_t *tokens, unsigned long numtok, ...) { return ok; } -static int parse(const char *s, int status, unsigned long numtok, ...) { +static int query(const char *s, const jsmnint_t status) { + jsmnint_t r; + jsmn_parser p; + + jsmn_init(&p); + r = jsmn_parse(&p, s, strlen(s), NULL, 0); + + return (status == r); +} + +static int parse(const char *s, const jsmnint_t status, const size_t numtok, ...) { jsmnint_t r; int ok = 1; va_list args; From 3135d9587c0ff73a0b201283ae8b63819b91dbbb Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Wed, 10 Jun 2020 12:06:51 -0500 Subject: [PATCH 16/20] Add .gitignore --- .gitignore | 508 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 508 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..42a550ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,508 @@ +##### Custom +Index/ +build_*/ + +##### C.gitignore +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + + +##### C++.gitignore +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + + +##### CMake.gitignore +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake + + +##### KDevelop4.gitignore +*.kdev4 +.kdev4/ + + +##### VisualStudio.gitignore +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + + +##### Xcode.gitignore +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + + +##### macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +##### Linux.gitignore +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* From 550a6d5f5f366d259a73f3a725d51f8d0a557b54 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Sun, 14 Jun 2020 17:59:36 -0500 Subject: [PATCH 17/20] Update jsmn, add jsmn_utils and explode, update tests. * Updated jsmn to allow for continuation of PRIMITIVEs after a JSMN_ERROR_PART error. Conditionals are split between lines for coverage tests. jsmn_defines.h documents and sets all of the possible build configurations. * jsmn_utils added for better examples. The explode example is in the example folder. * tests have been updated to correctly test different configurations. * README.md updated with c89 comments. --- Makefile | 37 ++-- README.md | 30 ++-- example/explode.c | 29 ++++ jsmn.h | 431 ++++++++++++++++++++++++++++------------------ jsmn_defines.h | 137 +++++++++++++++ jsmn_utils.c | 299 ++++++++++++++++++++++++++++++++ jsmn_utils.h | 94 ++++++++++ test/tests.c | 222 ++++++++++++++---------- 8 files changed, 992 insertions(+), 287 deletions(-) create mode 100644 example/explode.c create mode 100644 jsmn_defines.h create mode 100644 jsmn_utils.c create mode 100644 jsmn_utils.h diff --git a/Makefile b/Makefile index c9bf1474..b3e86056 100644 --- a/Makefile +++ b/Makefile @@ -3,25 +3,29 @@ CFLAGS:=${CFLAGS} -std=c89 -Wno-invalid-source-encoding -test: test_default test_permissive test_links test_permissive_links -test_default: test/tests.c jsmn.h - $(CC) $(CFLAGS) $(LDFLAGS) $< -o test/$@ +test: test_default test_default_low_memory test_permissive test_permissive_low_memory +test_default: test/tests.c + $(CC) $(CFLAGS) $(LDFLAGS) $^ -o test/$@ ./test/$@ -test_permissive: test/tests.c jsmn.h - $(CC) -DJSMN_PERMISSIVE=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ +test_default_low_memory: test/tests.c + $(CC) -DJSMN_LOW_MEMORY $(CFLAGS) $(LDFLAGS) $^ -o test/$@ ./test/$@ -test_links: test/tests.c jsmn.h - $(CC) -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ + +test_permissive: test/tests.c + $(CC) -DJSMN_PERMISSIVE $(CFLAGS) $(LDFLAGS) $^ -o test/$@ ./test/$@ -test_permissive_links: test/tests.c jsmn.h - $(CC) -DJSMN_PERMISSIVE=1 -DJSMN_PARENT_LINKS=1 $(CFLAGS) $(LDFLAGS) $< -o test/$@ +test_permissive_low_memory: test/tests.c + $(CC) -DJSMN_PERMISSIVE -DJSMN_LOW_MEMORY $(CFLAGS) $(LDFLAGS) $^ -o test/$@ ./test/$@ -simple_example: example/simple.c jsmn.h - $(CC) $(LDFLAGS) $< -o $@ +simple_example: example/simple.c + $(CC) $(LDFLAGS) $^ -o $@ + +jsondump: example/jsondump.c + $(CC) $(LDFLAGS) $^ -o $@ -jsondump: example/jsondump.c jsmn.h - $(CC) $(LDFLAGS) $< -o $@ +explode: example/explode.c jsmn_utils.c + $(CC) $(LDFLAGS) $^ -o $@ fmt: clang-format -i jsmn.h test/*.[ch] example/*.[ch] @@ -33,7 +37,10 @@ clean: rm -f *.o example/*.o rm -f simple_example rm -f jsondump - rm -f test/test_* + rm -f explode + rm -f test/test_default + rm -f test/test_default_low_memory + rm -f test/test_permissive + rm -f test/test_permissive_low_memory .PHONY: clean test - diff --git a/README.md b/README.md index 388f8913..e4d00ce7 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Download `jsmn.h`, include it, done. ... jsmn_parser p; -jsmntok_t t[128]; // We expect no more than 128 JSON tokens +jsmntok_t t[128]; /* We expect no more than 128 JSON tokens */ jsmn_init(&p); r = jsmn_parse(&p, s, strlen(s), t, 128); @@ -98,11 +98,11 @@ from multiple C files, to avoid duplication of symbols you may define `JSMN_HEADER` macro. ```c -// In every .c file that uses jsmn include only declarations: +/* In every .c file that uses jsmn include only declarations: */ #define JSMN_HEADER #include "jsmn.h" -// Additionally, create one jsmn.c file for jsmn implementation: +/* Additionally, create one jsmn.c file for jsmn implementation: */ #include "jsmn.h" ``` @@ -112,11 +112,11 @@ API Token types are described by `jsmntype_t`: ```c typedef enum { - JSMN_UNDEFINED = 0x00, - JSMN_OBJECT = 0x01, //!< Object - JSMN_ARRAY = 0x02, //!< Array - JSMN_STRING = 0x04, //!< String - JSMN_PRIMITIVE = 0x08, //!< Other primitive: number, boolean (true/false) or null + JSMN_UNDEFINED = 0x0000, + JSMN_OBJECT = 0x0001, /*!< Object */ + JSMN_ARRAY = 0x0002, /*!< Array */ + JSMN_STRING = 0x0004, /*!< String */ + JSMN_PRIMITIVE = 0x0008, /*!< Other primitive: number, boolean (true/false) or null */ ... } jsmntype_t; ``` @@ -131,10 +131,10 @@ first character: Token is an object of `jsmntok_t` type: ```c typedef struct { - jsmntype_t type; //!< type (object, array, string etc.) - jsmnint_t start; //!< start position in JSON data string - jsmnint_t end; //!< end position in JSON data string - jsmnint_t size; //!< number of children + jsmntype_t type; /*!< type (object, array, string etc.) */ + jsmnint_t start; /*!< start position in JSON data string */ + jsmnint_t end; /*!< end position in JSON data string */ + jsmnint_t size; /*!< number of children */ ... } jsmntok_t; ``` @@ -150,9 +150,9 @@ jsmntok_t tokens[10]; jsmn_init(&parser); -// js - pointer to JSON string -// tokens - an array of tokens available -// 10 - number of tokens available +/* js - pointer to JSON string */ +/* tokens - an array of tokens available */ +/* 10 - number of tokens available */ jsmn_parse(&parser, js, strlen(js), tokens, 10); ``` This will create a parser, and then it tries to parse up to 10 JSON tokens from diff --git a/example/explode.c b/example/explode.c new file mode 100644 index 00000000..73945093 --- /dev/null +++ b/example/explode.c @@ -0,0 +1,29 @@ +#define JSMN_HEADER +#include "../jsmn_utils.h" + +#include +#include + +int main(void) +{ + FILE *fp; + size_t size; + char *JSON_STRING; + + fp = fopen("library.json", "r"); + + fseek(fp, 0, SEEK_END); + size = ftell(fp); + rewind(fp); + + JSON_STRING = calloc(size, sizeof(char)); + (void)fread(JSON_STRING, sizeof(char), size, fp); + + fclose(fp); + + jsmn_explodeJSON(JSON_STRING, size); + + free(JSON_STRING); + + return EXIT_SUCCESS; +} diff --git a/jsmn.h b/jsmn.h index d6315d15..95c8b296 100644 --- a/jsmn.h +++ b/jsmn.h @@ -24,44 +24,41 @@ #ifndef JSMN_H #define JSMN_H +#define JSMN_VERSION_MAJOR 2 +#define JSMN_VERSION_MINOR 0 +#define JSMN_VERSION_PATCH 0 + #include -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef JSMN_API -# ifdef JSMN_STATIC -# define JSMN_API static -# else -# define JSMN_API extern -# endif -#endif +#include "jsmn_defines.h" #ifdef JSMN_SHORT_TOKENS typedef unsigned short jsmnint_t; -# define packed __attribute__((packed)) #else typedef unsigned int jsmnint_t; -# define packed #endif #define JSMN_NEG ((jsmnint_t)-1) /** * JSON type identifier. Basic types are: */ -typedef enum packed { +typedef enum { JSMN_UNDEFINED = 0x0000, - JSMN_OBJECT = 0x0001, //!< Object - JSMN_ARRAY = 0x0002, //!< Array - JSMN_STRING = 0x0004, //!< String - JSMN_PRIMITIVE = 0x0008, //!< Other primitive: number, boolean (true/false) or null + JSMN_OBJECT = 0x0001, /*!< Object */ + JSMN_ARRAY = 0x0002, /*!< Array */ + JSMN_STRING = 0x0004, /*!< String */ + JSMN_PRIMITIVE = 0x0008, /*!< Other primitive: number, boolean (true/false) or null */ - JSMN_KEY = 0x0010, //!< is a key - JSMN_VALUE = 0x0020, //!< is a value + JSMN_KEY = 0x0010, /*!< is a key */ + JSMN_VALUE = 0x0020, /*!< is a value */ /* Complex elements */ JSMN_CONTAINER = JSMN_OBJECT | JSMN_ARRAY, +#ifndef JSMN_PERMISSIVE_KEY + JSMN_KEY_TYPE = JSMN_STRING, +#else + JSMN_KEY_TYPE = JSMN_STRING | JSMN_PRIMITIVE, +#endif JSMN_ANY_TYPE = JSMN_OBJECT | JSMN_ARRAY | JSMN_STRING | JSMN_PRIMITIVE, JSMN_OBJ_VAL = JSMN_OBJECT | JSMN_VALUE, @@ -69,30 +66,27 @@ typedef enum packed { JSMN_STR_KEY = JSMN_STRING | JSMN_KEY, JSMN_STR_VAL = JSMN_STRING | JSMN_VALUE, JSMN_PRI_VAL = JSMN_PRIMITIVE | JSMN_VALUE, -#ifdef JSMN_PERMISSIVE +#ifdef JSMN_PERMISSIVE_KEY JSMN_OBJ_KEY = JSMN_OBJECT | JSMN_KEY, JSMN_ARR_KEY = JSMN_ARRAY | JSMN_KEY, JSMN_PRI_KEY = JSMN_PRIMITIVE | JSMN_KEY, #endif /* Primitive extension */ - JSMN_PRI_LITERAL = 0x0040, //!< true, false, null - JSMN_PRI_INTEGER = 0x0080, //!< 0, 1 - 9 - JSMN_PRI_SIGN = 0x0100, //!< minus sign, '-' or plus sign, '+' - JSMN_PRI_DECIMAL = 0x0200, //!< deminal point '.' - JSMN_PRI_EXPONENT = 0x0400, //!< exponent, 'e' or 'E' + JSMN_PRI_LITERAL = 0x0040, /*!< true, false, null */ + JSMN_PRI_INTEGER = 0x0080, /*!< 0, 1 - 9 */ + JSMN_PRI_SIGN = 0x0100, /*!< minus sign, '-' or plus sign, '+' */ + JSMN_PRI_DECIMAL = 0x0200, /*!< deminal point '.' */ + JSMN_PRI_EXPONENT = 0x0400, /*!< exponent, 'e' or 'E' */ JSMN_PRI_MINUS = JSMN_PRI_SIGN, /* Parsing validation, expectations, and state information */ - JSMN_CLOSE = 0x0800, //!< Close OBJECT '}' or ARRAY ']' - JSMN_DELIMITER = 0x1000, //!< Colon ':' after KEY, Comma ',' after VALUE - JSMN_PREV_KEY = 0x2000, //!< Previous token is an KEY - JSMN_PREV_VAL = 0x4000, //!< Previous token is a VALUE - JSMN_INSD_OBJ = 0x8000, //!< Inside an OBJECT - - JSMN_COLON = JSMN_DELIMITER | JSMN_PREV_KEY, - JSMN_COMMA = JSMN_DELIMITER | JSMN_PREV_VAL, + JSMN_PRI_CONTINUE = 0x0800, /*!< Allow a continuation of a PRIMITIVE */ + JSMN_CLOSE = 0x1000, /*!< Close OBJECT '}' or ARRAY ']' */ + JSMN_COLON = 0x2000, /*!< Colon ':' expected after KEY */ + JSMN_COMMA = 0x4000, /*!< Comma ',' expected after VALUE */ + JSMN_INSD_OBJ = 0x8000, /*!< Inside an OBJECT */ /* Parsing rules */ JSMN_ROOT_INIT = JSMN_ANY_TYPE | JSMN_VALUE, @@ -102,53 +96,65 @@ typedef enum packed { #else JSMN_ROOT = JSMN_ANY_TYPE | JSMN_VALUE, #endif - JSMN_OPEN_OBJECT = JSMN_STRING | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, + JSMN_OPEN_OBJECT = JSMN_KEY_TYPE | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, JSMN_AFTR_OBJ_KEY = JSMN_VALUE | JSMN_INSD_OBJ | JSMN_COLON, JSMN_AFTR_OBJ_VAL = JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, JSMN_OPEN_ARRAY = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_CLOSE, JSMN_AFTR_ARR_VAL = JSMN_VALUE | JSMN_CLOSE | JSMN_COMMA, JSMN_AFTR_CLOSE = JSMN_CLOSE | JSMN_COMMA, - JSMN_AFTR_COLON = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_INSD_OBJ | JSMN_PREV_KEY, - JSMN_AFTR_COMMA_O = JSMN_STRING | JSMN_KEY | JSMN_INSD_OBJ | JSMN_PREV_VAL, - JSMN_AFTR_COMMA_A = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_PREV_VAL, + JSMN_AFTR_COLON = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_INSD_OBJ, + JSMN_AFTR_COMMA_O = JSMN_KEY_TYPE | JSMN_KEY | JSMN_INSD_OBJ, + JSMN_AFTR_COMMA_A = JSMN_ANY_TYPE | JSMN_VALUE, #else JSMN_ROOT = JSMN_ANY_TYPE | JSMN_COLON | JSMN_COMMA, JSMN_ROOT_AFTR_O = JSMN_ANY_TYPE | JSMN_COMMA, - JSMN_OPEN_OBJECT = JSMN_ANY_TYPE | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, + JSMN_OPEN_OBJECT = JSMN_KEY_TYPE | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, JSMN_AFTR_OBJ_KEY = JSMN_VALUE | JSMN_INSD_OBJ | JSMN_COLON, JSMN_AFTR_OBJ_VAL = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, JSMN_OPEN_ARRAY = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_CLOSE, - JSMN_AFTR_ARR_VAL = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_COLON | JSMN_COMMA, + JSMN_AFTR_ARR_VAL = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_COMMA, JSMN_AFTR_CLOSE = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_COMMA, - JSMN_AFTR_COLON = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_INSD_OBJ | JSMN_PREV_KEY, - JSMN_AFTR_COLON_R = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_PREV_KEY, - JSMN_AFTR_COMMA_O = JSMN_ANY_TYPE | JSMN_KEY | JSMN_INSD_OBJ | JSMN_PREV_VAL, - JSMN_AFTR_COMMA_A = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_PREV_VAL, - JSMN_AFTR_COMMA_R = JSMN_ANY_TYPE | JSMN_PREV_VAL, + JSMN_AFTR_COLON = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_INSD_OBJ, + JSMN_AFTR_COLON_R = JSMN_ANY_TYPE | JSMN_VALUE, + JSMN_AFTR_COMMA_O = JSMN_KEY_TYPE | JSMN_KEY | JSMN_INSD_OBJ, + JSMN_AFTR_COMMA_A = JSMN_ANY_TYPE | JSMN_VALUE, + JSMN_AFTR_COMMA_R = JSMN_ANY_TYPE, #endif } jsmntype_t; -enum jsmnerr { - JSMN_SUCCESS = 0, - JSMN_ERROR_NOMEM = -1, //!< Not enough tokens were provided - JSMN_ERROR_INVAL = -2, //!< Invalid character inside JSON string - JSMN_ERROR_PART = -3, //!< The string is not a full JSON packet, more bytes expected - JSMN_ERROR_LEN = -4, //!< Input data too long -}; +/*! + * JSMN Error Codes + */ +typedef enum jsmnerr { + JSMN_SUCCESS = 0, + JSMN_ERROR_NOMEM = -1, /*!< Not enough tokens were provided */ + JSMN_ERROR_LEN = -2, /*!< Input data too long */ + JSMN_ERROR_INVAL = -3, /*!< Invalid character inside JSON string */ + JSMN_ERROR_PART = -4, /*!< The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_UNMATCHED_BRACKETS = -5, /*!< The JSON string has unmatched brackets */ +} jsmnerr; + +/*! + * JSMN Boolean + */ +typedef enum jsmnbool { + JSMN_FALSE = 0, + JSMN_TRUE = 1, +} jsmnbool; /** * JSON token description. */ typedef struct jsmntok_t { - jsmntype_t type; //!< type (object, array, string etc.) - jsmnint_t start; //!< start position in JSON data string - jsmnint_t end; //!< end position in JSON data string - jsmnint_t size; //!< number of children + jsmntype_t type; /*!< type (object, array, string etc.) */ + jsmnint_t start; /*!< start position in JSON data string */ + jsmnint_t end; /*!< end position in JSON data string */ + jsmnint_t size; /*!< number of children */ #ifdef JSMN_PARENT_LINKS - jsmnint_t parent; //!< parent id + jsmnint_t parent; /*!< parent id */ #endif #ifdef JSMN_NEXT_SIBLING - jsmnint_t next_sibling; //!< next sibling id + jsmnint_t next_sibling; /*!< next sibling id */ #endif } jsmntok_t; @@ -159,12 +165,12 @@ typedef struct jsmntok_t { * the string being parsed now and current position in that string. */ typedef struct jsmn_parser { - jsmnint_t pos; //!< offset in the JSON string - jsmnint_t toknext; //!< next token to allocate - //!< when tokens == NULL, keeps track of container types to a depth of (sizeof(jsmnint_t) * 8) - jsmnint_t toksuper; //!< superior token node, e.g. parent object or array - //!< when tokens == NULL, toksuper represents container depth - jsmntype_t expected; //!< Expected jsmn type(s) + jsmnint_t pos; /*!< offset in the JSON string */ + jsmnint_t toknext; /*!< next token to allocate */ + /*!< when tokens == NULL, keeps track of container types to a depth of (sizeof(jsmnint_t) * 8) */ + jsmnint_t toksuper; /*!< superior token node, e.g. parent object or array */ + /*!< when tokens == NULL, toksuper represents container depth */ + jsmntype_t expected; /*!< Expected jsmn type(s) */ } jsmn_parser; /** @@ -194,12 +200,18 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, const size_t num_tokens); #ifndef JSMN_HEADER + +#ifdef __cplusplus +extern "C" { +#endif + /** * Allocates a fresh unused token from the token pool. */ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, - const size_t num_tokens) { + const size_t num_tokens) +{ if (parser->toknext >= num_tokens) { return NULL; } @@ -222,7 +234,8 @@ jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, */ static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, - const jsmnint_t start, const jsmnint_t end) { + const jsmnint_t start, const jsmnint_t end) +{ token->type = type; token->start = start; token->end = end; @@ -234,7 +247,8 @@ void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, * Set previous child's next_sibling to current token */ static -void jsmn_next_sibling(jsmn_parser *parser, jsmntok_t *tokens) { +void jsmn_next_sibling(jsmn_parser *parser, jsmntok_t *tokens) +{ jsmnint_t sibling; /* Start with parent's first child */ @@ -245,79 +259,140 @@ void jsmn_next_sibling(jsmn_parser *parser, jsmntok_t *tokens) { } /* If the first child is the current token */ - if (sibling == parser->toknext - 1) + if (sibling == parser->toknext - 1) { return; + } /* Loop until we find previous sibling */ - while (tokens[sibling].next_sibling != JSMN_NEG) + while (tokens[sibling].next_sibling != JSMN_NEG) { sibling = tokens[sibling].next_sibling; + } /* Set previous sibling's next_sibling to current token */ tokens[sibling].next_sibling = parser->toknext - 1; } #endif +static +jsmnbool isWhitespace(const char c) +{ + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + return JSMN_TRUE; + } + return JSMN_FALSE; +} + +static +jsmnbool isHexadecimal(const char c) +{ + if ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f')) { + return JSMN_TRUE; + } + return JSMN_FALSE; +} + +static +jsmnbool isCharacter(const char c) +{ + if ((c >= 0x20 && c <= 0x21) || + (c >= 0x23 && c <= 0x5B) || + (c >= 0x5D)) { + return JSMN_TRUE; + } + return JSMN_FALSE; +} + +static +jsmnbool isSpecialChar(const char c) +{ + if (c == '{' || c == '}' || c == '[' || c == ']' || + c == '"' || c == ':' || c == ',') { + return JSMN_TRUE; + } + return JSMN_FALSE; +} + /** * Fills next available token with JSON primitive. */ static jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { + const size_t num_tokens) +{ /* If a PRIMITIVE wasn't expected */ - if (!(parser->expected & JSMN_PRIMITIVE)) { + if (!(parser->expected & (JSMN_PRIMITIVE | JSMN_PRI_CONTINUE))) { return JSMN_ERROR_INVAL; } - jsmnint_t start; + jsmnint_t pos; jsmntype_t type; - jsmntype_t expected; + jsmntype_t expected = JSMN_CLOSE; - start = parser->pos; + if (!(parser->expected & JSMN_PRI_CONTINUE)) { + pos = parser->pos; + } else { + if (tokens != NULL) { + pos = tokens[parser->toknext - 1].start; + } else { + pos = parser->pos; + while (pos != JSMN_NEG && + !isWhitespace(js[pos]) && + !isSpecialChar(js[pos]) && + isCharacter(js[pos])) { + pos--; + } + pos++; + } + } type = JSMN_PRIMITIVE; -#ifndef JSMN_PERMISSIVE - if (js[parser->pos] == 't' || js[parser->pos] == 'f' || js[parser->pos] == 'n') { +#ifndef JSMN_PERMISSIVE_PRIMITIVE + if (js[pos] == 't' || + js[pos] == 'f' || + js[pos] == 'n') { char *literal = NULL; jsmnint_t size = 0; - if (js[parser->pos] == 't') { + if (js[pos] == 't') { literal = "true"; size = 4; - } else if (js[parser->pos] == 'f') { + } else if (js[pos] == 'f') { literal = "false"; size = 5; - } else if (js[parser->pos] == 'n') { + } else if (js[pos] == 'n') { literal = "null"; size = 4; } jsmnint_t i; - for (i = 1, parser->pos++; i < size; i++, parser->pos++) { - if (parser->pos >= len || js[parser->pos] == '\0' || - (parser->toksuper != JSMN_NEG && js[parser->pos] == ',') || - js[parser->pos] == (parser->expected & JSMN_INSD_OBJ ? '}' : ']')) { - parser->pos = start; + for (i = 1, pos++; i < size; i++, pos++) { + if (pos == len || + js[pos] == '\0') { return JSMN_ERROR_PART; - } else if (js[parser->pos] != literal[i]) { - parser->pos = start; + } else if (js[pos] != literal[i]) { return JSMN_ERROR_INVAL; } } type |= JSMN_PRI_LITERAL; + if (pos == len || + js[pos] == '\0') { + goto found; + } } else { expected = JSMN_PRI_MINUS | JSMN_PRI_INTEGER; - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { + for (; pos < len && js[pos] != '\0'; pos++) { + switch (js[pos]) { case '0': if (!(expected & JSMN_PRI_INTEGER)) { - parser->pos = start; return JSMN_ERROR_INVAL; } if (type & JSMN_PRI_EXPONENT) { expected = JSMN_PRI_INTEGER | JSMN_CLOSE; } else if (type & JSMN_PRI_DECIMAL) { expected = JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT | JSMN_CLOSE; - } else if (start == parser->pos || - (start + 1 == parser->pos && (type & JSMN_PRI_MINUS))) { + } else if (parser->pos == pos || + (parser->pos + 1 == pos && (type & JSMN_PRI_MINUS))) { expected = JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; } else { expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; @@ -333,7 +408,6 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, case '8': case '9': if (!(expected & JSMN_PRI_INTEGER)) { - parser->pos = start; return JSMN_ERROR_INVAL; } if (type & JSMN_PRI_EXPONENT) { @@ -341,29 +415,26 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } else if (type & JSMN_PRI_DECIMAL) { expected = JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT | JSMN_CLOSE; } else { - expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; + expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; } break; case '-': if (!(expected & JSMN_PRI_MINUS)) { - parser->pos = start; return JSMN_ERROR_INVAL; } expected = JSMN_PRI_INTEGER; - if (start == parser->pos) { + if (parser->pos == pos) { type |= JSMN_PRI_MINUS; } break; case '+': if (!(expected & JSMN_PRI_SIGN)) { - parser->pos = start; return JSMN_ERROR_INVAL; } expected = JSMN_PRI_INTEGER; break; case '.': if (!(expected & JSMN_PRI_DECIMAL)) { - parser->pos = start; return JSMN_ERROR_INVAL; } type |= JSMN_PRI_DECIMAL; @@ -372,7 +443,6 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, case 'e': case 'E': if (!(expected & JSMN_PRI_EXPONENT)) { - parser->pos = start; return JSMN_ERROR_INVAL; } type |= JSMN_PRI_EXPONENT; @@ -380,19 +450,19 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, break; default: if (!(expected & JSMN_CLOSE)) { - parser->pos = start; return JSMN_ERROR_INVAL; } goto check_primitive_border; } } + if (!(expected & JSMN_CLOSE)) { + return JSMN_ERROR_INVAL; + } else { + goto found; + } } check_primitive_border: - if (parser->pos == len && js[parser->pos] != '\0') { - parser->pos = start; - return JSMN_ERROR_PART; - } - switch (js[parser->pos]) { + switch (js[pos]) { case ' ': case '\t': case '\n': @@ -405,21 +475,15 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, case ':': case '{': case '[': - parser->pos = start; return JSMN_ERROR_INVAL; case '\0': - if (parser->toksuper != JSMN_NEG) { - parser->pos = start; - return JSMN_ERROR_PART; - } goto found; default: - parser->pos = start; return JSMN_ERROR_INVAL; } #else - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - switch (js[parser->pos]) { + for (; pos < len && js[pos] != '\0'; pos++) { + switch (js[pos]) { case ' ': case '\t': case '\n': @@ -428,12 +492,15 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, case '}': case ']': case ':': + + case '{': + case '[': + case '"': goto found; default: /* to quiet a warning from gcc */ break; } - if (js[parser->pos] < 32 || js[parser->pos] >= 127) { - parser->pos = start; + if (!isCharacter(js[pos])) { return JSMN_ERROR_INVAL; } } @@ -452,7 +519,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, type |= JSMN_VALUE | JSMN_INSD_OBJ; #ifdef JSMN_PERMISSIVE /* OBJECT VALUE at the ROOT level */ - } else if (parser->expected == JSMN_AFTR_COLON_R) { + } else if (parser->toksuper == JSMN_NEG) { parser->expected = JSMN_ROOT_AFTR_O; type |= JSMN_VALUE; #endif @@ -465,20 +532,29 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, parser->expected = JSMN_ROOT; type |= JSMN_VALUE; } + if (pos == len || + js[pos] == '\0') { + parser->expected |= JSMN_PRI_CONTINUE; + } if (tokens == NULL) { - parser->pos--; + parser->pos = pos - 1; return JSMN_SUCCESS; } jsmntok_t *token; - token = jsmn_alloc_token(parser, tokens, num_tokens); - if (token == NULL) { - parser->expected = expected; - parser->pos = start; - return JSMN_ERROR_NOMEM; + if (!(expected & JSMN_PRI_CONTINUE)) { + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->expected = expected; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, type, parser->pos, pos); + } else { + token = &tokens[parser->toknext - 1]; + jsmn_fill_token(token, type, token->start, pos); } - jsmn_fill_token(token, type, start, parser->pos); + parser->pos = pos; #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif @@ -487,7 +563,9 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, #endif if (parser->toksuper != JSMN_NEG) { - tokens[parser->toksuper].size++; + if (!(expected & JSMN_PRI_CONTINUE)) { + tokens[parser->toksuper].size++; + } if (!(tokens[parser->toksuper].type & JSMN_CONTAINER)) { #ifdef JSMN_PARENT_LINKS @@ -519,7 +597,8 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, static jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { + const size_t num_tokens) +{ /* If a STRING wasn't expected */ if (!(parser->expected & JSMN_STRING)) { return JSMN_ERROR_INVAL; @@ -529,15 +608,15 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_LEN; } - jsmnint_t start; - start = parser->pos; + jsmnint_t pos; + pos = parser->pos; /* Skip starting quote */ - parser->pos++; + pos++; char c; - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { - c = js[parser->pos]; + for (; pos < len && js[pos] != '\0'; pos++) { + c = js[pos]; /* Quote: end of string */ if (c == '\"') { @@ -554,7 +633,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, type = JSMN_STRING | JSMN_VALUE | JSMN_INSD_OBJ; #ifdef JSMN_PERMISSIVE /* OBJECT VALUE at the ROOT level */ - } else if (parser->expected == JSMN_AFTR_COLON_R) { + } else if (parser->toksuper == JSMN_NEG) { parser->expected = JSMN_ROOT_AFTR_O; type = JSMN_STRING | JSMN_VALUE; #endif @@ -569,6 +648,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, } if (tokens == NULL) { + parser->pos = pos; return JSMN_SUCCESS; } @@ -576,10 +656,10 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->expected = expected; - parser->pos = start; return JSMN_ERROR_NOMEM; } - jsmn_fill_token(token, type, start + 1, parser->pos); + jsmn_fill_token(token, type, parser->pos + 1, pos); + parser->pos = pos; #ifdef JSMN_PARENT_LINKS token->parent = parser->toksuper; #endif @@ -601,11 +681,11 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, break; } } -# ifdef JSMN_PERMISSIVE +# ifdef JSMN_PERMISSIVE if (i == JSMN_NEG) { parser->toksuper = i; } -# endif +# endif #endif } } @@ -614,9 +694,9 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, } /* Backslash: Quoted symbol expected */ - if (c == '\\' && parser->pos + 1 < len) { - parser->pos++; - switch (js[parser->pos]) { + if (c == '\\' && pos + 1 < len) { + pos++; + switch (js[pos]) { /* Allowed escaped symbols */ case '\"': case '\\': @@ -629,40 +709,42 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, break; /* Allows escaped symbol \uXXXX */ case 'u': - parser->pos++; + pos++; jsmnint_t i; - for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + for (i = pos + 4; pos < i; pos++) { + if (pos == len || + js[pos] == '\0') { + return JSMN_ERROR_PART; + } /* If it isn't a hex character we have an error */ - if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ - parser->pos = start; + if (!isHexadecimal(js[pos])) { return JSMN_ERROR_INVAL; } - parser->pos++; } - parser->pos--; + pos--; break; /* Unexpected symbol */ default: - parser->pos = start; return JSMN_ERROR_INVAL; } } - /* Actual form feed (0x0C), new line (0x0A), carraige return (0x0D), or tab (0x09) not allowed */ - else if (c == '\f' || c == '\n' || c == '\r' || c == '\t') { - parser->pos = start; + /* form feed, new line, carraige return, tab, and vertical tab not allowed */ + else if (c == '\f' || + c == '\n' || + c == '\r' || + c == '\t' || + c == '\v') { return JSMN_ERROR_INVAL; } } - parser->pos = start; return JSMN_ERROR_PART; } static jsmnint_t jsmn_parse_container_open(jsmn_parser *parser, const char c, - jsmntok_t *tokens, const size_t num_tokens) { + jsmntok_t *tokens, const size_t num_tokens) +{ /* If an OBJECT or ARRAY wasn't expected */ if (!(parser->expected & JSMN_CONTAINER)) { return JSMN_ERROR_INVAL; @@ -714,7 +796,8 @@ jsmnint_t jsmn_parse_container_open(jsmn_parser *parser, const char c, static jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, - jsmntok_t *tokens) { + jsmntok_t *tokens) +{ /* If an OBJECT or ARRAY CLOSE wasn't expected */ if (!(parser->expected & JSMN_CLOSE)) { return JSMN_ERROR_INVAL; @@ -724,9 +807,9 @@ jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, if (parser->toksuper < (sizeof(jsmnint_t) * 8)) { jsmntype_t type; type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); - if ((((parser->toknext & 1 << parser->toksuper) == 1) && !(type & JSMN_OBJECT)) || - (((parser->toknext & 1 << parser->toksuper) == 0) && !(type & JSMN_ARRAY))) { - return JSMN_ERROR_INVAL; + if ((((parser->toknext & (1 << parser->toksuper)) == 1) && !(type & JSMN_OBJECT)) || + (((parser->toknext & (1 << parser->toksuper)) == 0) && !(type & JSMN_ARRAY))) { + return JSMN_ERROR_UNMATCHED_BRACKETS; } parser->toknext &= ~(1 << parser->toksuper); } @@ -736,9 +819,15 @@ jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, jsmntok_t *token; type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PERMISSIVE + if (parser->toksuper == JSMN_NEG) { + return JSMN_ERROR_UNMATCHED_BRACKETS; + } +#endif token = &tokens[parser->toksuper]; - if (!(token->type & type) || token->end != JSMN_NEG) { - return JSMN_ERROR_INVAL; + if (!(token->type & type) || + token->end != JSMN_NEG) { + return JSMN_ERROR_UNMATCHED_BRACKETS; } token->end = parser->pos + 1; #ifdef JSMN_PARENT_LINKS @@ -775,7 +864,8 @@ jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, } static -jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) { +jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) +{ /* If a COLON wasn't expected; strict check because it is a complex enum */ if (!((parser->expected & JSMN_COLON) == JSMN_COLON)) { return JSMN_ERROR_INVAL; @@ -804,7 +894,8 @@ jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) { } static -jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) { +jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) +{ /* If a COMMA wasn't expected; strict check because it is a complex enum */ if (!((parser->expected & JSMN_COMMA) == JSMN_COMMA)) { return JSMN_ERROR_INVAL; @@ -851,12 +942,18 @@ jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) { JSMN_API jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, - const size_t num_tokens) { + const size_t num_tokens) +{ jsmnint_t r; jsmnint_t count = parser->toknext; char c; for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { +#ifndef JSMN_MULTIPLE_JSON_FAIL + if (parser->expected == JSMN_UNDEFINED) { + break; + } +#endif c = js[parser->pos]; switch (c) { case '{': @@ -899,7 +996,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, case '\n': case '\r': break; -#ifndef JSMN_PERMISSIVE +#ifndef JSMN_PERMISSIVE_PRIMITIVE /* rfc8259: PRIMITIVEs are numbers and booleans */ case '-': case '0': @@ -941,6 +1038,11 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, if (count == 0) { return JSMN_ERROR_INVAL; } + + while (parser->pos < len && isWhitespace(js[parser->pos])) { + parser->pos++; + } + return count; } @@ -949,17 +1051,18 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, * available. */ JSMN_API -void jsmn_init(jsmn_parser *parser) { +void jsmn_init(jsmn_parser *parser) +{ parser->pos = 0; parser->toknext = 0; parser->toksuper = JSMN_NEG; parser->expected = JSMN_ROOT_INIT; } -#endif /* JSMN_HEADER */ - #ifdef __cplusplus } #endif +#endif /* JSMN_HEADER */ + #endif /* JSMN_H */ diff --git a/jsmn_defines.h b/jsmn_defines.h new file mode 100644 index 00000000..09815026 --- /dev/null +++ b/jsmn_defines.h @@ -0,0 +1,137 @@ +#ifndef JSMN_DEFINES +#define JSMN_DEFINES + +/*! + * If nothing is defined, the default definitions are JSMN_PARENT_LINKS and * + * JSMN_NEXT_SIBLING with a jsmntok_t field size of 4 bytes (unsigned int). * + * This will parse one json object in a buffer at a time and return after a * + * successful json object parse. To check if there is more data in the * + * buffer that hasn't been parsed, run jsmn_eof. !*/ + +/*! @def JSMN_PARENT_LINKS + * @brief Adds a parent field to the token + * + * This decreases the initial time required to parse a json buffer and + * simplifies the post-processing of token array by adding a link to the id of + * a token's parent. + * This is enabled by default and highly recommended. + */ + +/*! @def JSMN_NEXT_SIBLING + * @brief Adds a next_sibling field to the token + * + * This simplifies the post-processing of token array by adding a link to the id + * of a token's next sibling. + * This is enabled by default and highly recommended. + */ + +/*! @def JSMN_UTF8 + * @brief Add UTF-8 functionality + * + * This allows for stricter parsing of json strings and also allows for the + * conversion of escaped characters (\uXXXX) to UTF-8 and back. + */ + +/*! @def JSMN_LOW_MEMORY + * @brief Enables defintions that reduce jsmn's memory footprint for small + * devices and doesn't enable definitions that increase it's footprint. + * + * This enables definitions that reduce jsmn's memory footprint at the cost of + * CPU usage. This is useful for small devices that don't parse json objects + * often and have restrictive memory requirements. + */ + +/*! @def JSMN_SHORT_TOKENS + * @brief Changes the tokens field size from a uint32_t to a uint16_t + * + * This reduces the jsmntok_t size by half by changing jsmntok_t field sizes + * from an unsigned int to an unsigned short. NOTE: This reduces the maximum + * possible json string length from 4,294,967,295 to 65,535 minus the size of + * jsmnerr. + */ + +/*! @def JSMN_PERMISSIVE + * @brief Enables all PERMISSIVE definitions + * + * Enables JSMN_PERMISSIVE_KEY, JSMN_PERMISSIVE_PRIMITIVE, and + * JSMN_MULTIPLE_JSON + */ + +/*! @def JSMN_PERMISSIVE_KEY + * @brief Allows PRIMITIVEs to be OBJECT KEYs + */ + +/*! @def JSMN_PERMISSIVE_PRIMITIVE + * @brief Allows PRIMITIVEs to be any contiguous value + * + * This allows PRIMIVITEs to be any contiguous value that does not contain a + * character that has a special meaning to json (`{}[]",:`). NOTE: There is no + * validation of JSMN_PRI_MINUS, JSNM_PRI_DECIMAL, or JSMN_PRI_EXPONENT; + * everything is the base type JSMN_PRIMITIVE. + */ + +/*! @def JSMN_MULTIPLE_JSON + * @brief Allows multiple json objects in a complete buffer + * + * This allows jsmn to parse multiple json objects in a single buffer. + * NOTE: If a single json object is malformed jsmn_parse will return with + * an error. + */ + +/*! @def JSMN_MULTIPLE_JSON_FAIL + * @brief Fails if there is more than one json object in a buffer. + */ + +#ifndef JSMN_API +# ifdef JSMN_STATIC +# define JSMN_API static +# else +# define JSMN_API extern +# endif +#endif + +#ifndef JSMN_LOW_MEMORY + +# ifndef JSMN_PARENT_LINKS +# define JSMN_PARENT_LINKS +# endif +# ifndef JSMN_NEXT_SIBLING +# define JSMN_NEXT_SIBLING +# endif + +#else + +# ifndef JSMN_SHORT_TOKENS +# define JSMN_SHORT_TOKENS +# endif + +#endif + +#ifdef JSMN_PERMISSIVE +# ifndef JSMN_PERMISSIVE_KEY +# define JSMN_PERMISSIVE_KEY +# endif +# ifndef JSMN_PERMISSIVE_PRIMITIVE +# define JSMN_PERMISSIVE_PRIMITIVE +# endif +# ifndef JSMN_MULTIPLE_JSON +# define JSMN_MULTIPLE_JSON +# endif +#endif + +#ifdef JSMN_MULTIPLE_JSON_FAIL +# undef JSMN_MULTIPLE_JSON +#endif + +#if (defined(__linux__) || defined(__APPLE__) || defined(ARDUINO)) +# define JSMN_EXPORT __attribute__((visibility("default"))) +# define JSMN_LOCAL __attribute__((visibility("hidden"))) +#elif (defined(_WIN32)) +# define JSMN_EXPORT __declspec(dllexport) +# define JSMN_LOCAL +#else +# define JSMN_EXPORT +# define JSMN_LOCAL +#endif + +#endif /* JSMN_DEFINES */ diff --git a/jsmn_utils.c b/jsmn_utils.c new file mode 100644 index 00000000..0b228c01 --- /dev/null +++ b/jsmn_utils.c @@ -0,0 +1,299 @@ +#include "jsmn_utils.h" + +#ifdef UNIT_TESTING +#include +#include +#endif + +#include +#include +#include +#include +#include + +JSMN_EXPORT +const char *jsmn_strerror(jsmnerr errno) +{ + switch (errno) { + case JSMN_SUCCESS: + return "*** Success, should not be printing an error. ***"; + case JSMN_ERROR_NOMEM: + return "Not enough tokens were provided."; + case JSMN_ERROR_LEN: + return "Input data too long."; + case JSMN_ERROR_INVAL: + return "Invalid character inside JSON string."; + case JSMN_ERROR_PART: + return "The string is not a full JSON packet, more bytes expected."; + case JSMN_ERROR_UNMATCHED_BRACKETS: + return "The JSON string has unmatched brackets."; + } + + return NULL; +} + +JSMN_EXPORT +jsmntok_t *jsmn_tokenize(const char *json, const size_t json_len, jsmnint_t *rv) +{ + jsmn_parser p; + + jsmn_init(&p); + *rv = jsmn_parse(&p, json, json_len, NULL, 0); + + /* enum jsmnerr has four errors, thus */ + if (*rv >= (jsmnint_t)-5) { + fprintf(stderr, "jsmn_parse error: %s\n", jsmn_strerror(*rv)); + return NULL; + } + +/* fprintf(stderr, "jsmn_parse: %d tokens found.\n", *rv); */ + + jsmntok_t *tokens = calloc(*rv, sizeof(jsmntok_t)); + + jsmn_init(&p); + *rv = jsmn_parse(&p, json, json_len, tokens, *rv); + + return tokens; +} + +JSMN_EXPORT +jsmnint_t jsmn_tokenize_noalloc(jsmntok_t *tokens, const uint32_t num_tokens, const char *json, const size_t json_len) +{ + jsmn_parser p; + jsmn_init(&p); + + jsmnint_t rv; + + rv = jsmn_parse(&p, json, json_len, tokens, num_tokens); + + /* enum jsmnerr has four errors, thus */ + if (rv >= (jsmnint_t)-5) { + fprintf(stderr, "jsmn_parse error: %s\n", jsmn_strerror(rv)); + return rv; + } + +/* fprintf(stderr, "jsmn_parse: %d tokens found.\n", rv); */ + + return rv; +} + +JSMN_EXPORT +int jsmn_streq(const char *json, const jsmntok_t *tok, const char *s) +{ + if ((tok->type & JSMN_STRING) && strlen(s) == tok->end - tok->start && + strncmp(json + tok->start, s, tok->end - tok->start) == 0) { + return JSMN_SUCCESS; + } + return -1; +} + +JSMN_EXPORT +jsmnint_t jsmn_get_prev_sibling(const jsmntok_t *tokens, const jsmnint_t t) +{ +#if defined(JSMN_NEXT_SIBLING) && defined(JSMN_PARENT_LINKS) + jsmnint_t sibling; + + /* Start with parent's first child */ + if (tokens[t].parent == JSMN_NEG) { + return JSMN_NEG; + } + + sibling = tokens[t].parent + 1; + + /* If the first child is the current token */ + if (sibling == t) { + return JSMN_NEG; + } + + /* Loop until we find previous sibling */ + while (tokens[sibling].next_sibling != t) { + sibling = tokens[sibling].next_sibling; + } + + return sibling; +#else + jsmnint_t remaining, sibling = t; + for (remaining = JSMN_NEG; remaining != 1 && sibling != JSMN_NEG; remaining++, sibling--) { + remaining -= tokens[sibling]->size; + } + return sibling; +#endif +} + +JSMN_EXPORT +jsmnint_t jsmn_get_next_sibling(const jsmntok_t *tokens, const jsmnint_t t) +{ +#if defined(JSMN_NEXT_SIBLING) + return tokens[t].next_sibling; +#else + jsmnint_t remaining, sibling = t; + for (remaining = 1; remaining != JSMN_NEG; remaining--, sibling++) { + remaining += tokens[sibling]->size; + } + return sibling; +#endif +} + +static +jsmnint_t jsmn_lookup_object(const char *json, const jsmntok_t *tokens, const jsmnint_t parent, const char *key) +{ + /* first child is the first token after the parent */ + jsmnint_t child = parent + 1; + + /* loop through children */ + while (child != JSMN_NEG) { + /* if child's string is equal to key */ + if (jsmn_streq(json, &tokens[child], key) == JSMN_SUCCESS) { + /* return current child */ + return child; + } + + /* move to the next child */ + child = jsmn_get_next_sibling(tokens, child); + } + + /* key didn't match any of the json keys */ + return JSMN_NEG; +} + +static +jsmnint_t jsmn_lookup_array(const jsmntok_t *tokens, const jsmnint_t parent, const jsmnint_t key) +{ + /* if parent's children is less than or equal to key, key is bad */ + if (tokens[parent].size <= key) + return JSMN_NEG; + + /* first child is the first token after the parent */ + jsmnint_t i, child = parent + 1; + /* loop through children until you reach the nth child */ + for (i = 0; i < key; i++) { + child = jsmn_get_next_sibling(tokens, child); + } + + /* return nth child */ + return child; +} + +JSMN_EXPORT +jsmnint_t jsmn_lookup(const char *json, const jsmntok_t *tokens, const size_t num_keys, ...) +{ + jsmnint_t i, pos; + + /* keys may be either const char * or jsmnint_t, at this point we don't care */ + va_list keys; + va_start(keys, num_keys); + + /* start at position zero */ + pos = 0; + for (i = 0; i < num_keys; i++) { + if (tokens[pos].type & JSMN_OBJECT) { + /* if `pos`.type is an object, treat key as a const char * */ + pos = jsmn_lookup_object(json, tokens, pos, va_arg(keys, void *)); + if (pos == JSMN_NEG) { break; } + /* move position to current key's value (with check) */ + if (tokens[pos].type & JSMN_KEY) { + pos++; + } + } else if (tokens[pos].type & JSMN_ARRAY) { + /* if `pos`.type is an array, treat key as a jsmnint_t (by way of uintptr_t) */ + pos = jsmn_lookup_array(tokens, pos, (uintptr_t)va_arg(keys, void *)); + } else { + /* `pos` must be either an object or array */ + pos = JSMN_NEG; + break; + } + + /* if jsmn_parse_{object,array} returns JSMN_NEG, break */ + if (pos == JSMN_NEG) { + break; + } + } + + va_end(keys); + return pos; +} + +JSMN_EXPORT +void jsmn_explodeJSON(const char *json, const size_t len) +{ + jsmnint_t rv, i, depth; + char c; + + jsmntok_t *tokens = jsmn_tokenize(json, len, &rv); + jsmntok_t *token; + + if (rv >= (jsmnint_t)-4) { + printf("jsmn_parse error: %s\n", jsmn_strerror(rv)); + return; + } + + const char *jsmntype[] = { "", "Object", "Array", "", "String", "", "", "", "Primitive", }; + const char *jsmnextr[] = { "", "Key ", "Value", "", "", "", "", "", "", }; + + printf("\n"); + printf(" Token | Type | Start | End | Length | Children | Parent | Sibling | K/V | \n"); + printf("----------+-----------+----------+----------+----------+----------+----------+----------+-------+-\n"); + for (i = 0, depth = 0; i < rv; i++) { + token = &tokens[i]; + printf( "%9d", i); + printf(" | %9s", jsmntype[token->type & JSMN_ANY_TYPE]); + printf(" | %8d", token->start); + printf(" | %8d", token->end); + printf(" | %8d", token->end - token->start); + printf(" | %8d", token->size); + printf(" | %8d", token->parent != JSMN_NEG ? token->parent : -1); + printf(" | %8d", token->next_sibling != JSMN_NEG ? token->next_sibling : -1); + printf(" | %5s", jsmnextr[(token->type & (JSMN_KEY | JSMN_VALUE)) >> 4]); + printf(" |"); + + if (token->type & JSMN_CONTAINER) { + printf("%*s %s\n", depth << 2, "", token->type & JSMN_OBJECT ? "{" : "["); + depth += 1; + continue; + } + + if (token->type & JSMN_KEY) { + c = (token->type & JSMN_STRING) ? '\"' : ' '; + printf("%*s%c%.*s%c :\n", depth << 2, "", c, token->end - token->start, &json[token->start], c); + continue; + } + + printf("%*s", depth << 2, ""); + if (token->type & JSMN_INSD_OBJ) + printf(" "); + c = (token->type & JSMN_STRING) ? '\"' : ' '; + printf("%c%.*s%c", c, token->end - token->start, &json[token->start], c); + if ((token->type & JSMN_INSD_OBJ && tokens[token->parent].next_sibling != JSMN_NEG) || + token->next_sibling != JSMN_NEG) { + printf(",\n"); + continue; + } + + printf("\n | | | | | | | | Close |"); + + depth -= 1; + if (depth == JSMN_NEG) { + printf(" %c\n", tokens[0].type == JSMN_OBJECT ? '}' : ']'); + continue; + } + + if (tokens[token->parent].type & JSMN_ARRAY) { + printf("%*s ]", depth << 2, ""); + if (tokens[token->parent].parent != 0 && + tokens[tokens[token->parent].parent].next_sibling != JSMN_NEG) { + printf(","); + } + printf("\n"); + } + else if (tokens[tokens[token->parent].parent].type & JSMN_OBJECT) { + printf("%*s }", depth << 2, ""); + if (tokens[tokens[token->parent].parent].parent != JSMN_NEG && + tokens[tokens[tokens[token->parent].parent].parent].next_sibling != JSMN_NEG) { + printf(","); + } + printf("\n"); + } + } + + free(tokens); +} diff --git a/jsmn_utils.h b/jsmn_utils.h new file mode 100644 index 00000000..dbcc0548 --- /dev/null +++ b/jsmn_utils.h @@ -0,0 +1,94 @@ +#ifndef __JSMN_UTILS_H__ +#define __JSMN_UTILS_H__ + +#include +#include + +#include "jsmn_defines.h" + +#include "jsmn.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Return a pointer to jsmn error message + * + * @param[in] errno jsmn error number + * @return const char* jsmn error message + */ +const char *jsmn_strerror(jsmnerr errno); + +/** + * @brief Tokenizes JSON string + * + * @param[in] json JSON String + * @param[in] json_len Length of JSON String + * @param[out] rv Return Value + * @return Allocated jsmntok_t array pointer + */ +jsmntok_t *jsmn_tokenize(const char *json, const size_t json_len, jsmnint_t *rv); + +/** + * @brief Tokenize JSON string + * + * @param[out] tokens Pointer to preallocated Tokens + * @param[in] num_tokens Number of Tokens + * @param[in] json JSON String + * @param[in] json_len Length of JSON String + * @return Return Value + */ +jsmnint_t jsmn_tokenize_noalloc(jsmntok_t *tokens, const uint32_t num_tokens, const char *json, const size_t json_len); + +/** + * @brief String comparison between token and string + * + * @param[in] json JSON String + * @param[in] tok Token to compare + * @param[in] s String to complare + * @return 0 when token string and s are equal, -1 otherwise + */ +int jsmn_streq(const char *json, const jsmntok_t *tok, const char *s); + +/** + * @brief Find the previous sibling of token at position t + * + * @param[in] tokens jsmn tokens + * @param[in] t the position of the token + * @return jsmnint_t the position of t's previous sibling, else JSMN_NEG + */ +jsmnint_t jsmn_get_prev_sibling(const jsmntok_t *tokens, const jsmnint_t t); + +/** + * @brief Find the next sibling of token at position t + * + * @param[in] tokens jsmn tokens + * @param[in] t the position of the token + * @return jsmnint_t the position of t's next sibling, else JSMN_NEG + */ +jsmnint_t jsmn_get_next_sibling(const jsmntok_t *tokens, const jsmnint_t t); + +/** + * @brief Look for a value in a JSON string + * + * @param[in] json json string + * @param[in] tokens jsmn tokens + * @param[in] num_keys number of keys + * @return jsmnint_t position of value requested + */ +jsmnint_t jsmn_lookup(const char *json, const jsmntok_t *tokens, const size_t num_keys, ...); + +/** + * @brief Print an extremely verbose description of JSON string + * + * @param[in] json JSON String + * @param[in] len Length of JSON String + */ +void jsmn_explodeJSON(const char *json, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_UTILS_H__ */ diff --git a/test/tests.c b/test/tests.c index 7fdb0e1a..d824650f 100644 --- a/test/tests.c +++ b/test/tests.c @@ -284,7 +284,11 @@ int test_jsmn_test_suite_n_(void) { check(query("[\"\": 1]", JSMN_ERROR_INVAL)); /* n_array_comma_after_close.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("[\"\"],", JSMN_ERROR_INVAL)); +#else + check(query("[\"\"],", 2)); +#endif /* n_array_comma_and_number.json */ check(query("[,1]", JSMN_ERROR_INVAL)); @@ -296,7 +300,11 @@ int test_jsmn_test_suite_n_(void) { check(query("[\"x\",,]", JSMN_ERROR_INVAL)); /* n_array_extra_close.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("[\"x\"]]", JSMN_ERROR_INVAL)); +#else + check(query("[\"x\"]]", 2)); +#endif /* n_array_extra_comma.json */ check(query("[\"\",]", JSMN_ERROR_INVAL)); @@ -353,13 +361,13 @@ int test_jsmn_test_suite_n_(void) { check(query("[{}", JSMN_ERROR_PART)); /* n_incomplete_false.json */ - check(query("[fals]", JSMN_ERROR_PART)); + check(query("[fals]", JSMN_ERROR_INVAL)); /* n_incomplete_null.json */ - check(query("[nul]", JSMN_ERROR_PART)); + check(query("[nul]", JSMN_ERROR_INVAL)); /* n_incomplete_true.json */ - check(query("[tru]", JSMN_ERROR_PART)); + check(query("[tru]", JSMN_ERROR_INVAL)); /* n_multidigit_number_then_00.json has a null byte in it. */ @@ -574,16 +582,32 @@ int test_jsmn_test_suite_n_(void) { check(query("{\"id\":0,}", JSMN_ERROR_INVAL)); /* n_object_trailing_comment.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("{\"a\":\"b\"}/**/", JSMN_ERROR_INVAL)); +#else + check(query("{\"a\":\"b\"}/**/", 3)); +#endif /* n_object_trailing_comment_open.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("{\"a\":\"b\"}/**//", JSMN_ERROR_INVAL)); +#else + check(query("{\"a\":\"b\"}/**//", 3)); +#endif /* n_object_trailing_comment_slash_open_incomplete.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("{\"a\":\"b\"}/", JSMN_ERROR_INVAL)); +#else + check(query("{\"a\":\"b\"}/", 3)); +#endif /* n_object_trailing_comment_slash_open.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("{\"a\":\"b\"}//", JSMN_ERROR_INVAL)); +#else + check(query("{\"a\":\"b\"}//", 3)); +#endif /* n_object_two_commas_in_a_row.json */ check(query("{\"a\":\"b\",,\"c\":\"d\"}", JSMN_ERROR_INVAL)); @@ -598,7 +622,11 @@ int test_jsmn_test_suite_n_(void) { check(query("{ \"foo\" : \"bar\", \"a\" }", JSMN_ERROR_INVAL)); /* n_object_with_trailing_garbage.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("{\"a\":\"b\"}#", JSMN_ERROR_INVAL)); +#else + check(query("{\"a\":\"b\"}#", 3)); +#endif /* n_single_space.json */ check(query(" ", JSMN_ERROR_INVAL)); @@ -685,7 +713,11 @@ int test_jsmn_test_suite_n_(void) { check(query("\"\\UA66D\"", JSMN_ERROR_INVAL)); /* n_string_with_trailing_garbage.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("\"\"x", JSMN_ERROR_INVAL)); +#else + check(query("\"\"x", 1)); +#endif /* n_structure_angle_bracket_..json */ check(query("<.>", JSMN_ERROR_INVAL)); @@ -694,10 +726,18 @@ int test_jsmn_test_suite_n_(void) { check(query("[]", JSMN_ERROR_INVAL)); /* n_structure_array_trailing_garbage.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("[1]x", JSMN_ERROR_INVAL)); +#else + check(query("[1]x", 2)); +#endif /* n_structure_array_with_extra_array_close.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("[1]]", JSMN_ERROR_INVAL)); +#else + check(query("[1]]", 2)); +#endif /* n_structure_array_with_unclosed_string.json */ check(query("[\"asd]", JSMN_ERROR_PART)); @@ -709,13 +749,23 @@ int test_jsmn_test_suite_n_(void) { check(query("[True]", JSMN_ERROR_INVAL)); /* n_structure_close_unopened_array.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("1]", JSMN_ERROR_INVAL)); +#else + check(query("1]", 1)); +#endif /* n_structure_comma_instead_of_closing_brace.json */ check(query("{\"x\": true,", JSMN_ERROR_PART)); /* n_structure_double_array.json */ +#if defined(JSMN_MULTIPLE_JSON_FAIL) check(query("[][]", JSMN_ERROR_INVAL)); +#elif defined(JSMN_MULTIPLE_JSON) + check(query("[][]", 2)); +#else + check(query("[][]", 1)); +#endif /* n_structure_end_array.json */ check(query("]", JSMN_ERROR_INVAL)); @@ -738,7 +788,11 @@ int test_jsmn_test_suite_n_(void) { check(query("2@", JSMN_ERROR_INVAL)); /* n_structure_object_followed_by_closing_object.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("{}}", JSMN_ERROR_INVAL)); +#else + check(query("{}}", 1)); +#endif /* n_structure_object_unclosed_no_value.json */ check(query("{\"\":", JSMN_ERROR_PART)); @@ -747,7 +801,11 @@ int test_jsmn_test_suite_n_(void) { check(query("{\"a\":/*comment*/\"b\"}", JSMN_ERROR_INVAL)); /* n_structure_object_with_trailing_garbage.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("{\"a\": true} \"x\"", JSMN_ERROR_INVAL)); +#else + check(query("{\"a\": true} \"x\"", 3)); +#endif /* n_structure_open_array_apostrophe.json */ check(query("['", JSMN_ERROR_INVAL)); @@ -767,7 +825,7 @@ int test_jsmn_test_suite_n_(void) { check(query("[\"a\"", JSMN_ERROR_PART)); /* n_structure_open_object_close_array.json */ - check(query("{]", JSMN_ERROR_INVAL)); + check(query("{]", JSMN_ERROR_UNMATCHED_BRACKETS)); /* n_structure_open_object_comma.json */ check(query("{,", JSMN_ERROR_INVAL)); @@ -794,7 +852,11 @@ int test_jsmn_test_suite_n_(void) { check(query("*", JSMN_ERROR_INVAL)); /* n_structure_trailing_#.json */ +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) check(query("{\"a\":\"b\"}#{}", JSMN_ERROR_INVAL)); +#else + check(query("{\"a\":\"b\"}#{}", 3)); +#endif /* n_structure_U+2060_word_joined.json */ check(query("[⁠]", JSMN_ERROR_INVAL)); @@ -1378,15 +1440,11 @@ int test_object(void) { } int test_array(void) { - check(query("[10}", JSMN_ERROR_INVAL)); + check(query("[10}", JSMN_ERROR_UNMATCHED_BRACKETS)); check(query("[1,,3]", JSMN_ERROR_INVAL)); check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10")); - check(query("{\"a\": 1]", JSMN_ERROR_INVAL)); -#ifndef JSMN_PERMISSIVE + check(query("{\"a\": 1]", JSMN_ERROR_UNMATCHED_BRACKETS)); check(query("[\"a\": 1]", JSMN_ERROR_INVAL)); -#else - check(parse("[\"a\": 1]", 3, 3, JSMN_ARRAY, -1, -1, 1, JSMN_STRING, "a", 1, JSMN_PRIMITIVE, "1")); -#endif return 0; } @@ -1430,12 +1488,13 @@ int test_string(void) { int test_partial_string(void) { jsmnint_t r; - size_t i; - jsmn_parser p; jsmntok_t tok[5]; + jsmn_parser p; + jsmn_init(&p); + const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}"; - jsmn_init(&p); + size_t i; for (i = 1; i <= strlen(js); i++) { r = jsmn_parse(&p, js, i, tok, sizeof(tok) / sizeof(tok[0])); if (i == strlen(js)) { @@ -1453,12 +1512,13 @@ int test_partial_string(void) { int test_partial_array(void) { #ifndef JSMN_PERMISSIVE jsmnint_t r; - unsigned long i; - jsmn_parser p; jsmntok_t tok[10]; + jsmn_parser p; + jsmn_init(&p); + const char *js = "[ 1, true, [123, \"hello\"]]"; - jsmn_init(&p); + size_t i; for (i = 1; i <= strlen(js); i++) { r = jsmn_parse(&p, js, i, tok, sizeof(tok) / sizeof(tok[0])); if (i == strlen(js)) { @@ -1475,14 +1535,13 @@ int test_partial_array(void) { } int test_array_nomem(void) { - jsmnint_t i; jsmnint_t r; - jsmn_parser p; jsmntok_t toksmall[10], toklarge[10]; - const char *js; + jsmn_parser p; - js = " [ 1, true, [123, \"hello\"]]"; + const char *js = " [ 1, true, [123, \"hello\"]]"; + size_t i; for (i = 0; i < 6; i++) { jsmn_init(&p); memset(toksmall, 0, sizeof(toksmall)); @@ -1504,12 +1563,11 @@ int test_array_nomem(void) { int test_unquoted_keys(void) { #ifdef JSMN_PERMISSIVE jsmnint_t r; - jsmn_parser p; jsmntok_t tok[10]; - const char *js; - + jsmn_parser p; jsmn_init(&p); - js = "key1: \"value\"\nkey2 : 123"; + + const char *js = "key1: \"value\"\nkey2 : 123"; r = jsmn_parse(&p, js, strlen(js), tok, 10); check(r >= 0); @@ -1538,8 +1596,13 @@ int test_issue_22(void) { int test_issue_27(void) { const char *js = "{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", "; -#if !defined(JSMN_PERMISSIVE) && !defined(JSMN_MULTIPLE_JSON) - check(query(js, JSMN_ERROR_INVAL)); +#ifndef JSMN_MULTIPLE_JSON + check(parse(js, 5, 5, + JSMN_OBJECT, -1, -1, 2, + JSMN_STRING, "name", 1, + JSMN_STRING, "Jack", 0, + JSMN_STRING, "age", 1, + JSMN_PRIMITIVE, "27")); #else check(query(js, JSMN_ERROR_PART)); #endif @@ -1547,14 +1610,13 @@ int test_issue_27(void) { } int test_input_length(void) { - const char *js; jsmnint_t r; - jsmn_parser p; jsmntok_t tokens[10]; + jsmn_parser p; + jsmn_init(&p); - js = "{\"a\": 0}garbage"; + const char *js = "{\"a\": 0}garbage"; - jsmn_init(&p); r = jsmn_parse(&p, js, 8, tokens, 10); check(r == 3); check(tokeq(js, tokens, 3, JSMN_OBJECT, -1, -1, 1, JSMN_STRING, "a", 1, @@ -1563,44 +1625,18 @@ int test_input_length(void) { } int test_count(void) { - const char *js; - - js = "{}"; - check(query(js, 1)); - - js = "[]"; - check(query(js, 1)); - - js = "[[]]"; - check(query(js, 2)); - - js = "[[], []]"; - check(query(js, 3)); - - js = "[[], []]"; - check(query(js, 3)); - - js = "[[], [[]], [[], []]]"; - check(query(js, 7)); - - js = "[\"a\", [[], []]]"; - check(query(js, 5)); - - js = "[[], \"[], [[]]\", [[]]]"; - check(query(js, 5)); - - js = "[1, 2, 3]"; - check(query(js, 4)); - - js = "[1, 2, [3, \"a\"], null]"; - check(query(js, 7)); - - js = "[}"; - check(query(js, JSMN_ERROR_INVAL)); - - js = "{]"; - check(query(js, JSMN_ERROR_INVAL)); - + check(query("{}", 1)); + check(query("[]", 1)); + check(query("[[]]", 2)); + check(query("[[], []]", 3)); + check(query("[[], []]", 3)); + check(query("[[], [[]], [[], []]]", 7)); + check(query("[\"a\", [[], []]]", 5)); + check(query("[[], \"[], [[]]\", [[]]]", 5)); + check(query("[1, 2, 3]", 4)); + check(query("[1, 2, [3, \"a\"], null]", 7)); + check(query("[}", JSMN_ERROR_UNMATCHED_BRACKETS)); + check(query("{]", JSMN_ERROR_UNMATCHED_BRACKETS)); return 0; } @@ -1623,38 +1659,38 @@ int test_nonstrict(void) { } int test_unmatched_brackets(void) { - const char *js; - js = "\"key 1\": 1234}"; - check(query(js, JSMN_ERROR_INVAL)); - js = "{\"key 1\": 1234"; - check(query(js, JSMN_ERROR_PART)); - js = "{\"key 1\": 1234}}"; - check(query(js, JSMN_ERROR_INVAL)); - js = "\"key 1\"}: 1234"; - check(query(js, JSMN_ERROR_INVAL)); - js = "{\"key {1\": 1234}"; - check(parse(js, 3, 3, JSMN_OBJECT, 0, 16, 1, JSMN_STRING, "key {1", 1, +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) + check(query("\"key 1\": 1234}", JSMN_ERROR_INVAL)); +#else + check(parse("\"key 1\": 1234}", 1, 1, JSMN_STRING, "key 1", 0)); +#endif + check(query("{\"key 1\": 1234", JSMN_ERROR_PART)); +#if defined(JSMN_MULTIPLE_JSON) || defined(JSMN_MULTIPLE_JSON_FAIL) + check(query("{\"key 1\": 1234}}", JSMN_ERROR_INVAL)); + check(query("\"key 1\"}: 1234", JSMN_ERROR_INVAL)); +#else + check(parse("{\"key 1\": 1234}}", 3, 3, JSMN_OBJECT, 0, 15, 1, + JSMN_STRING, "key 1", 1, JSMN_PRIMITIVE, "1234")); + check(parse("\"key 1\"}: 1234", 1, 1, JSMN_STRING, "key 1", 0)); +#endif + check(parse("{\"key {1\": 1234}", 3, 3, + JSMN_OBJECT, 0, 16, 1, + JSMN_STRING, "key {1", 1, JSMN_PRIMITIVE, "1234")); - js = "{\"key 1\":{\"key 2\": 1234}"; - check(query(js, JSMN_ERROR_PART)); + check(query("{\"key 1\":{\"key 2\": 1234}", JSMN_ERROR_PART)); return 0; } int test_object_key(void) { - const char *js; - - js = "{\"key\": 1}"; - check(parse(js, 3, 3, JSMN_OBJECT, 0, 10, 1, JSMN_STRING, "key", 1, + check(parse("{\"key\": 1}", 3, 3, + JSMN_OBJECT, 0, 10, 1, + JSMN_STRING, "key", 1, JSMN_PRIMITIVE, "1")); #ifndef JSMN_PERMISSIVE - js = "{true: 1}"; - check(query(js, JSMN_ERROR_INVAL)); - js = "{1: 1}"; - check(query(js, JSMN_ERROR_INVAL)); - js = "{{\"key\": 1}: 2}"; - check(query(js, JSMN_ERROR_INVAL)); - js = "{[1,2]: 2}"; - check(query(js, JSMN_ERROR_INVAL)); + check(query("{true: 1}", JSMN_ERROR_INVAL)); + check(query("{1: 1}", JSMN_ERROR_INVAL)); + check(query("{{\"key\": 1}: 2}", JSMN_ERROR_INVAL)); + check(query("{[1,2]: 2}", JSMN_ERROR_INVAL)); #endif return 0; } From fe07a1577a4ba6d7957af73ca6fb00e99baa58c3 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Sat, 4 Jul 2020 12:09:59 -0500 Subject: [PATCH 18/20] Final revisions. --- README.md | 16 +- jsmn.h | 581 ++++++++++++++++++++++++------------------------- jsmn_defines.h | 60 +++-- jsmn_utils.c | 10 +- jsmn_utils.h | 6 +- test/tests.c | 10 +- 6 files changed, 358 insertions(+), 325 deletions(-) diff --git a/README.md b/README.md index e4d00ce7..ddd0aa7b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ JSMN ==== -[![Build Status](https://travis-ci.org/zserge/jsmn.svg?branch=master)](https://travis-ci.org/zserge/jsmn) +[![pipeline status](https://gitlab.com/themobiusproject/jsmn/badges/master/pipeline.svg)](https://gitlab.com/themobiusproject/jsmn/-/commits/master) [![coverage report](https://gitlab.com/themobiusproject/jsmn/badges/master/coverage.svg)](https://gitlab.com/themobiusproject/jsmn/-/commits/master) jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be easily integrated into resource-limited or embedded projects. @@ -23,7 +23,7 @@ often is an overkill. JSON format itself is extremely simple, so why should we complicate it? -jsmn is designed to be **robust** (it should work fine even with erroneous +jsmn is designed to be **robust** (it should work fine even with erroneous data), **fast** (it should parse data on the fly), **portable** (no superfluous dependencies or non-standard C extensions). And of course, **simplicity** is a key feature - simple code style, simple algorithm, simple integration into @@ -170,12 +170,24 @@ If something goes wrong, you will get an error. Error will be one of these: * `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted * `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large * `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data +* `JSMN_ERROR_LEN` - JSON string is too long (see note) If you get `JSMN_ERROR_NOMEM`, you can re-allocate more tokens and call `jsmn_parse` once more. If you read json data from the stream, you can periodically call `jsmn_parse` and check if return value is `JSMN_ERROR_PART`. You will get this error until you reach the end of JSON data. +**Note:** The amount of input data jsmn can parse is limited by the size of +jsmnint_t. Currently typedefed as an unsigned int. + +Thus follows max len = 2^(sizeof(jsmnint_t)*8) -1 for various int sizes: + +* 16bit ints - 65535 +* 32bit ints - 4294967295 +* 64bit ints - ~1.8e19 + +feed more data into `jsmn_parse` and you will get `JSMN_ERROR_LEN`. + Other info ---------- diff --git a/jsmn.h b/jsmn.h index 95c8b296..2f83a60a 100644 --- a/jsmn.h +++ b/jsmn.h @@ -2,6 +2,7 @@ * MIT License * * Copyright (c) 2010 Serge Zaitsev + * Copyright (c) 2020 Mark Conway (mark.conway@themobiusproject.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,21 +22,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef JSMN_H -#define JSMN_H +#ifndef JSMN_H_ +#define JSMN_H_ #define JSMN_VERSION_MAJOR 2 #define JSMN_VERSION_MINOR 0 #define JSMN_VERSION_PATCH 0 +#if defined(UNIT_TESTING) +#include +#include +#include +#endif + #include +#include #include "jsmn_defines.h" -#ifdef JSMN_SHORT_TOKENS +#if defined(JSMN_SHORT_TOKENS) typedef unsigned short jsmnint_t; +# define JSMNINT_MAX USHRT_MAX #else typedef unsigned int jsmnint_t; +# define JSMNINT_MAX UINT_MAX #endif #define JSMN_NEG ((jsmnint_t)-1) @@ -54,21 +64,19 @@ typedef enum { /* Complex elements */ JSMN_CONTAINER = JSMN_OBJECT | JSMN_ARRAY, -#ifndef JSMN_PERMISSIVE_KEY - JSMN_KEY_TYPE = JSMN_STRING, +#if !defined(JSMN_PERMISSIVE_KEY) + JSMN_KEY_TYPE = JSMN_STRING, #else - JSMN_KEY_TYPE = JSMN_STRING | JSMN_PRIMITIVE, + JSMN_KEY_TYPE = JSMN_STRING | JSMN_PRIMITIVE, #endif - JSMN_ANY_TYPE = JSMN_OBJECT | JSMN_ARRAY | JSMN_STRING | JSMN_PRIMITIVE, + JSMN_VAL_TYPE = JSMN_OBJECT | JSMN_ARRAY | JSMN_STRING | JSMN_PRIMITIVE, JSMN_OBJ_VAL = JSMN_OBJECT | JSMN_VALUE, JSMN_ARR_VAL = JSMN_ARRAY | JSMN_VALUE, JSMN_STR_KEY = JSMN_STRING | JSMN_KEY, JSMN_STR_VAL = JSMN_STRING | JSMN_VALUE, JSMN_PRI_VAL = JSMN_PRIMITIVE | JSMN_VALUE, -#ifdef JSMN_PERMISSIVE_KEY - JSMN_OBJ_KEY = JSMN_OBJECT | JSMN_KEY, - JSMN_ARR_KEY = JSMN_ARRAY | JSMN_KEY, +#if defined(JSMN_PERMISSIVE_KEY) JSMN_PRI_KEY = JSMN_PRIMITIVE | JSMN_KEY, #endif @@ -89,36 +97,44 @@ typedef enum { JSMN_INSD_OBJ = 0x8000, /*!< Inside an OBJECT */ /* Parsing rules */ - JSMN_ROOT_INIT = JSMN_ANY_TYPE | JSMN_VALUE, -#ifndef JSMN_PERMISSIVE -#ifndef JSMN_MULTIPLE_JSON +#if !defined(JSMN_PERMISSIVE_RULESET) + JSMN_ROOT_INIT = JSMN_VAL_TYPE | JSMN_VALUE, +# if !defined(JSMN_MULTIPLE_JSON) JSMN_ROOT = JSMN_UNDEFINED, -#else - JSMN_ROOT = JSMN_ANY_TYPE | JSMN_VALUE, -#endif +# else + JSMN_ROOT = JSMN_VAL_TYPE | JSMN_VALUE, +# endif JSMN_OPEN_OBJECT = JSMN_KEY_TYPE | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, JSMN_AFTR_OBJ_KEY = JSMN_VALUE | JSMN_INSD_OBJ | JSMN_COLON, - JSMN_AFTR_OBJ_VAL = JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, - JSMN_OPEN_ARRAY = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_CLOSE, + JSMN_AFTR_OBJ_VAL = JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, + JSMN_OPEN_ARRAY = JSMN_VAL_TYPE | JSMN_VALUE | JSMN_CLOSE, JSMN_AFTR_ARR_VAL = JSMN_VALUE | JSMN_CLOSE | JSMN_COMMA, JSMN_AFTR_CLOSE = JSMN_CLOSE | JSMN_COMMA, - JSMN_AFTR_COLON = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_INSD_OBJ, + JSMN_AFTR_COLON = JSMN_VAL_TYPE | JSMN_VALUE | JSMN_INSD_OBJ, JSMN_AFTR_COMMA_O = JSMN_KEY_TYPE | JSMN_KEY | JSMN_INSD_OBJ, - JSMN_AFTR_COMMA_A = JSMN_ANY_TYPE | JSMN_VALUE, + JSMN_AFTR_COMMA_A = JSMN_VAL_TYPE | JSMN_VALUE, + #else - JSMN_ROOT = JSMN_ANY_TYPE | JSMN_COLON | JSMN_COMMA, - JSMN_ROOT_AFTR_O = JSMN_ANY_TYPE | JSMN_COMMA, + + JSMN_ROOT_INIT = JSMN_VAL_TYPE | JSMN_VALUE | JSMN_KEY, +# if !defined(JSMN_MULTIPLE_JSON_FAIL) + JSMN_ROOT = JSMN_VAL_TYPE | JSMN_COLON | JSMN_COMMA, + JSMN_ROOT_AFTR_O = JSMN_VAL_TYPE | JSMN_COMMA, +# else + JSMN_ROOT = JSMN_UNDEFINED, + JSMN_ROOT_AFTR_O = JSMN_UNDEFINED, +# endif JSMN_OPEN_OBJECT = JSMN_KEY_TYPE | JSMN_KEY | JSMN_CLOSE | JSMN_INSD_OBJ, JSMN_AFTR_OBJ_KEY = JSMN_VALUE | JSMN_INSD_OBJ | JSMN_COLON, - JSMN_AFTR_OBJ_VAL = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, - JSMN_OPEN_ARRAY = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_CLOSE, - JSMN_AFTR_ARR_VAL = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_COMMA, - JSMN_AFTR_CLOSE = JSMN_ANY_TYPE | JSMN_CLOSE | JSMN_COMMA, - JSMN_AFTR_COLON = JSMN_ANY_TYPE | JSMN_VALUE | JSMN_INSD_OBJ, - JSMN_AFTR_COLON_R = JSMN_ANY_TYPE | JSMN_VALUE, + JSMN_AFTR_OBJ_VAL = JSMN_VAL_TYPE | JSMN_CLOSE | JSMN_INSD_OBJ | JSMN_COMMA, + JSMN_OPEN_ARRAY = JSMN_VAL_TYPE | JSMN_VALUE | JSMN_CLOSE, + JSMN_AFTR_ARR_VAL = JSMN_VAL_TYPE | JSMN_CLOSE | JSMN_COMMA, + JSMN_AFTR_CLOSE = JSMN_VAL_TYPE | JSMN_CLOSE | JSMN_COMMA, + JSMN_AFTR_COLON = JSMN_VAL_TYPE | JSMN_VALUE | JSMN_INSD_OBJ, + JSMN_AFTR_COLON_R = JSMN_VAL_TYPE | JSMN_VALUE, JSMN_AFTR_COMMA_O = JSMN_KEY_TYPE | JSMN_KEY | JSMN_INSD_OBJ, - JSMN_AFTR_COMMA_A = JSMN_ANY_TYPE | JSMN_VALUE, - JSMN_AFTR_COMMA_R = JSMN_ANY_TYPE, + JSMN_AFTR_COMMA_A = JSMN_VAL_TYPE | JSMN_VALUE, + JSMN_AFTR_COMMA_R = JSMN_VAL_TYPE, #endif } jsmntype_t; @@ -126,20 +142,22 @@ typedef enum { * JSMN Error Codes */ typedef enum jsmnerr { - JSMN_SUCCESS = 0, - JSMN_ERROR_NOMEM = -1, /*!< Not enough tokens were provided */ - JSMN_ERROR_LEN = -2, /*!< Input data too long */ - JSMN_ERROR_INVAL = -3, /*!< Invalid character inside JSON string */ - JSMN_ERROR_PART = -4, /*!< The string is not a full JSON packet, more bytes expected */ - JSMN_ERROR_UNMATCHED_BRACKETS = -5, /*!< The JSON string has unmatched brackets */ + JSMN_SUCCESS = 0, + JSMN_ERROR_NOMEM = -1, /*!< Not enough tokens were provided */ + JSMN_ERROR_LENGTH = -2, /*!< Input data too long */ + JSMN_ERROR_INVAL = -3, /*!< Invalid character inside JSON string */ + JSMN_ERROR_PART = -4, /*!< The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_BRACKETS = -5, /*!< The JSON string has unmatched brackets */ + + JSMN_ERROR_MAX = -5, /*!< "MAX" value to be tested against when checking for errors */ } jsmnerr; /*! * JSMN Boolean */ typedef enum jsmnbool { - JSMN_FALSE = 0, - JSMN_TRUE = 1, + JSMN_FALSE = 0, /*!< false */ + JSMN_TRUE = 1, /*!< true */ } jsmnbool; /** @@ -150,10 +168,10 @@ typedef struct jsmntok_t { jsmnint_t start; /*!< start position in JSON data string */ jsmnint_t end; /*!< end position in JSON data string */ jsmnint_t size; /*!< number of children */ -#ifdef JSMN_PARENT_LINKS +#if defined(JSMN_PARENT_LINKS) jsmnint_t parent; /*!< parent id */ #endif -#ifdef JSMN_NEXT_SIBLING +#if defined(JSMN_NEXT_SIBLING) jsmnint_t next_sibling; /*!< next sibling id */ #endif } jsmntok_t; @@ -170,9 +188,14 @@ typedef struct jsmn_parser { /*!< when tokens == NULL, keeps track of container types to a depth of (sizeof(jsmnint_t) * 8) */ jsmnint_t toksuper; /*!< superior token node, e.g. parent object or array */ /*!< when tokens == NULL, toksuper represents container depth */ + jsmnint_t count; /*!< useful to have in the parser when you are continuing a failed parse with NULL tokens */ jsmntype_t expected; /*!< Expected jsmn type(s) */ } jsmn_parser; +#ifdef __cplusplus +extern "C" { +#endif + /** * @brief Create JSON parser over an array of tokens * @@ -199,11 +222,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens); -#ifndef JSMN_HEADER - -#ifdef __cplusplus -extern "C" { -#endif +#if !defined(JSMN_HEADER) /** * Allocates a fresh unused token from the token pool. @@ -216,16 +235,16 @@ jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, return NULL; } - jsmntok_t *tok; - tok = &tokens[parser->toknext++]; + jsmntok_t *tok = &tokens[parser->toknext++]; tok->start = tok->end = JSMN_NEG; tok->size = 0; -#ifdef JSMN_PARENT_LINKS +#if defined(JSMN_PARENT_LINKS) tok->parent = JSMN_NEG; #endif -#ifdef JSMN_NEXT_SIBLING +#if defined(JSMN_NEXT_SIBLING) tok->next_sibling = JSMN_NEG; #endif + parser->count++; return tok; } @@ -242,7 +261,7 @@ void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type, token->size = 0; } -#ifdef JSMN_NEXT_SIBLING +#if defined(JSMN_NEXT_SIBLING) /** * Set previous child's next_sibling to current token */ @@ -296,9 +315,7 @@ jsmnbool isHexadecimal(const char c) static jsmnbool isCharacter(const char c) { - if ((c >= 0x20 && c <= 0x21) || - (c >= 0x23 && c <= 0x5B) || - (c >= 0x5D)) { + if (c >= ' ' && c != '"' && c != '\\') { return JSMN_TRUE; } return JSMN_FALSE; @@ -349,118 +366,114 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } type = JSMN_PRIMITIVE; -#ifndef JSMN_PERMISSIVE_PRIMITIVE - if (js[pos] == 't' || - js[pos] == 'f' || - js[pos] == 'n') { - char *literal = NULL; - jsmnint_t size = 0; - if (js[pos] == 't') { - literal = "true"; - size = 4; - } else if (js[pos] == 'f') { - literal = "false"; - size = 5; - } else if (js[pos] == 'n') { - literal = "null"; - size = 4; +#if !defined(JSMN_PERMISSIVE_PRIMITIVE) +# if !defined(JSMN_PERMISSIVE_LITERALS) + char literal[][6] = { "true", "false", "null" }; +# else + char literal[][9] = { "true", "false", "null", "NaN", "Infinity" }; +# endif + jsmnint_t i; + for (i = 0; i < sizeof(literal) / sizeof(literal[0]); i++) { + if (js[pos] != literal[i][0]) { + continue; } - jsmnint_t i; - for (i = 1, pos++; i < size; i++, pos++) { + jsmnint_t j; + for (j = 1, pos++; literal[i][j] != '\0'; j++, pos++) { if (pos == len || js[pos] == '\0') { return JSMN_ERROR_PART; - } else if (js[pos] != literal[i]) { + } + if (js[pos] != literal[i][j]) { return JSMN_ERROR_INVAL; } } type |= JSMN_PRI_LITERAL; - if (pos == len || - js[pos] == '\0') { + if (pos == len) { goto found; } - } else { - expected = JSMN_PRI_MINUS | JSMN_PRI_INTEGER; - for (; pos < len && js[pos] != '\0'; pos++) { - switch (js[pos]) { - case '0': - if (!(expected & JSMN_PRI_INTEGER)) { - return JSMN_ERROR_INVAL; - } - if (type & JSMN_PRI_EXPONENT) { - expected = JSMN_PRI_INTEGER | JSMN_CLOSE; - } else if (type & JSMN_PRI_DECIMAL) { - expected = JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT | JSMN_CLOSE; - } else if (parser->pos == pos || - (parser->pos + 1 == pos && (type & JSMN_PRI_MINUS))) { - expected = JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; - } else { - expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; - } - break; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (!(expected & JSMN_PRI_INTEGER)) { - return JSMN_ERROR_INVAL; - } - if (type & JSMN_PRI_EXPONENT) { - expected = JSMN_PRI_INTEGER | JSMN_CLOSE; - } else if (type & JSMN_PRI_DECIMAL) { - expected = JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT | JSMN_CLOSE; - } else { - expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; - } - break; - case '-': - if (!(expected & JSMN_PRI_MINUS)) { - return JSMN_ERROR_INVAL; - } - expected = JSMN_PRI_INTEGER; - if (parser->pos == pos) { - type |= JSMN_PRI_MINUS; - } - break; - case '+': - if (!(expected & JSMN_PRI_SIGN)) { - return JSMN_ERROR_INVAL; - } - expected = JSMN_PRI_INTEGER; - break; - case '.': - if (!(expected & JSMN_PRI_DECIMAL)) { - return JSMN_ERROR_INVAL; - } - type |= JSMN_PRI_DECIMAL; - expected = JSMN_PRI_INTEGER; - break; - case 'e': - case 'E': - if (!(expected & JSMN_PRI_EXPONENT)) { - return JSMN_ERROR_INVAL; - } - type |= JSMN_PRI_EXPONENT; - expected = JSMN_PRI_SIGN | JSMN_PRI_INTEGER; - break; - default: - if (!(expected & JSMN_CLOSE)) { - return JSMN_ERROR_INVAL; - } - goto check_primitive_border; + goto check_primitive_border; + } + + expected = JSMN_PRI_MINUS | JSMN_PRI_INTEGER; + for (; pos < len; pos++) { + if (js[pos] == '0') { + if (!(expected & JSMN_PRI_INTEGER)) { + return JSMN_ERROR_INVAL; + } + if (type & JSMN_PRI_EXPONENT) { + expected = JSMN_PRI_INTEGER | JSMN_CLOSE; + } else if (type & JSMN_PRI_DECIMAL) { + expected = JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT | JSMN_CLOSE; + } else if (parser->pos == pos || + (parser->pos + 1 == pos && (type & JSMN_PRI_MINUS))) { + expected = JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; + } else { + expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; + } + continue; + } + + if (js[pos] >= '1' && js[pos] <= '9') { + if (!(expected & JSMN_PRI_INTEGER)) { + return JSMN_ERROR_INVAL; + } + if (type & JSMN_PRI_EXPONENT) { + expected = JSMN_PRI_INTEGER | JSMN_CLOSE; + } else if (type & JSMN_PRI_DECIMAL) { + expected = JSMN_PRI_INTEGER | JSMN_PRI_EXPONENT | JSMN_CLOSE; + } else { + expected = JSMN_PRI_INTEGER | JSMN_PRI_DECIMAL | JSMN_PRI_EXPONENT | JSMN_CLOSE; + } + continue; + } + + if (js[pos] == '-') { + if (!(expected & JSMN_PRI_MINUS)) { + return JSMN_ERROR_INVAL; + } + if (parser->pos == pos) { + type |= JSMN_PRI_MINUS; + } + expected = JSMN_PRI_INTEGER; + continue; + } + + if (js[pos] == '+') { + if (!(expected & JSMN_PRI_SIGN)) { + return JSMN_ERROR_INVAL; + } + expected = JSMN_PRI_INTEGER; + continue; + } + + if (js[pos] == '.') { + if (!(expected & JSMN_PRI_DECIMAL)) { + return JSMN_ERROR_INVAL; + } + type |= JSMN_PRI_DECIMAL; + expected = JSMN_PRI_INTEGER; + continue; + } + + if (js[pos] == 'e' || js[pos] == 'E') { + if (!(expected & JSMN_PRI_EXPONENT)) { + return JSMN_ERROR_INVAL; } + type |= JSMN_PRI_EXPONENT; + expected = JSMN_PRI_SIGN | JSMN_PRI_INTEGER; + continue; } + if (!(expected & JSMN_CLOSE)) { return JSMN_ERROR_INVAL; - } else { - goto found; } + goto check_primitive_border; + } + if (!(expected & JSMN_CLOSE)) { + return JSMN_ERROR_INVAL; } + goto found; + check_primitive_border: switch (js[pos]) { case ' ': @@ -470,35 +483,20 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, case ',': case '}': case ']': + case '\0': goto found; case '"': case ':': case '{': case '[': - return JSMN_ERROR_INVAL; - case '\0': - goto found; default: return JSMN_ERROR_INVAL; } #else for (; pos < len && js[pos] != '\0'; pos++) { - switch (js[pos]) { - case ' ': - case '\t': - case '\n': - case '\r': - case ',': - case '}': - case ']': - case ':': - - case '{': - case '[': - case '"': + if (isWhitespace(js[pos]) || + isSpecialChar(js[pos])) { goto found; - default: /* to quiet a warning from gcc */ - break; } if (!isCharacter(js[pos])) { return JSMN_ERROR_INVAL; @@ -509,20 +507,17 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, found: expected = parser->expected; if (parser->toksuper != JSMN_NEG) { +#if defined(JSMN_PERMISSIVE_KEY) /* OBJECT KEY, strict query */ - if ((parser->expected & (JSMN_KEY | JSMN_INSD_OBJ)) == (JSMN_KEY | JSMN_INSD_OBJ)) { + if ((expected & (JSMN_KEY | JSMN_INSD_OBJ)) == (JSMN_KEY | JSMN_INSD_OBJ)) { parser->expected = JSMN_AFTR_OBJ_KEY; type |= JSMN_KEY | JSMN_INSD_OBJ; + } else +#endif /* OBJECT VALUE, VALUE is implicit */ - } else if (parser->expected & JSMN_INSD_OBJ) { + if (expected & JSMN_INSD_OBJ) { parser->expected = JSMN_AFTR_OBJ_VAL; type |= JSMN_VALUE | JSMN_INSD_OBJ; -#ifdef JSMN_PERMISSIVE - /* OBJECT VALUE at the ROOT level */ - } else if (parser->toksuper == JSMN_NEG) { - parser->expected = JSMN_ROOT_AFTR_O; - type |= JSMN_VALUE; -#endif /* ARRAY VALUE, VALUE is implicit */ } else { parser->expected = JSMN_AFTR_ARR_VAL; @@ -530,15 +525,22 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } } else { parser->expected = JSMN_ROOT; +#if defined(JSMN_PERMISSIVE_RULESET) && defined(JSMN_MULTIPLE_JSON_FAIL) + if (expected & JSMN_KEY) { + parser->expected |= JSMN_COLON; + } +#endif type |= JSMN_VALUE; } - if (pos == len || - js[pos] == '\0') { + if (pos == len) { parser->expected |= JSMN_PRI_CONTINUE; } if (tokens == NULL) { parser->pos = pos - 1; + if (!(expected & JSMN_PRI_CONTINUE)) { + parser->count++; + } return JSMN_SUCCESS; } @@ -555,10 +557,10 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, jsmn_fill_token(token, type, token->start, pos); } parser->pos = pos; -#ifdef JSMN_PARENT_LINKS +#if defined(JSMN_PARENT_LINKS) token->parent = parser->toksuper; #endif -#ifdef JSMN_NEXT_SIBLING +#if defined(JSMN_NEXT_SIBLING) jsmn_next_sibling(parser, tokens); #endif @@ -568,7 +570,7 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, } if (!(tokens[parser->toksuper].type & JSMN_CONTAINER)) { -#ifdef JSMN_PARENT_LINKS +#if defined(JSMN_PARENT_LINKS) parser->toksuper = tokens[parser->toksuper].parent; #else jsmnint_t i; @@ -578,11 +580,11 @@ jsmnint_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, break; } } -# ifdef JSMN_PERMISSIVE +# if defined(JSMN_PERMISSIVE_RULESET) if (i == JSMN_NEG) { parser->toksuper = i; } -# endif +# endif #endif } } @@ -604,12 +606,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, return JSMN_ERROR_INVAL; } - if (len >= JSMN_NEG) { - return JSMN_ERROR_LEN; - } - - jsmnint_t pos; - pos = parser->pos; + jsmnint_t pos = parser->pos; /* Skip starting quote */ pos++; @@ -619,24 +616,18 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, c = js[pos]; /* Quote: end of string */ - if (c == '\"') { + if (c == '"') { jsmntype_t expected = parser->expected; jsmntype_t type; if (parser->toksuper != JSMN_NEG) { /* OBJECT KEY, strict query */ - if ((parser->expected & (JSMN_INSD_OBJ | JSMN_KEY)) == (JSMN_INSD_OBJ | JSMN_KEY)) { + if ((expected & (JSMN_INSD_OBJ | JSMN_KEY)) == (JSMN_INSD_OBJ | JSMN_KEY)) { parser->expected = JSMN_AFTR_OBJ_KEY; type = JSMN_STRING | JSMN_KEY | JSMN_INSD_OBJ; /* OBJECT VALUE, VALUE is implicit */ - } else if (parser->expected & JSMN_INSD_OBJ) { + } else if (expected & JSMN_INSD_OBJ) { parser->expected = JSMN_AFTR_OBJ_VAL; type = JSMN_STRING | JSMN_VALUE | JSMN_INSD_OBJ; -#ifdef JSMN_PERMISSIVE - /* OBJECT VALUE at the ROOT level */ - } else if (parser->toksuper == JSMN_NEG) { - parser->expected = JSMN_ROOT_AFTR_O; - type = JSMN_STRING | JSMN_VALUE; -#endif /* ARRAY VALUE, VALUE is implicit */ } else { parser->expected = JSMN_AFTR_ARR_VAL; @@ -644,26 +635,31 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, } } else { parser->expected = JSMN_ROOT; +#if defined(JSMN_PERMISSIVE_RULESET) && defined(JSMN_MULTIPLE_JSON_FAIL) + if (expected & JSMN_KEY) { + parser->expected |= JSMN_COLON; + } +#endif type = JSMN_STRING | JSMN_VALUE; } if (tokens == NULL) { parser->pos = pos; + parser->count++; return JSMN_SUCCESS; } - jsmntok_t *token; - token = jsmn_alloc_token(parser, tokens, num_tokens); + jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { parser->expected = expected; return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, type, parser->pos + 1, pos); parser->pos = pos; -#ifdef JSMN_PARENT_LINKS +#if defined(JSMN_PARENT_LINKS) token->parent = parser->toksuper; #endif -#ifdef JSMN_NEXT_SIBLING +#if defined(JSMN_NEXT_SIBLING) jsmn_next_sibling(parser, tokens); #endif @@ -671,7 +667,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, tokens[parser->toksuper].size++; if (!(tokens[parser->toksuper].type & JSMN_CONTAINER)) { -#ifdef JSMN_PARENT_LINKS +#if defined(JSMN_PARENT_LINKS) parser->toksuper = tokens[parser->toksuper].parent; #else jsmnint_t i; @@ -681,7 +677,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, break; } } -# ifdef JSMN_PERMISSIVE +# if defined(JSMN_PERMISSIVE_RULESET) if (i == JSMN_NEG) { parser->toksuper = i; } @@ -698,7 +694,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, pos++; switch (js[pos]) { /* Allowed escaped symbols */ - case '\"': + case '"': case '\\': case '/': case 'b': @@ -707,7 +703,7 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, case 'r': case 't': break; - /* Allows escaped symbol \uXXXX */ + /* Allows escaped symbol \uhhhh */ case 'u': pos++; jsmnint_t i; @@ -723,6 +719,23 @@ jsmnint_t jsmn_parse_string(jsmn_parser *parser, const char *js, } pos--; break; +#if defined(JSMN_PERMISSIVE_UTF32) + /* Allows escaped symbol \Uhhhhhhhh */ + case 'U': + pos++; + for (i = pos + 8; pos < i; pos++) { + if (pos == len || + js[pos] == '\0') { + return JSMN_ERROR_PART; + } + /* If it isn't a hex character we have an error */ + if (!isHexadecimal(js[pos])) { + return JSMN_ERROR_INVAL; + } + } + pos--; + break; +#endif /* Unexpected symbol */ default: return JSMN_ERROR_INVAL; @@ -761,10 +774,11 @@ jsmnint_t jsmn_parse_container_open(jsmn_parser *parser, const char c, if (tokens == NULL) { parser->toksuper++; - if (parser->toksuper < (sizeof(jsmnint_t) * 8) && + if (parser->toksuper < (sizeof(jsmnint_t) * CHAR_BIT) && parser->expected & JSMN_INSD_OBJ) { parser->toknext |= (1 << parser->toksuper); } + parser->count++; return JSMN_SUCCESS; } @@ -773,16 +787,15 @@ jsmnint_t jsmn_parse_container_open(jsmn_parser *parser, const char c, type |= JSMN_INSD_OBJ; } - jsmntok_t *token; - token = jsmn_alloc_token(parser, tokens, num_tokens); + jsmntok_t *token = jsmn_alloc_token(parser, tokens, num_tokens); if (token == NULL) { return JSMN_ERROR_NOMEM; } jsmn_fill_token(token, type, parser->pos, JSMN_NEG); -#ifdef JSMN_PARENT_LINKS +#if defined(JSMN_PARENT_LINKS) token->parent = parser->toksuper; #endif -#ifdef JSMN_NEXT_SIBLING +#if defined(JSMN_NEXT_SIBLING) jsmn_next_sibling(parser, tokens); #endif @@ -804,39 +817,33 @@ jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, } if (tokens == NULL) { - if (parser->toksuper < (sizeof(jsmnint_t) * 8)) { - jsmntype_t type; - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); + if (parser->toksuper < (sizeof(jsmnint_t) * CHAR_BIT)) { + jsmntype_t type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); if ((((parser->toknext & (1 << parser->toksuper)) == 1) && !(type & JSMN_OBJECT)) || (((parser->toknext & (1 << parser->toksuper)) == 0) && !(type & JSMN_ARRAY))) { - return JSMN_ERROR_UNMATCHED_BRACKETS; + return JSMN_ERROR_BRACKETS; } parser->toknext &= ~(1 << parser->toksuper); } parser->toksuper--; } else { - jsmntype_t type; - jsmntok_t *token; - - type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); -#ifdef JSMN_PERMISSIVE +#if defined(JSMN_PERMISSIVE_RULESET) if (parser->toksuper == JSMN_NEG) { - return JSMN_ERROR_UNMATCHED_BRACKETS; + return JSMN_ERROR_BRACKETS; } #endif - token = &tokens[parser->toksuper]; + jsmntype_t type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); + jsmntok_t *token = &tokens[parser->toksuper]; + if (!(token->type & type) || token->end != JSMN_NEG) { - return JSMN_ERROR_UNMATCHED_BRACKETS; + return JSMN_ERROR_BRACKETS; } token->end = parser->pos + 1; -#ifdef JSMN_PARENT_LINKS - if (token->type & JSMN_INSD_OBJ) { - if (tokens[token->parent].type & JSMN_CONTAINER) { - parser->toksuper = token->parent; - } else { - parser->toksuper = tokens[token->parent].parent; - } +#if defined(JSMN_PARENT_LINKS) + if (token->type & JSMN_INSD_OBJ && + !(tokens[token->parent].type & JSMN_CONTAINER)) { + parser->toksuper = tokens[token->parent].parent; } else { parser->toksuper = token->parent; } @@ -866,14 +873,14 @@ jsmnint_t jsmn_parse_container_close(jsmn_parser *parser, const char c, static jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) { - /* If a COLON wasn't expected; strict check because it is a complex enum */ - if (!((parser->expected & JSMN_COLON) == JSMN_COLON)) { + /* If a COLON wasn't expected */ + if (!(parser->expected & JSMN_COLON)) { return JSMN_ERROR_INVAL; } if (parser->toksuper != JSMN_NEG) { parser->expected = JSMN_AFTR_COLON; -#ifdef JSMN_PERMISSIVE +#if defined(JSMN_PERMISSIVE_RULESET) } else { parser->expected = JSMN_AFTR_COLON_R; #endif @@ -883,7 +890,7 @@ jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) return JSMN_SUCCESS; } -#ifdef JSMN_PERMISSIVE +#if defined(JSMN_PERMISSIVE_RULESET) tokens[parser->toknext - 1].type &= ~JSMN_VALUE; tokens[parser->toknext - 1].type |= JSMN_KEY; #endif @@ -896,14 +903,14 @@ jsmnint_t jsmn_parse_colon(jsmn_parser *parser, jsmntok_t *tokens) static jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) { - /* If a COMMA wasn't expected; strict check because it is a complex enum */ - if (!((parser->expected & JSMN_COMMA) == JSMN_COMMA)) { + /* If a COMMA wasn't expected */ + if (!(parser->expected & JSMN_COMMA)) { return JSMN_ERROR_INVAL; } jsmntype_t type = JSMN_UNDEFINED; if (tokens == NULL) { - if (parser->toksuper < (sizeof(jsmnint_t) * 8) && + if (parser->toksuper < (sizeof(jsmnint_t) * CHAR_BIT) && parser->toknext & (1 << parser->toksuper)) { type = JSMN_INSD_OBJ; } @@ -919,7 +926,7 @@ jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) } else { parser->expected = JSMN_AFTR_COMMA_A; } -#ifdef JSMN_PERMISSIVE +#if defined(JSMN_PERMISSIVE_RULESET) } else { parser->expected = JSMN_AFTR_COMMA_R; #endif @@ -929,7 +936,7 @@ jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) return JSMN_SUCCESS; } -#ifdef JSMN_PERMISSIVE +#if defined(JSMN_PERMISSIVE_RULESET) tokens[parser->toknext - 1].type |= JSMN_VALUE; #endif @@ -944,98 +951,89 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, jsmntok_t *tokens, const size_t num_tokens) { + if (((jsmnint_t)-1 > 0 && len >= (jsmnint_t)JSMN_ERROR_MAX) || + len > JSMNINT_MAX) { + return JSMN_ERROR_LENGTH; + } + jsmnint_t r; - jsmnint_t count = parser->toknext; - char c; - for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { -#ifndef JSMN_MULTIPLE_JSON_FAIL + for (; parser->pos < len; parser->pos++) { +#if !defined(JSMN_MULTIPLE_JSON_FAIL) if (parser->expected == JSMN_UNDEFINED) { break; } #endif - c = js[parser->pos]; - switch (c) { - case '{': - case '[': + char c = js[parser->pos]; + if (c == '{' || c == '[') { r = jsmn_parse_container_open(parser, c, tokens, num_tokens); if (r != JSMN_SUCCESS) { return r; } - count++; - break; - case '}': - case ']': + continue; + } + + if (c == '}' || c == ']') { r = jsmn_parse_container_close(parser, c, tokens); if (r != JSMN_SUCCESS) { return r; } - break; - case '\"': + continue; + } + + if (c == '"') { r = jsmn_parse_string(parser, js, len, tokens, num_tokens); if (r != JSMN_SUCCESS) { return r; } - count++; - break; - case ':': + continue; + } + + if (c == ':') { r = jsmn_parse_colon(parser, tokens); if (r != JSMN_SUCCESS) { return r; } - break; - case ',': + continue; + } + + if (c == ',') { r = jsmn_parse_comma(parser, tokens); if (r != JSMN_SUCCESS) { return r; } - break; + continue; + } + /* Valid whitespace */ - case ' ': - case '\t': - case '\n': - case '\r': - break; -#ifndef JSMN_PERMISSIVE_PRIMITIVE + if (isWhitespace(c)) { + continue; + } + +#if !defined(JSMN_PERMISSIVE_PRIMITIVE) /* rfc8259: PRIMITIVEs are numbers and booleans */ - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 't': - case 'f': - case 'n': + if (c == '-' || (c >= '0' && c <= '9') || + c == 'n' || c == 't' || c == 'f') { #else /* In permissive mode every unquoted value is a PRIMITIVE */ - default: + if (isCharacter(c) || c == '\\') { #endif r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); if (r != JSMN_SUCCESS) { return r; } - count++; - break; + continue; + } -#ifndef JSMN_PERMISSIVE /* Unexpected char */ - default: - return JSMN_ERROR_INVAL; -#endif - } + return JSMN_ERROR_INVAL; } if (parser->toksuper != JSMN_NEG) { return JSMN_ERROR_PART; } - if (count == 0) { + if (parser->count == 0) { return JSMN_ERROR_INVAL; } @@ -1043,7 +1041,7 @@ jsmnint_t jsmn_parse(jsmn_parser *parser, const char *js, parser->pos++; } - return count; + return parser->count; } /** @@ -1056,13 +1054,14 @@ void jsmn_init(jsmn_parser *parser) parser->pos = 0; parser->toknext = 0; parser->toksuper = JSMN_NEG; + parser->count = 0; parser->expected = JSMN_ROOT_INIT; } +#endif /* JSMN_HEADER */ + #ifdef __cplusplus } #endif -#endif /* JSMN_HEADER */ - -#endif /* JSMN_H */ +#endif /* JSMN_H_ */ diff --git a/jsmn_defines.h b/jsmn_defines.h index 09815026..1bdee4dd 100644 --- a/jsmn_defines.h +++ b/jsmn_defines.h @@ -1,5 +1,5 @@ -#ifndef JSMN_DEFINES -#define JSMN_DEFINES +#ifndef JSMN_DEFINES_H_ +#define JSMN_DEFINES_H_ /*! * If nothing is defined, the default definitions are JSMN_PARENT_LINKS and * @@ -47,14 +47,19 @@ * This reduces the jsmntok_t size by half by changing jsmntok_t field sizes * from an unsigned int to an unsigned short. NOTE: This reduces the maximum * possible json string length from 4,294,967,295 to 65,535 minus the size of - * jsmnerr. + * jsmnerr (JSMN_ERROR_MAX). */ /*! @def JSMN_PERMISSIVE * @brief Enables all PERMISSIVE definitions * - * Enables JSMN_PERMISSIVE_KEY, JSMN_PERMISSIVE_PRIMITIVE, and - * JSMN_MULTIPLE_JSON + * Enables JSMN_PERMISSIVE_RULESET, JSMN_PERMISSIVE_KEY, + * JSMN_PERMISSIVE_PRIMITIVE, JSMN_PERMISSIVE_LITERALS, + * JSMN_PERMISSIVE_UTF32, and JSMN_MULTIPLE_JSON + */ + +/*! @def JSMN_PERMISSIVE_RULESET + * @brief Enables the PERMISSIVE set of rules */ /*! @def JSMN_PERMISSIVE_KEY @@ -66,8 +71,16 @@ * * This allows PRIMIVITEs to be any contiguous value that does not contain a * character that has a special meaning to json (`{}[]",:`). NOTE: There is no - * validation of JSMN_PRI_MINUS, JSNM_PRI_DECIMAL, or JSMN_PRI_EXPONENT; - * everything is the base type JSMN_PRIMITIVE. + * validation of JSMN_PRI_LITERAL, JSMN_PRI_MINUS, JSNM_PRI_DECIMAL, or + * JSMN_PRI_EXPONENT; everything is the base type JSMN_PRIMITIVE. + */ + +/*! @def JSMN_PERMISSIVE_LITERALS + * @brief Extends the PRIMITIVE literals to include 'NaN' and 'Infinity' + */ + +/*! @def JSMN_PERMISSIVE_UTF32 + * @brief Allows escaped characters in the style \Uhhhhhhhh */ /*! @def JSMN_MULTIPLE_JSON @@ -82,44 +95,53 @@ * @brief Fails if there is more than one json object in a buffer. */ -#ifndef JSMN_API -# ifdef JSMN_STATIC +#if !defined(JSMN_API) +# if defined(JSMN_STATIC) # define JSMN_API static # else # define JSMN_API extern # endif #endif -#ifndef JSMN_LOW_MEMORY +#if !defined(JSMN_LOW_MEMORY) -# ifndef JSMN_PARENT_LINKS +# if !defined(JSMN_PARENT_LINKS) # define JSMN_PARENT_LINKS # endif -# ifndef JSMN_NEXT_SIBLING +# if !defined(JSMN_NEXT_SIBLING) # define JSMN_NEXT_SIBLING # endif #else -# ifndef JSMN_SHORT_TOKENS +# if !defined(JSMN_SHORT_TOKENS) # define JSMN_SHORT_TOKENS # endif #endif -#ifdef JSMN_PERMISSIVE -# ifndef JSMN_PERMISSIVE_KEY +#if defined(JSMN_PERMISSIVE) +# if !defined(JSMN_PERMISSIVE_RULESET) +# define JSMN_PERMISSIVE_RULESET +# endif +# if !defined(JSMN_PERMISSIVE_KEY) # define JSMN_PERMISSIVE_KEY # endif -# ifndef JSMN_PERMISSIVE_PRIMITIVE +# if !defined(JSMN_PERMISSIVE_PRIMITIVE) # define JSMN_PERMISSIVE_PRIMITIVE # endif -# ifndef JSMN_MULTIPLE_JSON +# if !defined(JSMN_PERMISSIVE_LITERALS) +# define JSMN_PERMISSIVE_LITERALS +# endif +# if !defined(JSMN_PERMISSIVE_UTF32) +# define JSMN_PERMISSIVE_UTF32 +# endif +# if !defined(JSMN_MULTIPLE_JSON) # define JSMN_MULTIPLE_JSON # endif #endif -#ifdef JSMN_MULTIPLE_JSON_FAIL +#if defined(JSMN_MULTIPLE_JSON_FAIL) # undef JSMN_MULTIPLE_JSON #endif @@ -134,4 +156,4 @@ # define JSMN_LOCAL #endif -#endif /* JSMN_DEFINES */ +#endif /* JSMN_DEFINES_H_ */ diff --git a/jsmn_utils.c b/jsmn_utils.c index 0b228c01..3218b6e9 100644 --- a/jsmn_utils.c +++ b/jsmn_utils.c @@ -19,13 +19,13 @@ const char *jsmn_strerror(jsmnerr errno) return "*** Success, should not be printing an error. ***"; case JSMN_ERROR_NOMEM: return "Not enough tokens were provided."; - case JSMN_ERROR_LEN: + case JSMN_ERROR_LENGTH: return "Input data too long."; case JSMN_ERROR_INVAL: return "Invalid character inside JSON string."; case JSMN_ERROR_PART: return "The string is not a full JSON packet, more bytes expected."; - case JSMN_ERROR_UNMATCHED_BRACKETS: + case JSMN_ERROR_BRACKETS: return "The JSON string has unmatched brackets."; } @@ -41,7 +41,7 @@ jsmntok_t *jsmn_tokenize(const char *json, const size_t json_len, jsmnint_t *rv) *rv = jsmn_parse(&p, json, json_len, NULL, 0); /* enum jsmnerr has four errors, thus */ - if (*rv >= (jsmnint_t)-5) { + if (*rv >= (jsmnint_t)JSMN_ERROR_MAX) { fprintf(stderr, "jsmn_parse error: %s\n", jsmn_strerror(*rv)); return NULL; } @@ -67,7 +67,7 @@ jsmnint_t jsmn_tokenize_noalloc(jsmntok_t *tokens, const uint32_t num_tokens, co rv = jsmn_parse(&p, json, json_len, tokens, num_tokens); /* enum jsmnerr has four errors, thus */ - if (rv >= (jsmnint_t)-5) { + if (rv >= (jsmnint_t)JSMN_ERROR_MAX) { fprintf(stderr, "jsmn_parse error: %s\n", jsmn_strerror(rv)); return rv; } @@ -236,7 +236,7 @@ void jsmn_explodeJSON(const char *json, const size_t len) for (i = 0, depth = 0; i < rv; i++) { token = &tokens[i]; printf( "%9d", i); - printf(" | %9s", jsmntype[token->type & JSMN_ANY_TYPE]); + printf(" | %9s", jsmntype[token->type & JSMN_VAL_TYPE]); printf(" | %8d", token->start); printf(" | %8d", token->end); printf(" | %8d", token->end - token->start); diff --git a/jsmn_utils.h b/jsmn_utils.h index dbcc0548..1a942d2d 100644 --- a/jsmn_utils.h +++ b/jsmn_utils.h @@ -1,5 +1,5 @@ -#ifndef __JSMN_UTILS_H__ -#define __JSMN_UTILS_H__ +#ifndef JSMN_UTILS_H_ +#define JSMN_UTILS_H_ #include #include @@ -91,4 +91,4 @@ void jsmn_explodeJSON(const char *json, size_t len); } #endif -#endif /* __JSMN_UTILS_H__ */ +#endif /* JSMN_UTILS_H_ */ diff --git a/test/tests.c b/test/tests.c index d824650f..eb27ba95 100644 --- a/test/tests.c +++ b/test/tests.c @@ -825,7 +825,7 @@ int test_jsmn_test_suite_n_(void) { check(query("[\"a\"", JSMN_ERROR_PART)); /* n_structure_open_object_close_array.json */ - check(query("{]", JSMN_ERROR_UNMATCHED_BRACKETS)); + check(query("{]", JSMN_ERROR_BRACKETS)); /* n_structure_open_object_comma.json */ check(query("{,", JSMN_ERROR_INVAL)); @@ -1440,10 +1440,10 @@ int test_object(void) { } int test_array(void) { - check(query("[10}", JSMN_ERROR_UNMATCHED_BRACKETS)); + check(query("[10}", JSMN_ERROR_BRACKETS)); check(query("[1,,3]", JSMN_ERROR_INVAL)); check(parse("[10]", 2, 2, JSMN_ARRAY, -1, -1, 1, JSMN_PRIMITIVE, "10")); - check(query("{\"a\": 1]", JSMN_ERROR_UNMATCHED_BRACKETS)); + check(query("{\"a\": 1]", JSMN_ERROR_BRACKETS)); check(query("[\"a\": 1]", JSMN_ERROR_INVAL)); return 0; } @@ -1635,8 +1635,8 @@ int test_count(void) { check(query("[[], \"[], [[]]\", [[]]]", 5)); check(query("[1, 2, 3]", 4)); check(query("[1, 2, [3, \"a\"], null]", 7)); - check(query("[}", JSMN_ERROR_UNMATCHED_BRACKETS)); - check(query("{]", JSMN_ERROR_UNMATCHED_BRACKETS)); + check(query("[}", JSMN_ERROR_BRACKETS)); + check(query("{]", JSMN_ERROR_BRACKETS)); return 0; } From 9f7e9de1c9004b2fde88e714a8db12d70406a62c Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Tue, 8 Sep 2020 16:06:57 -0500 Subject: [PATCH 19/20] Fix {"a":[1,2]} case --- jsmn.h | 6 +++--- test/tests.c | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/jsmn.h b/jsmn.h index 2f83a60a..c0a36b07 100644 --- a/jsmn.h +++ b/jsmn.h @@ -908,11 +908,11 @@ jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) return JSMN_ERROR_INVAL; } - jsmntype_t type = JSMN_UNDEFINED; + jsmntype_t type = JSMN_UNDEFINED; /*!< parent's type */ if (tokens == NULL) { if (parser->toksuper < (sizeof(jsmnint_t) * CHAR_BIT) && parser->toknext & (1 << parser->toksuper)) { - type = JSMN_INSD_OBJ; + type = JSMN_OBJECT; } } else { if (parser->toksuper != JSMN_NEG) { @@ -921,7 +921,7 @@ jsmnint_t jsmn_parse_comma(jsmn_parser *parser, jsmntok_t *tokens) } if (parser->toksuper != JSMN_NEG) { - if (type & (JSMN_OBJECT | JSMN_INSD_OBJ)) { + if (type & JSMN_OBJECT) { parser->expected = JSMN_AFTR_COMMA_O; } else { parser->expected = JSMN_AFTR_COMMA_A; diff --git a/test/tests.c b/test/tests.c index eb27ba95..a299f751 100644 --- a/test/tests.c +++ b/test/tests.c @@ -1403,6 +1403,8 @@ int test_object(void) { JSMN_PRIMITIVE, "0")); check(parse("{\"a\":[]}", 3, 3, JSMN_OBJECT, 0, 8, 1, JSMN_STRING, "a", 1, JSMN_ARRAY, 5, 7, 0)); + check(parse("{\"a\":[1,2]}", 5, 5, JSMN_OBJECT, 0, 11, 1, JSMN_STRING, "a", 1, + JSMN_ARRAY, 5, 10, 2, JSMN_PRIMITIVE, "1", JSMN_PRIMITIVE, "2")); check(parse("{\"a\":{},\"b\":{}}", 5, 5, JSMN_OBJECT, -1, -1, 2, JSMN_STRING, "a", 1, JSMN_OBJECT, -1, -1, 0, JSMN_STRING, "b", 1, JSMN_OBJECT, -1, -1, 0)); From 0560339f28028c3bf6e2e31d120feb6522ea8243 Mon Sep 17 00:00:00 2001 From: Mark Conway Date: Sun, 14 Nov 2021 14:00:35 -0600 Subject: [PATCH 20/20] Add simple.c test to tests/tests.c --- test/tests.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/tests.c b/test/tests.c index a299f751..a39a5236 100644 --- a/test/tests.c +++ b/test/tests.c @@ -1697,6 +1697,24 @@ int test_object_key(void) { return 0; } +int test_simple(void) { + check(parse("{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}", 13, 13, + JSMN_OBJECT, 0, 95, 4, + JSMN_STRING, "user", 1, + JSMN_STRING, "johndoe", 0, + JSMN_STRING, "admin", 1, + JSMN_PRIMITIVE, "false", + JSMN_STRING, "uid", 1, + JSMN_PRIMITIVE, "1000", + JSMN_STRING, "groups", 1, + JSMN_ARRAY, 58, 94, 4, + JSMN_STRING, "users", 0, + JSMN_STRING, "wheel", 0, + JSMN_STRING, "audio", 0, + JSMN_STRING, "video", 0)); + return 0; +} + int main(void) { test(test_empty, "test for a empty JSON objects/arrays"); test(test_object, "test for a JSON objects"); @@ -1715,6 +1733,7 @@ int main(void) { test(test_nonstrict, "test for non-strict mode"); test(test_unmatched_brackets, "test for unmatched brackets"); test(test_object_key, "test for key type"); + test(test_simple, "test for jsmn string from simple.c"); test(test_jsmn_test_suite_i_, "test jsmn test suite implementation"); test(test_jsmn_test_suite_n_, "test jsmn test suite should fail");