Added support for integers and double parsing.
[yacjs.git] / src / yacjs.c
1 #include <stdbool.h>
2 #include <stdio.h> // for debugging
3 #include <stdlib.h>
4 #include <ctype.h>
5
6 #include "yacjs.h"
7 #include "yacjs_dict.h"
8 #include "yacjs_u8s.h"
9
10 struct yacjs_node {
11     enum yacjs_node_type type;
12     union {
13         const char *string;
14         int64_t number;
15         double fp;
16         struct {
17             struct yacjs_node *entries;
18             int entries_count;
19             int entries_size;
20         } array;
21         struct yacjs_dict *dict;
22     } data;
23 };
24
25 enum token_type {
26     TOKEN_STRING,
27     TOKEN_NUMBER,
28     TOKEN_FLOAT,
29     TOKEN_FPNUMBER,
30     TOKEN_OPENDICT,
31     TOKEN_CLOSEDICT,
32     TOKEN_OPENARRAY,
33     TOKEN_CLOSEARRAY,
34     TOKEN_COMMA,
35     TOKEN_COLON,
36     TOKEN_NONE,
37     TOKEN_ERROR,
38     TOKEN_TYPES
39 };
40
41 static enum yacjs_error last_error = YACJS_ERROR_NONE;
42
43 static void destroy_helper(struct yacjs_node *node);
44 static void skip_whitespace(const char ** const ptr);
45 static const char *next_token(const char ** const ptr, int *length,
46     enum token_type *type);
47 static const char *peek_token(const char ** const ptr, int *length,
48     enum token_type *type);
49 static struct yacjs_node *parse_any(const char **string);
50 static struct yacjs_node *parse_dict_contents(const char **string);
51 static struct yacjs_node *parse_array_contents(const char **string);
52
53 enum yacjs_error yacjs_last_error() {
54     enum yacjs_error err = last_error;
55     last_error = YACJS_ERROR_NONE;
56     return err;
57 }
58
59 struct yacjs_node *yacjs_parse(const char *string) {
60     struct yacjs_node *ret = parse_any(&string);
61     // ensure there's nothing afterwards.
62     skip_whitespace(&string);
63     if(string[0] != 0) {
64         yacjs_destroy(ret);
65         last_error = YACJS_ERROR_PARSE;
66         return NULL;
67     }
68     return ret;
69 }
70
71 void yacjs_destroy(struct yacjs_node *node) {
72     if(!node) return;
73     destroy_helper(node);
74     free(node);
75 }
76
77 static void destroy_helper(struct yacjs_node *node) {
78     if(node->type == YACJS_NODE_ARRAY) {
79         for(int i = 0; i < node->data.array.entries_count; i ++) {
80             yacjs_destroy(node->data.array.entries + i);
81         }
82         free(node->data.array.entries);
83     }
84     else if(node->type == YACJS_NODE_DICT) {
85         yacjs_dict_destroy(node->data.dict,
86             (yacjs_dict_visitor)destroy_helper);
87     }
88 }
89
90 enum yacjs_node_type yacjs_node_type(struct yacjs_node *node) {
91     return node->type;
92 }
93
94 const char *yacjs_node_str(struct yacjs_node *node) {
95     if(node->type != YACJS_NODE_STRING) {
96         last_error = YACJS_ERROR_TYPE;
97         return NULL;
98     }
99     return node->data.string;
100 }
101
102 int64_t yacjs_node_num(struct yacjs_node *node) {
103     if(node->type != YACJS_NODE_NUMBER) {
104         last_error = YACJS_ERROR_TYPE;
105         return -1;
106     }
107     return node->data.number;
108 }
109
110 double yacjs_node_float(struct yacjs_node *node) {
111     if(node->type != YACJS_NODE_FLOAT) {
112         last_error = YACJS_ERROR_TYPE;
113         return 1/0.0; // NaN
114     }
115     return node->data.fp;
116 }
117
118 int yacjs_node_array_size(struct yacjs_node *node) {
119     if(node->type != YACJS_NODE_ARRAY) {
120         last_error = YACJS_ERROR_TYPE;
121         return -1;
122     }
123     return node->data.array.entries_count;
124 }
125
126 struct yacjs_node *yacjs_node_array_elem(struct yacjs_node *node, int index) {
127     if(node->type != YACJS_NODE_ARRAY) {
128         last_error = YACJS_ERROR_TYPE;
129         return NULL;
130     }
131     if(index >= node->data.array.entries_count || index < 0) {
132         last_error = YACJS_ERROR_BOUNDS;
133         return NULL;
134         
135     }
136     return node->data.array.entries + index;
137 }
138
139 struct yacjs_node *yacjs_node_dict_get(struct yacjs_node *node,
140     const char *key) {
141
142     if(node->type != YACJS_NODE_DICT) {
143         last_error = YACJS_ERROR_TYPE;
144         return NULL;
145     }
146
147     return yacjs_dict_get(node->data.dict, key);
148 }
149
150 static void skip_whitespace(const char ** const ptr) {
151     while((**ptr == ' ' || **ptr == '\t' || **ptr == '\n') && **ptr != 0) {
152         *ptr = yacjs_u8s_next(*ptr);
153     }
154 }
155
156 static const char *next_token(const char ** const ptr, int *length,
157     enum token_type *type) {
158
159     *length = 0;
160     // skip whitespace
161     skip_whitespace(ptr);
162     if(*ptr == 0) {
163         *type = TOKEN_NONE;
164         return NULL;
165     }
166
167     if(**ptr == '"') {
168         // TODO: parse string PROPERLY
169         const char *start = ++(*ptr);
170         *length = 0;
171         while(**ptr != '"' && **ptr != 0) {
172             (*ptr) ++, (*length) ++;
173         }
174         if(**ptr == 0) return NULL;
175         // skip closing quotes
176         else (*ptr)++;
177
178         *type = TOKEN_STRING;
179         return start;
180     }
181     else if(isdigit(**ptr)) {
182         // TODO: support floating-point etc.
183         const char *start = (*ptr);
184         *length = 0;
185         bool is_fp = false;
186         while((isdigit(**ptr) || **ptr == '.' || **ptr == 'e') && **ptr != 0) {
187             if(**ptr == '.' || **ptr == 'e') is_fp = true;
188             (*ptr) ++, (*length) ++;
189         }
190         if(*ptr == 0) return NULL;
191         if(is_fp) *type = TOKEN_FLOAT;
192         else *type = TOKEN_NUMBER;
193
194         return start;
195     }
196     else if(**ptr == '{') {
197         *type = TOKEN_OPENDICT;
198         return (*ptr)++;
199     }
200     else if(**ptr == '}') {
201         *type = TOKEN_CLOSEDICT;
202         return (*ptr)++;
203     }
204     else if(**ptr == '[') {
205         *type = TOKEN_OPENARRAY;
206         return (*ptr)++;
207     }
208     else if(**ptr == ']') {
209         *type = TOKEN_CLOSEARRAY;
210         return (*ptr)++;
211     }
212     else if(**ptr == ',') {
213         *type = TOKEN_COMMA;
214         return (*ptr)++;
215     }
216     else if(**ptr == ':') {
217         *type = TOKEN_COLON;
218         return (*ptr)++;
219     }
220
221     printf("Don't know what to do with a '%c'\n", **ptr);
222
223     *type = TOKEN_ERROR;
224
225     return NULL;
226 }
227
228 static const char *peek_token(const char ** const ptr, int *length,
229     enum token_type *type) {
230
231     const char *s = *ptr;
232
233     return next_token(&s, length, type);
234 }
235
236 static struct yacjs_node *parse_any(const char **string) {
237     enum token_type type;
238     const char *s;
239     int len;
240
241     s = next_token(string, &len, &type);
242     if(type == TOKEN_OPENDICT) return parse_dict_contents(string);
243     else if(type == TOKEN_OPENARRAY) return parse_array_contents(string);
244     else if(type == TOKEN_STRING) {
245         struct yacjs_node *build = malloc(sizeof(*build));
246         build->type = YACJS_NODE_STRING;
247         build->data.string = yacjs_u8s_strndup(s, len);
248         return build;
249     }
250     else if(type == TOKEN_NUMBER) {
251         struct yacjs_node *build = malloc(sizeof(*build));
252         build->type = YACJS_NODE_NUMBER;
253         build->data.number = strtoll(s, NULL, 0);
254         return build;
255     }
256     else if(type == TOKEN_FLOAT) {
257         struct yacjs_node *build = malloc(sizeof(*build));
258         build->type = YACJS_NODE_FLOAT;
259         char *e;
260         build->data.fp = strtod(s, &e);
261         if(e != *string) {
262             free(build);
263             // error parsing number, not everything was used
264             return NULL;
265         }
266         return build;
267     }
268
269     printf("Unknown token type %i\n", type);
270
271     return NULL;
272 }
273
274 static struct yacjs_node *parse_dict_contents(const char **string) {
275     enum token_type type;
276     const char *s;
277     int len;
278
279     struct yacjs_node *result = malloc(sizeof(*result));
280     result->type = YACJS_NODE_DICT;
281     result->data.dict = yacjs_dict_make();
282
283     while((s = next_token(string, &len, &type))) {
284         // closing dictionary token
285         if(type == TOKEN_CLOSEDICT) break;
286         // we expect a string here if it's not a closing dictionary
287         else if(type != TOKEN_STRING) {
288             return NULL;
289         }
290
291         char *ds = yacjs_u8s_strndup(s, len);
292
293         // expect a colon after the name
294         next_token(string, &len, &type);
295         if(type != TOKEN_COLON) {
296             return NULL;
297         }
298
299         struct yacjs_node *value = parse_any(string);
300
301         if(value == NULL) {
302             // TODO: leaked memory
303             return NULL;
304         }
305
306         yacjs_dict_set(result->data.dict, ds, value);
307         free(ds);
308
309         peek_token(string, &len, &type);
310         if(type == TOKEN_COMMA) {
311             // NOTE: this means things like {"a":1,} are accepted
312             // eat the comma
313             next_token(string, &len, &type);
314         }
315     }
316     if(!s) {
317         // TODO: leaked memory
318         return NULL;
319     }
320
321     return result;
322 }
323
324 static struct yacjs_node *parse_array_contents(const char **string) {
325     enum token_type type;
326     const char *s;
327     int len;
328
329     struct yacjs_node *result = malloc(sizeof(*result));
330     result->type = YACJS_NODE_ARRAY;
331     result->data.array.entries = NULL;
332     result->data.array.entries_size = 0;
333     result->data.array.entries_count = 0;
334
335     while((s = peek_token(string, &len, &type))) {
336         // closing dictionary token
337         if(type == TOKEN_CLOSEARRAY) {
338             // eat first
339             next_token(string, &len, &type);
340             break;
341         }
342         struct yacjs_node *next = parse_any(string);
343         if(!next) {
344             // TODO: leaked memory
345             return NULL;
346         }
347
348         if(result->data.array.entries_size
349             == result->data.array.entries_count) {
350
351             void *nmem = realloc(result->data.array.entries,
352                 sizeof(struct yacjs_node)
353                     * (result->data.array.entries_size * 2 + 1));
354             if(nmem == NULL) {
355                 last_error = YACJS_ERROR_MEMORY;
356                 return NULL;
357             }
358
359             result->data.array.entries_size *= 2;
360             result->data.array.entries_size ++;
361             result->data.array.entries = nmem;
362         }
363         result->data.array.entries[result->data.array.entries_count++] = *next;
364         free(next);
365
366         peek_token(string, &len, &type);
367         if(type == TOKEN_COMMA) {
368             // NOTE: this means things like [1,2,] are accepted
369             // eat the comma
370             next_token(string, &len, &type);
371         }
372     }
373     if(!s) {
374         // TODO: leaked memory
375         return NULL;
376     }
377
378     return result;
379 }