]> git.xonotic.org Git - xonotic/darkplaces.git/blob - json.c
Parser is now tail recursive. Implemented parse tree, self-cleaning on failure
[xonotic/darkplaces.git] / json.c
1 /*
2 Copyright (C) 2021 David Knapp (Cloudwalk)
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "darkplaces.h"
22 #include "parser.h"
23
24 typedef enum qjson_type_e
25 {
26         JSON_TYPE_UNDEFINED = 0,
27         JSON_TYPE_OBJECT = 1,
28         JSON_TYPE_ARRAY = 2,
29         JSON_TYPE_STRING = 3,
30         JSON_TYPE_USTRING = 4,
31         JSON_TYPE_NUMBER = 5,
32         JSON_TYPE_BOOL = 6,
33         JSON_TYPE_NULL = 7
34 } qjson_type_t;
35
36 typedef struct qjson_token_s
37 {
38         qjson_type_t type;
39
40         struct qjson_token_s *parent;
41
42         llist_t list; // Array elements or key-value pairs
43         llist_t clist; // Head of list for children key-value pairs
44
45         char *key;
46         char *string;
47
48         union
49         {
50                 double decimal;
51                 int number;
52         };
53 } qjson_token_t;
54
55 static inline qjson_token_t *Json_Parse_Object(struct qparser_state_s *state, qjson_token_t *, qjson_token_t *);
56 static inline qjson_token_t *Json_Parse_Array(struct qparser_state_s *state, qjson_token_t *, qjson_token_t *);
57 static inline qjson_token_t *Json_Parse_Terminator(struct qparser_state_s *state, qjson_token_t *, qjson_token_t *);
58
59 // Checks for C/C++-style comments and ignores them. This is not standard json.
60 static qbool Json_Parse_Comment_SingleLine(struct qparser_state_s *state)
61 {
62         if(*state->pos == '/')
63         {
64                 // FIXME: Let the parser interface increment this?
65                 if(*state->pos++ == '/')
66                         return true;
67                 else
68                         Parse_Error(state, PARSE_ERR_INVAL, "// or /*");
69         }
70         return false;
71 }
72
73 static qbool Json_Parse_CheckComment_Multiline_Start(struct qparser_state_s *state)
74 {
75         if(*state->pos == '/')
76         {
77                 // FIXME: Let the parser interface increment this?
78                 if(*state->pos++ == '*')
79                         return true;
80                 else
81                         Parse_Error(state, PARSE_ERR_INVAL, "// or /*");
82         }
83         return false;
84 }
85
86 static qbool Json_Parse_CheckComment_Multiline_End(struct qparser_state_s *state)
87 {
88         if(*state->pos == '*')
89         {
90                 // FIXME: Let the parser interface increment this?
91                 if(*state->pos++ == '/')
92                         return true;
93         }
94         return false;
95 }
96
97 static inline qjson_token_t *Json_Parse_NewToken(qparser_state_t *json, qjson_token_t *parent)
98 {
99         qjson_token_t *token;
100         token = (qjson_token_t *)Z_Malloc(sizeof(qjson_token_t));
101         if(parent)
102                 List_Add_Tail(&token->list, &parent->clist);
103         token->parent = parent;
104         return token;
105 }
106
107 static inline char Json_Parse_String_Escape(qparser_state_t *json, char escape)
108 {
109         switch(escape)
110         {
111         case '"':
112         case '\\':
113         case '/':
114                 // These can be returned literally
115                 return escape;
116         case 'b':
117                 return '\b';
118         case 'f':
119                 return '\f';
120         case 'n':
121                 return '\n';
122         case 'r':
123                 return '\r';
124         case 't':
125                 return '\t';
126         default:
127                 Parse_Error(json, PARSE_ERR_INVAL, "a valid escape sequence");
128                 return 0;
129         }
130 }
131
132 static inline qjson_token_t *Json_Parse_String(struct qparser_state_s *json, qjson_token_t *parent, qjson_token_t *token)
133 {
134         int i;
135         const char *start, *end;
136         size_t subtract = 0;
137
138         Parse_Next(json, 1);
139
140         start = json->pos;
141
142         // Get the length
143         while(*json->pos != '"')
144         {
145                 if(*json->pos == '\\')
146                 {
147                         subtract++;
148                         if(json->pos[1] == 'u')
149                         {
150                                 Parse_Error(json, PARSE_ERR_INVAL, "Json Unicode escapes (\\u) are not supported");
151                                 return NULL;
152                         }
153                         Parse_Next(json, 1);
154                 }
155                 Parse_Next(json, 1);
156         }
157
158         end = json->pos;
159
160         if(start != end)
161         {
162                 token->string = (char *)Z_Malloc(((end - start) - subtract));
163
164                 // Actually copy stuff over. 'i' should never exceed end - start.
165                 for(i = 0; start != end; i++, start++)
166                 {
167                         if(*start == '\\')
168                         {
169                                 start++;
170                                 token->string[i] = Json_Parse_String_Escape(json, *start);
171                                 continue;
172                         }
173                         token->string[i] = *start;
174                 }
175         }
176
177         token->type = JSON_TYPE_STRING;
178
179         return Json_Parse_Terminator(json, parent, NULL);
180 }
181
182 // Handles numbers. Json numbers can be either an integer or a double.
183 static inline qjson_token_t *Json_Parse_Number(struct qparser_state_s *json, qjson_token_t *parent, qjson_token_t *token)
184 {
185         const char *lookahead = json->pos;
186
187         // First, figure out where the cursor should end up after atof.
188         // We don't care if the number is valid right now. atof takes care of that.
189         while(isdigit(*lookahead) || *lookahead == '-' || *lookahead == '+' || *lookahead == 'E' || *lookahead == 'e' || *lookahead == '.')
190                 lookahead++;
191
192         token->type = JSON_TYPE_NUMBER;
193         token->decimal = atof(json->pos);
194
195         if(!token->decimal)
196         {
197                 Parse_Error(json, PARSE_ERR_INVAL, "a valid number");
198                 return NULL;
199         }
200
201         Parse_Next(json, (lookahead - json->pos) - 1);
202
203         return Json_Parse_Terminator(json, parent, NULL);
204 }
205
206 static const char *keyword_list[] =
207 {
208         "false",
209         "true",
210         "null",
211         NULL
212 };
213
214 // Parse a keyword.
215 static inline qjson_token_t *Json_Parse_Keyword(struct qparser_state_s *json, qjson_token_t *parent, qjson_token_t *token)
216 {
217         size_t keyword_size;
218
219         for (int i = 0; keyword_list[i]; i++)
220         {
221                 keyword_size = strlen(keyword_list[i]);
222
223                 if(!strncmp(keyword_list[i], json->pos, keyword_size))
224                 {
225                         // Don't advance the entire length of the keyword or we might run into a valid token that'd go missed.
226                         Parse_Next(json, keyword_size - 1);
227                         if(i == 2)
228                                 token->type = JSON_TYPE_NULL;
229                         else
230                         {
231                                 token->type = JSON_TYPE_BOOL;
232                                 token->number = i;
233                         }
234
235                         return Json_Parse_Terminator(json, parent, NULL);
236                 }
237         }
238         Parse_Error(json, PARSE_ERR_INVAL, "true, false, or null");
239         return NULL;
240 }
241
242 static inline qjson_token_t *Json_Parse_Value(qparser_state_t *json, qjson_token_t *parent, qjson_token_t *token)
243 {
244         switch(Parse_NextToken(json, 0))
245         {
246         case '"': // string
247                 return Json_Parse_String(json, parent, token);
248         case '{': // object
249                 return Json_Parse_Object(json, parent, token);
250         case '[': // array
251                 return Json_Parse_Array(json, parent, token);
252         case '-':
253                 return Json_Parse_Number(json, parent, token);
254         case 't': // true
255         case 'f': // false
256         case 'n': // null
257                 return Json_Parse_Keyword(json, parent, token);
258         default:
259                 if(isdigit(*json->pos))
260                         return Json_Parse_Number(json, parent, token);
261         }
262         Parse_Error(json, PARSE_ERR_INVAL, "a value");
263         return NULL;
264 }
265
266 static inline qjson_token_t *Json_Parse_Single(qparser_state_t *json, qjson_token_t *parent, qjson_token_t *token)
267 {
268         // TODO: Handle blank arrays
269
270         token = Json_Parse_NewToken(json, parent);
271         return Json_Parse_Value(json, parent, token);
272 }
273
274 static inline qjson_token_t *Json_Parse_Pair(struct qparser_state_s *json, qjson_token_t *parent, qjson_token_t *token)
275 {
276         const char *start;
277         size_t length = 0;
278
279         Parse_NextToken(json, 0);
280
281         // TODO: Handle blank objects
282
283         start = &json->pos[1];
284
285         while(json->pos[1] != '"')
286         {
287                 Parse_Next(json, 1);
288                 if(ISWHITESPACE(*json->pos))
289                 {
290                         Parse_Error(json, PARSE_ERR_INVAL, "a key without whitespace");
291                         return NULL;
292                 }
293                 length++;
294         }
295
296         if(!length)
297         {
298                 Parse_Error(json, PARSE_ERR_INVAL, "a key");
299                 return NULL;
300         }
301
302         if(Parse_NextToken(json, 1) != ':')
303         {
304                 Parse_Error(json, PARSE_ERR_INVAL, "':'");
305                 return NULL;
306         }
307
308         token = Json_Parse_NewToken(json, parent);
309         token->key = (char *)Z_Malloc(length + 1);
310         memcpy(token->key, start, length);
311
312         return Json_Parse_Value(json, parent, token);
313 }
314
315 static inline qjson_token_t *Json_Parse_Terminator(qparser_state_t *json, qjson_token_t *parent, qjson_token_t *token)
316 {
317         switch(Parse_NextToken(json, 0))
318         {
319         case ']':
320         case '}':
321                 if(!parent->parent)
322                         return parent;
323                 return Json_Parse_Terminator(json, parent->parent, NULL);
324         case ',':
325                 if(parent->type == JSON_TYPE_ARRAY)
326                         return Json_Parse_Single(json, parent, NULL);
327                 else
328                         return Json_Parse_Pair(json, parent, NULL);
329         default:
330                 Parse_Error(json, PARSE_ERR_INVAL, "']', '}', or ','");
331                 return NULL;
332         }
333 }
334
335 // Parse an object.
336 static inline qjson_token_t *Json_Parse_Object(struct qparser_state_s *json, qjson_token_t *parent, qjson_token_t *token)
337 {
338         //Parse_Indent(json);
339
340         /*
341          * Json objects are basically a data map; key-value pairs.
342          * They end in a comma or a closing curly brace.
343          */
344         token->type = JSON_TYPE_OBJECT;
345         List_Create(&token->clist);
346
347         return Json_Parse_Pair(json, token, NULL);
348 }
349
350 // Parse an array.
351 static inline qjson_token_t *Json_Parse_Array(struct qparser_state_s *json, qjson_token_t *parent, qjson_token_t *token)
352 {
353         //Parse_Indent(json);
354
355         /*
356          * Json arrays are basically lists. They can contain
357          * any value, comma-separated, and end with a closing square bracket.
358          */
359         token->type = JSON_TYPE_ARRAY;
360         List_Create(&token->clist);
361
362         return Json_Parse_Single(json, token, NULL);
363 }
364
365 static void Json_Parse_Cleanup(qparser_state_t *json, qjson_token_t *parent, qjson_token_t *token)
366 {
367         qjson_token_t *cur, *next;
368
369         token->type = JSON_TYPE_UNDEFINED;
370
371         List_For_Each_Entry_Safe(cur, next, &token->clist, list)
372         {
373                 if(cur->type == JSON_TYPE_ARRAY || cur->type == JSON_TYPE_BOOL)
374                 {
375                         Json_Parse_Cleanup(json, token, cur);
376                         return;
377                 }
378                 List_Delete(&cur->list);
379                 if(cur->key)
380                         Z_Free(cur->key);
381                 if(cur->string)
382                         Z_Free(cur->string);
383         }
384
385         if(parent)
386                 Json_Parse_Cleanup(json, parent->parent, parent);
387         else
388         {
389                 if(token->key)
390                         Z_Free(token->key);
391                 Z_Free(token);
392         }
393 }
394
395 // Main function for the parser.
396 static qjson_token_t *Json_Parse_Start(qparser_state_t *json)
397 {
398         qjson_token_t *tree = NULL;
399         qjson_token_t *head = NULL;
400
401         json->callback.CheckComment_SingleLine = Json_Parse_Comment_SingleLine;
402         json->callback.CheckComment_Multiline_Start = Json_Parse_CheckComment_Multiline_Start;
403         json->callback.CheckComment_Multiline_End = Json_Parse_CheckComment_Multiline_End;
404
405         if(json->buf == NULL)
406         {
407                 Con_Printf(CON_ERROR "Json_Parse: Empty json file\n");
408                 return NULL;
409         }
410
411         if(setjmp(parse_error))
412         {
413                 // actually not sure about this
414                 Json_Parse_Cleanup(json, NULL, head);
415                 Z_Free(json);
416                 return NULL;
417         }
418
419         head = Json_Parse_NewToken(json, NULL);
420
421         switch(Parse_NextToken(json, 0))
422         {
423         case '{':
424                 tree = Json_Parse_Object(json, NULL, head);
425                 break;
426         case '[':
427                 tree = Json_Parse_Array(json, NULL, head);
428                 break;
429         default:
430                 Con_Printf(CON_ERROR "Json_Parse: Not a json file\n");
431                 break;
432         }
433
434         Z_Free(json);
435         return tree;
436 }
437
438 qjson_token_t *Json_Parse_File(const char *file)
439 {
440         return Json_Parse_Start(Parse_LoadFile(file));
441 }
442
443 qjson_token_t *Json_Parse(const unsigned char *data)
444 {
445         return Json_Parse_Start(Parse_New(data));
446 }
447
448 void Json_Test_f(cmd_state_t *cmd)
449 {
450         qjson_token_t *testing = Json_Parse_File("test.json");
451         if(testing)
452                 Con_Printf("hmm yes this json here is made out of json\n");
453         else
454                 Con_Printf("failure\n");
455 }