Commit | Line | Data |
---|---|---|
867a979a AK |
1 | /* |
2 | * Copyright (c) 2010 Serge A. Zaitsev | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | * of this software and associated documentation files (the "Software"), to deal | |
6 | * in the Software without restriction, including without limitation the rights | |
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | * copies of the Software, and to permit persons to whom the Software is | |
9 | * furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
20 | * THE SOFTWARE. | |
21 | * | |
22 | * Slightly modified by AK to not assume 0 terminated input. | |
23 | */ | |
24 | ||
25 | #include <stdlib.h> | |
26 | #include "jsmn.h" | |
27 | ||
28 | /* | |
29 | * Allocates a fresh unused token from the token pool. | |
30 | */ | |
31 | static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, | |
32 | jsmntok_t *tokens, size_t num_tokens) | |
33 | { | |
34 | jsmntok_t *tok; | |
35 | ||
36 | if ((unsigned)parser->toknext >= num_tokens) | |
37 | return NULL; | |
38 | tok = &tokens[parser->toknext++]; | |
39 | tok->start = tok->end = -1; | |
40 | tok->size = 0; | |
41 | return tok; | |
42 | } | |
43 | ||
44 | /* | |
45 | * Fills token type and boundaries. | |
46 | */ | |
47 | static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, | |
48 | int start, int end) | |
49 | { | |
50 | token->type = type; | |
51 | token->start = start; | |
52 | token->end = end; | |
53 | token->size = 0; | |
54 | } | |
55 | ||
56 | /* | |
57 | * Fills next available token with JSON primitive. | |
58 | */ | |
59 | static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, | |
60 | size_t len, | |
61 | jsmntok_t *tokens, size_t num_tokens) | |
62 | { | |
63 | jsmntok_t *token; | |
64 | int start; | |
65 | ||
66 | start = parser->pos; | |
67 | ||
68 | for (; parser->pos < len; parser->pos++) { | |
69 | switch (js[parser->pos]) { | |
70 | #ifndef JSMN_STRICT | |
71 | /* | |
72 | * In strict mode primitive must be followed by "," | |
73 | * or "}" or "]" | |
74 | */ | |
75 | case ':': | |
76 | #endif | |
77 | case '\t': | |
78 | case '\r': | |
79 | case '\n': | |
80 | case ' ': | |
81 | case ',': | |
82 | case ']': | |
83 | case '}': | |
84 | goto found; | |
85 | default: | |
86 | break; | |
87 | } | |
88 | if (js[parser->pos] < 32 || js[parser->pos] >= 127) { | |
89 | parser->pos = start; | |
90 | return JSMN_ERROR_INVAL; | |
91 | } | |
92 | } | |
93 | #ifdef JSMN_STRICT | |
94 | /* | |
95 | * In strict mode primitive must be followed by a | |
96 | * comma/object/array. | |
97 | */ | |
98 | parser->pos = start; | |
99 | return JSMN_ERROR_PART; | |
100 | #endif | |
101 | ||
102 | found: | |
103 | token = jsmn_alloc_token(parser, tokens, num_tokens); | |
104 | if (token == NULL) { | |
105 | parser->pos = start; | |
106 | return JSMN_ERROR_NOMEM; | |
107 | } | |
108 | jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); | |
109 | parser->pos--; /* parent sees closing brackets */ | |
110 | return JSMN_SUCCESS; | |
111 | } | |
112 | ||
113 | /* | |
114 | * Fills next token with JSON string. | |
115 | */ | |
116 | static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, | |
117 | size_t len, | |
118 | jsmntok_t *tokens, size_t num_tokens) | |
119 | { | |
120 | jsmntok_t *token; | |
121 | int start = parser->pos; | |
122 | ||
123 | /* Skip starting quote */ | |
124 | parser->pos++; | |
125 | ||
126 | for (; parser->pos < len; parser->pos++) { | |
127 | char c = js[parser->pos]; | |
128 | ||
129 | /* Quote: end of string */ | |
130 | if (c == '\"') { | |
131 | token = jsmn_alloc_token(parser, tokens, num_tokens); | |
132 | if (token == NULL) { | |
133 | parser->pos = start; | |
134 | return JSMN_ERROR_NOMEM; | |
135 | } | |
136 | jsmn_fill_token(token, JSMN_STRING, start+1, | |
137 | parser->pos); | |
138 | return JSMN_SUCCESS; | |
139 | } | |
140 | ||
141 | /* Backslash: Quoted symbol expected */ | |
142 | if (c == '\\') { | |
143 | parser->pos++; | |
144 | switch (js[parser->pos]) { | |
145 | /* Allowed escaped symbols */ | |
146 | case '\"': | |
147 | case '/': | |
148 | case '\\': | |
149 | case 'b': | |
150 | case 'f': | |
151 | case 'r': | |
152 | case 'n': | |
153 | case 't': | |
154 | break; | |
155 | /* Allows escaped symbol \uXXXX */ | |
156 | case 'u': | |
157 | /* TODO */ | |
158 | break; | |
159 | /* Unexpected symbol */ | |
160 | default: | |
161 | parser->pos = start; | |
162 | return JSMN_ERROR_INVAL; | |
163 | } | |
164 | } | |
165 | } | |
166 | parser->pos = start; | |
167 | return JSMN_ERROR_PART; | |
168 | } | |
169 | ||
170 | /* | |
171 | * Parse JSON string and fill tokens. | |
172 | */ | |
173 | jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, | |
174 | jsmntok_t *tokens, unsigned int num_tokens) | |
175 | { | |
176 | jsmnerr_t r; | |
177 | int i; | |
178 | jsmntok_t *token; | |
179 | ||
180 | for (; parser->pos < len; parser->pos++) { | |
181 | char c; | |
182 | jsmntype_t type; | |
183 | ||
184 | c = js[parser->pos]; | |
185 | switch (c) { | |
186 | case '{': | |
187 | case '[': | |
188 | token = jsmn_alloc_token(parser, tokens, num_tokens); | |
189 | if (token == NULL) | |
190 | return JSMN_ERROR_NOMEM; | |
191 | if (parser->toksuper != -1) | |
192 | tokens[parser->toksuper].size++; | |
193 | token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); | |
194 | token->start = parser->pos; | |
195 | parser->toksuper = parser->toknext - 1; | |
196 | break; | |
197 | case '}': | |
198 | case ']': | |
199 | type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); | |
200 | for (i = parser->toknext - 1; i >= 0; i--) { | |
201 | token = &tokens[i]; | |
202 | if (token->start != -1 && token->end == -1) { | |
203 | if (token->type != type) | |
204 | return JSMN_ERROR_INVAL; | |
205 | parser->toksuper = -1; | |
206 | token->end = parser->pos + 1; | |
207 | break; | |
208 | } | |
209 | } | |
210 | /* Error if unmatched closing bracket */ | |
211 | if (i == -1) | |
212 | return JSMN_ERROR_INVAL; | |
213 | for (; i >= 0; i--) { | |
214 | token = &tokens[i]; | |
215 | if (token->start != -1 && token->end == -1) { | |
216 | parser->toksuper = i; | |
217 | break; | |
218 | } | |
219 | } | |
220 | break; | |
221 | case '\"': | |
222 | r = jsmn_parse_string(parser, js, len, tokens, | |
223 | num_tokens); | |
224 | if (r < 0) | |
225 | return r; | |
226 | if (parser->toksuper != -1) | |
227 | tokens[parser->toksuper].size++; | |
228 | break; | |
229 | case '\t': | |
230 | case '\r': | |
231 | case '\n': | |
232 | case ':': | |
233 | case ',': | |
234 | case ' ': | |
235 | break; | |
236 | #ifdef JSMN_STRICT | |
237 | /* | |
238 | * In strict mode primitives are: | |
239 | * numbers and booleans. | |
240 | */ | |
241 | case '-': | |
242 | case '0': | |
243 | case '1': | |
244 | case '2': | |
245 | case '3': | |
246 | case '4': | |
247 | case '5': | |
248 | case '6': | |
249 | case '7': | |
250 | case '8': | |
251 | case '9': | |
252 | case 't': | |
253 | case 'f': | |
254 | case 'n': | |
255 | #else | |
256 | /* | |
257 | * In non-strict mode every unquoted value | |
258 | * is a primitive. | |
259 | */ | |
260 | /*FALL THROUGH */ | |
261 | default: | |
262 | #endif | |
263 | r = jsmn_parse_primitive(parser, js, len, tokens, | |
264 | num_tokens); | |
265 | if (r < 0) | |
266 | return r; | |
267 | if (parser->toksuper != -1) | |
268 | tokens[parser->toksuper].size++; | |
269 | break; | |
270 | ||
271 | #ifdef JSMN_STRICT | |
272 | /* Unexpected char in strict mode */ | |
273 | default: | |
274 | return JSMN_ERROR_INVAL; | |
275 | #endif | |
276 | } | |
277 | } | |
278 | ||
279 | for (i = parser->toknext - 1; i >= 0; i--) { | |
280 | /* Unmatched opened object or array */ | |
281 | if (tokens[i].start != -1 && tokens[i].end == -1) | |
282 | return JSMN_ERROR_PART; | |
283 | } | |
284 | ||
285 | return JSMN_SUCCESS; | |
286 | } | |
287 | ||
288 | /* | |
289 | * Creates a new parser based over a given buffer with an array of tokens | |
290 | * available. | |
291 | */ | |
292 | void jsmn_init(jsmn_parser *parser) | |
293 | { | |
294 | parser->pos = 0; | |
295 | parser->toknext = 0; | |
296 | parser->toksuper = -1; | |
297 | } | |
298 | ||
299 | const char *jsmn_strerror(jsmnerr_t err) | |
300 | { | |
301 | switch (err) { | |
302 | case JSMN_ERROR_NOMEM: | |
303 | return "No enough tokens"; | |
304 | case JSMN_ERROR_INVAL: | |
305 | return "Invalid character inside JSON string"; | |
306 | case JSMN_ERROR_PART: | |
307 | return "The string is not a full JSON packet, more bytes expected"; | |
308 | case JSMN_SUCCESS: | |
309 | return "Success"; | |
310 | default: | |
311 | return "Unknown json error"; | |
312 | } | |
313 | } |