]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - json.c
json: Crude (but working) initial implementation of json parser (lexer only for now)
[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 "quakedef.h"
22 #include <setjmp.h>
23
24 // taken from json's wikipedia article
25 const char json_test_string[] =
26 {
27         "{\n"
28         "\t\"firstName\": \"John\",\n"
29         "\t\"lastName\": \"Smith\",\n"
30         "\t\"isAlive\": true,\n"
31         "\t\"age\": 27,\n"
32         "\t\"address\": {\n"
33         "\t\t\"streetAddress\": \"21 2nd Street\",\n"
34         "\t\t\"city\": \"New York\",\n"
35         "\t\t\"state\": \"NY\",\n"
36         "\t\t\"postalCode\": \"10021-3100\"\n"
37         "\t},\n"
38         "\t\"phoneNumbers\": [\n"
39         "\t\t{\n"
40         "\t\t\t\"type\": \"home\",\n"
41         "\t\t\t\"number\": \"212 555-1234\"\n"
42         "\t\t},\n"
43         "\t\t{\n"
44         "\t\t\t\"type\": \"office\",\n"
45         "\t\t\t\"number\": \"646 555-4567\"\n"
46         "\t\t}\n"
47         "\t],\n"
48         "\t\"children\": [],\n"
49         "\t\"spouse\": null\n"
50         "}\n\000"
51 };
52
53 static jmp_buf json_error;
54
55 typedef enum qjson_err_e
56 {
57         JSON_ERR_SUCCESS = 0,
58         JSON_ERR_INVAL = 1,
59         JSON_ERR_EOF = 2,
60         JSON_ERR_EMPTY = 3
61 } qjson_err_t;
62
63 typedef enum qjson_type_e
64 {
65         JSON_TYPE_UNDEFINED = 0,
66         JSON_TYPE_OBJECT = 1,
67         JSON_TYPE_ARRAY = 2,
68         JSON_TYPE_STRING = 3,
69         JSON_TYPE_PRIMITIVE = 4
70 } qjson_type_t;
71
72 typedef struct qjson_token_s
73 {
74         qjson_type_t type;
75         struct qjson_token_s *next; // if an array, next will be a NULL terminated array
76         char *string; // ASCII only for now
77 } qjson_token_t;
78
79 struct qjson_state_s
80 {
81         qjson_token_t *head, *cur;
82         const char *buf;
83         const char *pos;
84         int line, col;
85 };
86
87 static void Json_Parse_Object(struct qjson_state_s *state);
88 static void Json_Parse_Array(struct qjson_state_s *state);
89
90 // Tell the user that their json is broken, why it's broken, and where it's broken, so hopefully they fix it.
91 static void Json_Parse_Error(struct qjson_state_s *state, qjson_err_t error)
92 {
93         if(!error)
94                 return;
95         else
96         { 
97                 switch (error)
98                 {
99                 case JSON_ERR_INVAL:
100                         Con_Printf(CON_ERROR "Json Error: Unexpected token '%c', line %i, column %i\n", *state->pos, state->line, state->col);
101                         break;
102                 case JSON_ERR_EOF:
103                         Con_Printf(CON_ERROR "Json Error: Unexpected end-of-file\n");
104                         break;
105                 default:
106                         return;
107                 }
108         }
109         longjmp(json_error, 1);
110 }
111
112 // Skips newlines, and handles different line endings.
113 static qbool Json_Parse_Newline(struct qjson_state_s *state)
114 {
115         if(*state->pos == '\n')
116                 goto newline;
117         if(*state->pos == '\r')
118         {
119                 if(*state->pos + 1 == '\n')
120                         state->pos++;
121                 goto newline;
122         }
123         return false;
124 newline:
125         state->col = 1;
126         state->line++;
127         state->pos++;
128         return true;
129 }
130
131 // Skips the current line. Only useful for comments.
132 static void Json_Parse_SkipLine(struct qjson_state_s *state)
133 {
134         while(!Json_Parse_Newline(state))
135                 state->pos++;
136 }
137
138 // Checks for C/C++-style comments and ignores them. This is not standard json.
139 static qbool Json_Parse_Comment(struct qjson_state_s *state)
140 {
141         if(*state->pos == '/')
142         {
143                 if(*state->pos++ == '/')
144                         Json_Parse_SkipLine(state);
145                 else if(*state->pos == '*')
146                 {
147                         while(*state->pos++ != '*' && *state->pos + 1 != '/')
148                                 continue;
149                 }
150                 else
151                         Json_Parse_Error(state, JSON_ERR_INVAL);
152                 return true;
153         }
154         return false;
155 }
156
157 // Advance forward in the stream as many times as 'count', cleanly.
158 static void Json_Parse_Next(struct qjson_state_s *state, size_t count)
159 {
160         state->col = state->col + count;
161         state->pos = state->pos + count;
162
163         if(!*state->pos)
164                 Json_Parse_Error(state, JSON_ERR_EOF);
165 }
166
167 // Skip all whitespace, as we normally know it.
168 static void Json_Parse_Whitespace(struct qjson_state_s *state)
169 {
170         while(*state->pos == ' ' || *state->pos == '\t')
171                 Json_Parse_Next(state, 1);
172 }
173
174 // Skip all whitespace, as json defines it.
175 static void Json_Parse_Skip(struct qjson_state_s *state)
176 {
177         /*
178          * Repeat this until we run out of whitespace, newlines, and comments.
179          * state->pos should be left on non-whitespace when this returns.
180          */
181         do {
182                 Json_Parse_Whitespace(state);
183         } while (Json_Parse_Comment(state) || Json_Parse_Newline(state));
184 }
185
186 // Skip to the next token that isn't whitespace. Hopefully a valid one.
187 static char Json_Parse_NextToken(struct qjson_state_s *state)
188 {
189         /*
190          * This assumes state->pos is already on whitespace. Most of the time this
191          * doesn't happen automatically, but advancing the pointer here would break
192          * comment and newline handling when it does happen automatically.
193          */
194         Json_Parse_Skip(state);
195         return *state->pos;
196 }
197
198 // TODO: handle escape sequences
199 static void Json_Parse_String(struct qjson_state_s *state)
200 {
201         do {
202                 Json_Parse_Next(state, 1);
203                 if(*state->pos == '\\')
204                         Json_Parse_Next(state, 1);
205         } while(*state->pos != '"');
206
207         Json_Parse_Next(state, 1);
208 }
209
210 // Handles numbers. Json numbers can be either an integer or a double.
211 static qbool Json_Parse_Number(struct qjson_state_s *state)
212 {
213         int i, numsize;
214         const char *in = state->pos;
215         //char out[128];
216         qbool is_float = false;
217         qbool is_exp = false;
218
219         for(i = 0, numsize = 0; isdigit(in[i]); i++, numsize++)
220         {
221                 //out[numsize] = in[numsize];
222
223                 if(in[i] == '.')
224                 {
225                         if(is_float || is_exp)
226                                 Json_Parse_Error(state, JSON_ERR_INVAL);
227                         is_float = true;
228                         i++;
229                         continue;
230                 }
231
232                 if(in[i] == 'e' || in[i] == 'E')
233                 {
234                         if(is_exp)
235                                 Json_Parse_Error(state, JSON_ERR_INVAL);
236                         if(in[i+1] == '+' || in[i+1] == '-')
237                                 i++;
238                         is_exp = true;
239                         i++;
240                         continue;
241                 }
242         }
243         // TODO: use strtod()
244         Json_Parse_Next(state, i);
245         return true;
246 }
247
248 // Parse a keyword.
249 static qbool Json_Parse_Keyword(struct qjson_state_s *state, const char *keyword)
250 {
251         size_t keyword_size = strlen(keyword);
252         if(!strncmp(keyword, state->pos, keyword_size))
253         {
254                 Json_Parse_Next(state, keyword_size);
255                 return true;
256         }
257         return false;
258 }
259
260 // Parse a value.
261 static void Json_Parse_Value(struct qjson_state_s *state)
262 {
263         Json_Parse_Next(state, 1);
264
265         switch(Json_Parse_NextToken(state))
266         {
267         case '"': // string
268                 Json_Parse_String(state);
269                 break;
270         case '{': // object
271                 Json_Parse_Object(state);
272                 break;
273         case '[': // array
274                 Json_Parse_Array(state);
275                 break;
276         case '-':
277                 Json_Parse_Number(state);
278                 break;
279         default:
280                 if(Json_Parse_Keyword(state, "true"))
281                         break;
282                 if(Json_Parse_Keyword(state, "false"))
283                         break;
284                 if(Json_Parse_Keyword(state, "null"))
285                         break;
286                 if(isdigit(*state->pos))
287                         Json_Parse_Number(state);
288         }
289 }
290
291 // Parse an object.
292 static void Json_Parse_Object(struct qjson_state_s *state)
293 {
294         /*
295          * Json objects are basically a data map; key-value pairs.
296          * They end in a comma or a closing curly brace.
297          */
298         do {
299                 Json_Parse_Next(state, 1);
300
301                 // Parse the key
302                 if(Json_Parse_NextToken(state) == '"')
303                         Json_Parse_String(state);
304                 else
305                         goto fail;
306                 
307                 // And its value
308                 if(Json_Parse_NextToken(state) == ':')
309                         Json_Parse_Value(state);
310                 else
311                         goto fail;
312         } while (Json_Parse_NextToken(state) == ',');
313
314         if(Json_Parse_NextToken(state) == '}')
315                 return;
316 fail:
317         Json_Parse_Error(state, JSON_ERR_INVAL);
318 }
319
320 // Parse an array.
321 static void Json_Parse_Array(struct qjson_state_s *state)
322 {
323         /*
324          * Json arrays are basically lists. They can contain
325          * any value, comma-separated, and end with a closing square bracket.
326          */
327         do {
328                 Json_Parse_Value(state);
329         } while (Json_Parse_NextToken(state) == ',');
330
331         if(Json_Parse_NextToken(state) == ']')
332                 return;
333         else
334                 Json_Parse_Error(state, JSON_ERR_INVAL);
335 }
336
337 // Main function for the parser.
338 qjson_token_t *Json_Parse(const char *data)
339 {
340         struct qjson_state_s state =
341         {
342                 .head = NULL,
343                 .buf = data,
344                 .pos = &data[0],
345                 .line = 1,
346                 .col = 1
347         };
348
349         if(data == NULL)
350         {
351                 Con_Printf(CON_ERROR "Json_Parse: Empty json file\n");
352                 return NULL;
353         }
354
355         if(setjmp(json_error))
356         {
357                 // actually not sure about this
358                 return NULL;
359         }
360
361         if(Json_Parse_NextToken(&state) == '{')
362                 Json_Parse_Object(&state);
363         else
364         {
365                 Con_Printf(CON_ERROR "Json_Parse: Not a json file\n");
366                 return NULL;
367         }
368
369         // Success!
370         // TODO: Actually parse.
371         Con_Printf("Hmm, yes. This json is made of json\n");
372
373         return state.head;
374 }
375
376 void Json_Test_f(cmd_state_t *cmd)
377 {
378         Json_Parse(json_test_string);
379 }