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