damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

jsmn.h (12822B)


      1 /*
      2  * MIT License
      3  *
      4  * Copyright (c) 2010 Serge Zaitsev
      5  *
      6  * Permission is hereby granted, free of charge, to any person obtaining a copy
      7  * of this software and associated documentation files (the "Software"), to deal
      8  * in the Software without restriction, including without limitation the rights
      9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     10  * copies of the Software, and to permit persons to whom the Software is
     11  * furnished to do so, subject to the following conditions:
     12  *
     13  * The above copyright notice and this permission notice shall be included in
     14  * all copies or substantial portions of the Software.
     15  *
     16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     22  * SOFTWARE.
     23  */
     24 #ifndef JSMN_H
     25 #define JSMN_H
     26 
     27 #include <stddef.h>
     28 
     29 #define JSMN_PARENT_LINKS
     30 #define JSMN_STRICT
     31 
     32 #ifdef __cplusplus
     33 extern "C" {
     34 #endif
     35 
     36 #ifdef JSMN_STATIC
     37 #define JSMN_API static
     38 #else
     39 #define JSMN_API extern
     40 #endif
     41 
     42 /**
     43  * JSON type identifier. Basic types are:
     44  * 	o Object
     45  * 	o Array
     46  * 	o String
     47  * 	o Other primitive: number, boolean (true/false) or null
     48  */
     49 typedef enum {
     50   JSMN_UNDEFINED = 0,
     51   JSMN_OBJECT = 1 << 0,
     52   JSMN_ARRAY = 1 << 1,
     53   JSMN_STRING = 1 << 2,
     54   JSMN_PRIMITIVE = 1 << 3
     55 } jsmntype_t;
     56 
     57 enum jsmnerr {
     58   /* Not enough tokens were provided */
     59   JSMN_ERROR_NOMEM = -1,
     60   /* Invalid character inside JSON string */
     61   JSMN_ERROR_INVAL = -2,
     62   /* The string is not a full JSON packet, more bytes expected */
     63   JSMN_ERROR_PART = -3
     64 };
     65 
     66 /**
     67  * JSON token description.
     68  * type		type (object, array, string etc.)
     69  * start	start position in JSON data string
     70  * end		end position in JSON data string
     71  */
     72 typedef struct jsmntok {
     73   jsmntype_t type;
     74   int start;
     75   int end;
     76   int size;
     77 #ifdef JSMN_PARENT_LINKS
     78   int parent;
     79 #endif
     80 } jsmntok_t;
     81 
     82 /**
     83  * JSON parser. Contains an array of token blocks available. Also stores
     84  * the string being parsed now and current position in that string.
     85  */
     86 typedef struct jsmn_parser {
     87   unsigned int pos;     /* offset in the JSON string */
     88   unsigned int toknext; /* next token to allocate */
     89   int toksuper;         /* superior token node, e.g. parent object or array */
     90 } jsmn_parser;
     91 
     92 /**
     93  * Create JSON parser over an array of tokens
     94  */
     95 JSMN_API void jsmn_init(jsmn_parser *parser);
     96 
     97 /**
     98  * Run JSON parser. It parses a JSON data string into and array of tokens, each
     99  * describing
    100  * a single JSON object.
    101  */
    102 JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
    103                         jsmntok_t *tokens, const unsigned int num_tokens, int stop_at_id);
    104 
    105 #ifndef JSMN_HEADER
    106 /**
    107  * Allocates a fresh unused token from the token pool.
    108  */
    109 static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens,
    110                                    const size_t num_tokens) {
    111   jsmntok_t *tok;
    112   if (parser->toknext >= num_tokens) {
    113     return NULL;
    114   }
    115   tok = &tokens[parser->toknext++];
    116   tok->start = tok->end = -1;
    117   tok->size = 0;
    118 #ifdef JSMN_PARENT_LINKS
    119   tok->parent = -1;
    120 #endif
    121   return tok;
    122 }
    123 
    124 /**
    125  * Fills token type and boundaries.
    126  */
    127 static void jsmn_fill_token(jsmntok_t *token, const jsmntype_t type,
    128                             const int start, const int end) {
    129   token->type = type;
    130   token->start = start;
    131   token->end = end;
    132   token->size = 0;
    133 }
    134 
    135 /**
    136  * Fills next available token with JSON primitive.
    137  */
    138 static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
    139                                 const size_t len, jsmntok_t *tokens,
    140                                 const size_t num_tokens) {
    141   jsmntok_t *token;
    142   int start;
    143 
    144   start = parser->pos;
    145 
    146   for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
    147     switch (js[parser->pos]) {
    148 #ifndef JSMN_STRICT
    149     /* In strict mode primitive must be followed by "," or "}" or "]" */
    150     case ':':
    151 #endif
    152     case '\t':
    153     case '\r':
    154     case '\n':
    155     case ' ':
    156     case ',':
    157     case ']':
    158     case '}':
    159       goto found;
    160     default:
    161                    /* to quiet a warning from gcc*/
    162       break;
    163     }
    164     if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
    165       parser->pos = start;
    166       return JSMN_ERROR_INVAL;
    167     }
    168   }
    169 #ifdef JSMN_STRICT
    170   /* In strict mode primitive must be followed by a comma/object/array */
    171   parser->pos = start;
    172   return JSMN_ERROR_PART;
    173 #endif
    174 
    175 found:
    176   if (tokens == NULL) {
    177     parser->pos--;
    178     return 0;
    179   }
    180   token = jsmn_alloc_token(parser, tokens, num_tokens);
    181   if (token == NULL) {
    182     parser->pos = start;
    183     return JSMN_ERROR_NOMEM;
    184   }
    185   jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
    186 #ifdef JSMN_PARENT_LINKS
    187   token->parent = parser->toksuper;
    188 #endif
    189   parser->pos--;
    190   return 0;
    191 }
    192 
    193 /**
    194  * Fills next token with JSON string.
    195  */
    196 static int jsmn_parse_string(jsmn_parser *parser, const char *js,
    197                              const size_t len, jsmntok_t *tokens,
    198                              const size_t num_tokens) {
    199   jsmntok_t *token;
    200 
    201   int start = parser->pos;
    202   
    203   /* Skip starting quote */
    204   parser->pos++;
    205   
    206   for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
    207     char c = js[parser->pos];
    208 
    209     /* Quote: end of string */
    210     if (c == '\"') {
    211       if (tokens == NULL) {
    212         return 0;
    213       }
    214       token = jsmn_alloc_token(parser, tokens, num_tokens);
    215       if (token == NULL) {
    216         parser->pos = start;
    217         return JSMN_ERROR_NOMEM;
    218       }
    219       jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
    220 #ifdef JSMN_PARENT_LINKS
    221       token->parent = parser->toksuper;
    222 #endif
    223       return 0;
    224     }
    225 
    226     /* Backslash: Quoted symbol expected */
    227     if (c == '\\' && parser->pos + 1 < len) {
    228       int i;
    229       parser->pos++;
    230       switch (js[parser->pos]) {
    231       /* Allowed escaped symbols */
    232       case '\"':
    233       case '/':
    234       case '\\':
    235       case 'b':
    236       case 'f':
    237       case 'r':
    238       case 'n':
    239       case 't':
    240         break;
    241       /* Allows escaped symbol \uXXXX */
    242       case 'u':
    243         parser->pos++;
    244         for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0';
    245              i++) {
    246           /* If it isn't a hex character we have an error */
    247           if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) ||   /* 0-9 */
    248                 (js[parser->pos] >= 65 && js[parser->pos] <= 70) ||   /* A-F */
    249                 (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
    250             parser->pos = start;
    251             return JSMN_ERROR_INVAL;
    252           }
    253           parser->pos++;
    254         }
    255         parser->pos--;
    256         break;
    257       /* Unexpected symbol */
    258       default:
    259         parser->pos = start;
    260         return JSMN_ERROR_INVAL;
    261       }
    262     }
    263   }
    264   parser->pos = start;
    265   return JSMN_ERROR_PART;
    266 }
    267 
    268 /**
    269  * Parse JSON string and fill tokens.
    270  */
    271 JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len,
    272                         jsmntok_t *tokens, const unsigned int num_tokens, int stop_at_id) {
    273   int r, i, idkey;
    274   jsmntok_t *token;
    275   int count = parser->toknext;
    276 
    277   idkey = 0;
    278 
    279   for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
    280     char c;
    281     jsmntype_t type;
    282 
    283     c = js[parser->pos];
    284     switch (c) {
    285     case '{':
    286     case '[':
    287       count++;
    288       if (tokens == NULL) {
    289         break;
    290       }
    291       token = jsmn_alloc_token(parser, tokens, num_tokens);
    292       if (token == NULL) {
    293         return JSMN_ERROR_NOMEM;
    294       }
    295       if (parser->toksuper != -1) {
    296         jsmntok_t *t = &tokens[parser->toksuper];
    297 #ifdef JSMN_STRICT
    298         /* In strict mode an object or array can't become a key */
    299         if (t->type == JSMN_OBJECT) {
    300           return JSMN_ERROR_INVAL;
    301         }
    302 #endif
    303         t->size++;
    304 #ifdef JSMN_PARENT_LINKS
    305         token->parent = parser->toksuper;
    306 #endif
    307       }
    308       token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
    309       token->start = parser->pos;
    310       parser->toksuper = parser->toknext - 1;
    311       break;
    312     case '}':
    313     case ']':
    314       if (tokens == NULL) {
    315         break;
    316       }
    317       type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
    318 #ifdef JSMN_PARENT_LINKS
    319       if (parser->toknext < 1) {
    320         return JSMN_ERROR_INVAL;
    321       }
    322       token = &tokens[parser->toknext - 1];
    323       for (;;) {
    324         if (token->start != -1 && token->end == -1) {
    325           if (token->type != type) {
    326             return JSMN_ERROR_INVAL;
    327           }
    328           token->end = parser->pos + 1;
    329           parser->toksuper = token->parent;
    330           break;
    331         }
    332         if (token->parent == -1) {
    333           if (token->type != type || parser->toksuper == -1) {
    334             return JSMN_ERROR_INVAL;
    335           }
    336           break;
    337         }
    338         token = &tokens[token->parent];
    339       }
    340 #else
    341       for (i = parser->toknext - 1; i >= 0; i--) {
    342         token = &tokens[i];
    343         if (token->start != -1 && token->end == -1) {
    344           if (token->type != type) {
    345             return JSMN_ERROR_INVAL;
    346           }
    347           parser->toksuper = -1;
    348           token->end = parser->pos + 1;
    349           break;
    350         }
    351       }
    352       /* Error if unmatched closing bracket */
    353       if (i == -1) {
    354         return JSMN_ERROR_INVAL;
    355       }
    356       for (; i >= 0; i--) {
    357         token = &tokens[i];
    358         if (token->start != -1 && token->end == -1) {
    359           parser->toksuper = i;
    360           break;
    361         }
    362       }
    363 #endif
    364       break;
    365     case '\"':
    366       r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
    367       if (r < 0) {
    368         return r;
    369       }
    370       count++;
    371       if (parser->toksuper != -1 && tokens != NULL) {
    372         tokens[parser->toksuper].size++;
    373       }
    374 
    375       // big hack. resumable parsing when encountering the id field
    376       if (stop_at_id) {
    377         token = &tokens[parser->toknext-1];
    378         if (idkey == 1 && (token->end - token->start) == 64) {
    379           //printf("jsmn: found id '%.*s'\n", token->end - token->start, js + token->start);
    380           parser->pos++;
    381           return -42;
    382         } else if (idkey == 0 && (token->end - token->start) == 2 &&
    383                    (js + token->start)[0] == 'i' &&
    384 		   (js + token->start)[1] == 'd') {
    385           //printf("jsmn: found id key\n");
    386           idkey = 1;
    387         }
    388       }
    389 
    390       break;
    391     case '\t':
    392     case '\r':
    393     case '\n':
    394     case ' ':
    395       break;
    396     case ':':
    397       parser->toksuper = parser->toknext - 1;
    398       break;
    399     case ',':
    400       if (tokens != NULL && parser->toksuper != -1 &&
    401           tokens[parser->toksuper].type != JSMN_ARRAY &&
    402           tokens[parser->toksuper].type != JSMN_OBJECT) {
    403 #ifdef JSMN_PARENT_LINKS
    404         parser->toksuper = tokens[parser->toksuper].parent;
    405 #else
    406         for (i = parser->toknext - 1; i >= 0; i--) {
    407           if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
    408             if (tokens[i].start != -1 && tokens[i].end == -1) {
    409               parser->toksuper = i;
    410               break;
    411             }
    412           }
    413         }
    414 #endif
    415       }
    416       break;
    417 #ifdef JSMN_STRICT
    418     /* In strict mode primitives are: numbers and booleans */
    419     case '-':
    420     case '0':
    421     case '1':
    422     case '2':
    423     case '3':
    424     case '4':
    425     case '5':
    426     case '6':
    427     case '7':
    428     case '8':
    429     case '9':
    430     case 't':
    431     case 'f':
    432     case 'n':
    433       /* And they must not be keys of the object */
    434       if (tokens != NULL && parser->toksuper != -1) {
    435         const jsmntok_t *t = &tokens[parser->toksuper];
    436         if (t->type == JSMN_OBJECT ||
    437             (t->type == JSMN_STRING && t->size != 0)) {
    438           return JSMN_ERROR_INVAL;
    439         }
    440       }
    441 #else
    442     /* In non-strict mode every unquoted value is a primitive */
    443     default:
    444 #endif
    445       r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
    446       if (r < 0) {
    447         return r;
    448       }
    449       count++;
    450       if (parser->toksuper != -1 && tokens != NULL) {
    451         tokens[parser->toksuper].size++;
    452       }
    453       break;
    454 
    455 #ifdef JSMN_STRICT
    456     /* Unexpected char in strict mode */
    457     default:
    458       return JSMN_ERROR_INVAL;
    459 #endif
    460     }
    461   }
    462 
    463   if (tokens != NULL) {
    464     for (i = parser->toknext - 1; i >= 0; i--) {
    465       /* Unmatched opened object or array */
    466       if (tokens[i].start != -1 && tokens[i].end == -1) {
    467         return JSMN_ERROR_PART;
    468       }
    469     }
    470   }
    471 
    472   return count;
    473 }
    474 
    475 /**
    476  * Creates a new parser based over a given buffer with an array of tokens
    477  * available.
    478  */
    479 JSMN_API void jsmn_init(jsmn_parser *parser) {
    480   parser->pos = 0;
    481   parser->toknext = 0;
    482   parser->toksuper = -1;
    483 }
    484 
    485 #endif /* JSMN_HEADER */
    486 
    487 #ifdef __cplusplus
    488 }
    489 #endif
    490 
    491 #endif /* JSMN_H */