add yaml parser!
[protos/libecoli.git] / lib / parse-yaml.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdint.h>
5 #include <errno.h>
6 #include <limits.h>
7 #include <assert.h>
8
9 #include <yaml.h>
10 #include <ecoli_node.h>
11 #include <ecoli_config.h>
12
13 /* associate a yaml node to a ecoli node */
14 struct pair {
15         const yaml_node_t *ynode;
16         struct ec_node *enode;
17 };
18
19 /* store the ecoli node tree and the associations yaml_node <-> ec_node */
20 struct enode_tree {
21         struct ec_node *root;
22         struct pair *table;
23         size_t table_len;
24 };
25
26 static struct ec_node *
27 parse_ec_node(struct enode_tree *tree,
28         const yaml_document_t *document, const yaml_node_t *ynode);
29
30 static struct ec_config *
31 parse_ec_config_list(struct enode_tree *tree,
32                 const struct ec_config_schema *schema,
33                 const yaml_document_t *document, const yaml_node_t *ynode);
34
35 static struct ec_config *
36 parse_ec_config_dict(struct enode_tree *tree,
37                 const struct ec_config_schema *schema,
38                 const yaml_document_t *document, const yaml_node_t *ynode);
39
40 /* XXX to utils.c ? */
41 static int
42 parse_llint(const char *str, int64_t *val)
43 {
44         char *endptr;
45         int save_errno = errno;
46
47         errno = 0;
48         *val = strtoll(str, &endptr, 0);
49
50         if ((errno == ERANGE && (*val == LLONG_MAX || *val == LLONG_MIN)) ||
51                         (errno != 0 && *val == 0))
52                 return -1;
53
54         if (*endptr != 0) {
55                 errno = EINVAL;
56                 return -1;
57         }
58
59         errno = save_errno;
60         return 0;
61 }
62
63 static int
64 parse_ullint(const char *str, uint64_t *val)
65 {
66         char *endptr;
67         int save_errno = errno;
68
69         /* since a negative input is silently converted to a positive
70          * one by strtoull(), first check that it is positive */
71         if (strchr(str, '-'))
72                 return -1;
73
74         errno = 0;
75         *val = strtoull(str, &endptr, 0);
76
77         if ((errno == ERANGE && *val == ULLONG_MAX) ||
78                         (errno != 0 && *val == 0))
79                 return -1;
80
81         if (*endptr != 0)
82                 return -1;
83
84         errno = save_errno;
85         return 0;
86 }
87
88 static int
89 parse_bool(const char *str, bool *val)
90 {
91         if (!strcasecmp(str, "true")) {
92                 *val = true;
93                 return 0;
94         } else if (!strcasecmp(str, "false")) {
95                 *val = false;
96                 return 0;
97         }
98         errno = EINVAL;
99         return -1;
100 }
101
102 static int
103 add_in_table(struct enode_tree *tree, const yaml_node_t *ynode,
104         struct ec_node *enode)
105 {
106         struct pair *table = NULL;
107
108         table = realloc(tree->table, (tree->table_len + 1) * sizeof(*table));
109         if (table == NULL)
110                 return -1;
111
112         ec_node_clone(enode);
113         table[tree->table_len].ynode = ynode;
114         table[tree->table_len].enode = enode;
115         tree->table = table;
116         tree->table_len++;
117
118         return 0;
119 }
120
121 static void
122 free_tree(struct enode_tree *tree)
123 {
124         size_t i;
125
126         if (tree->root != NULL)
127                 ec_node_free(tree->root);
128         for (i = 0; i < tree->table_len; i++)
129                 ec_node_free(tree->table[i].enode);
130         free(tree->table);
131 }
132
133 static struct ec_config *
134 parse_ec_config(struct enode_tree *tree,
135                 const struct ec_config_schema *schema_elt,
136                 const yaml_document_t *document, const yaml_node_t *ynode)
137 {
138         const struct ec_config_schema *subschema;
139         struct ec_config *config = NULL;
140         struct ec_node *enode = NULL;
141         enum ec_config_type type;
142         const char *value_str;
143         uint64_t u64;
144         int64_t i64;
145         bool boolean;
146
147         type = ec_config_schema_type(schema_elt);
148
149         switch (type) {
150         case EC_CONFIG_TYPE_BOOL:
151                 if (ynode->type != YAML_SCALAR_NODE) {
152                         fprintf(stderr, "Boolean should be scalar\n");
153                         goto fail;
154                 }
155                 value_str = (const char *)ynode->data.scalar.value;
156                 if (parse_bool(value_str, &boolean)  < 0) {
157                         fprintf(stderr, "Failed to parse boolean\n");
158                         goto fail;
159                 }
160                 config = ec_config_bool(boolean);
161                 if (config == NULL) {
162                         fprintf(stderr, "Failed to create config\n");
163                         goto fail;
164                 }
165                 break;
166         case EC_CONFIG_TYPE_INT64:
167                 if (ynode->type != YAML_SCALAR_NODE) {
168                         fprintf(stderr, "Int64 should be scalar\n");
169                         goto fail;
170                 }
171                 value_str = (const char *)ynode->data.scalar.value;
172                 if (parse_llint(value_str, &i64)  < 0) {
173                         fprintf(stderr, "Failed to parse i64\n");
174                         goto fail;
175                 }
176                 config = ec_config_i64(i64);
177                 if (config == NULL) {
178                         fprintf(stderr, "Failed to create config\n");
179                         goto fail;
180                 }
181                 break;
182         case EC_CONFIG_TYPE_UINT64:
183                 if (ynode->type != YAML_SCALAR_NODE) {
184                         fprintf(stderr, "Uint64 should be scalar\n");
185                         goto fail;
186                 }
187                 value_str = (const char *)ynode->data.scalar.value;
188                 if (parse_ullint(value_str, &u64)  < 0) {
189                         fprintf(stderr, "Failed to parse u64\n");
190                         goto fail;
191                 }
192                 config = ec_config_u64(u64);
193                 if (config == NULL) {
194                         fprintf(stderr, "Failed to create config\n");
195                         goto fail;
196                 }
197                 break;
198         case EC_CONFIG_TYPE_STRING:
199                 if (ynode->type != YAML_SCALAR_NODE) {
200                         fprintf(stderr, "String should be scalar\n");
201                         goto fail;
202                 }
203                 value_str = (const char *)ynode->data.scalar.value;
204                 config = ec_config_string(value_str);
205                 if (config == NULL) {
206                         fprintf(stderr, "Failed to create config\n");
207                         goto fail;
208                 }
209                 break;
210         case EC_CONFIG_TYPE_NODE:
211                 enode = parse_ec_node(tree, document, ynode);
212                 if (enode == NULL)
213                         goto fail;
214                 config = ec_config_node(enode);
215                 if (config == NULL) {
216                         fprintf(stderr, "Failed to create config\n");
217                         goto fail;
218                 }
219                 break;
220         case EC_CONFIG_TYPE_LIST:
221                 subschema = ec_config_schema_sub(schema_elt);
222                 if (subschema == NULL) {
223                         fprintf(stderr, "List has no subschema\n");
224                         goto fail;
225                 }
226                 config = parse_ec_config_list(tree, subschema, document, ynode);
227                 if (config == NULL)
228                         goto fail;
229                 break;
230         case EC_CONFIG_TYPE_DICT:
231                 subschema = ec_config_schema_sub(schema_elt);
232                 if (subschema == NULL) {
233                         fprintf(stderr, "Dict has no subschema\n");
234                         goto fail;
235                 }
236                 config = parse_ec_config_dict(tree, subschema, document, ynode);
237                 if (config == NULL)
238                         goto fail;
239                 break;
240         default:
241                 fprintf(stderr, "Invalid config type %d\n", type);
242                 goto fail;
243         }
244
245         return config;
246
247 fail:
248         ec_node_free(enode);
249         ec_config_free(config);
250         return NULL;
251 }
252
253 static struct ec_config *
254 parse_ec_config_list(struct enode_tree *tree,
255                 const struct ec_config_schema *schema,
256                 const yaml_document_t *document, const yaml_node_t *ynode)
257 {
258         struct ec_config *config = NULL, *subconfig = NULL;
259         const yaml_node_item_t *item;
260         const yaml_node_t *value;
261
262         (void)tree;
263         (void)schema;
264         (void)document;
265
266         if (ynode->type != YAML_SEQUENCE_NODE) {
267                 fprintf(stderr, "Ecoli list config should be a yaml sequence\n");
268                 goto fail;
269         }
270
271         config = ec_config_list();
272         if (config == NULL) {
273                 fprintf(stderr, "Failed to allocate config\n");
274                 goto fail;
275         }
276
277         for (item = ynode->data.sequence.items.start;
278              item < ynode->data.sequence.items.top; item++) {
279                 value = document->nodes.start + (*item) - 1; // XXX -1 ?
280                 subconfig = parse_ec_config(tree, schema, document, value);
281                 if (subconfig == NULL)
282                         goto fail;
283                 if (ec_config_list_add(config, subconfig) < 0) {
284                         fprintf(stderr, "Failed to add list entry\n");
285                         goto fail;
286                 }
287         }
288
289         return config;
290
291 fail:
292         ec_config_free(config);
293         return NULL;
294 }
295
296 static struct ec_config *
297 parse_ec_config_dict(struct enode_tree *tree,
298                 const struct ec_config_schema *schema,
299                 const yaml_document_t *document, const yaml_node_t *ynode)
300 {
301         const struct ec_config_schema *schema_elt;
302         struct ec_config *config = NULL, *subconfig = NULL;
303         const yaml_node_t *key, *value;
304         const yaml_node_pair_t *pair;
305         const char *key_str;
306
307         if (ynode->type != YAML_MAPPING_NODE) {
308                 fprintf(stderr, "Ecoli config should be a yaml mapping node\n");
309                 goto fail;
310         }
311
312         config = ec_config_dict();
313         if (config == NULL) {
314                 fprintf(stderr, "Failed to allocate config\n");
315                 goto fail;
316         }
317
318         for (pair = ynode->data.mapping.pairs.start;
319              pair < ynode->data.mapping.pairs.top; pair++) {
320                 key = document->nodes.start + pair->key - 1; // XXX -1 ?
321                 value = document->nodes.start + pair->value - 1;
322                 key_str = (const char *)key->data.scalar.value;
323
324                 if (ec_config_key_is_reserved(key_str))
325                         continue;
326                 schema_elt = ec_config_schema_lookup(schema, key_str);
327                 if (schema_elt == NULL) {
328                         fprintf(stderr, "No such config %s\n", key_str);
329                         goto fail;
330                 }
331                 subconfig = parse_ec_config(tree, schema_elt, document, value);
332                 if (subconfig == NULL)
333                         goto fail;
334                 if (ec_config_dict_set(config, key_str, subconfig) < 0) {
335                         fprintf(stderr, "Failed to set dict entry\n");
336                         goto fail;
337                 }
338         }
339
340         return config;
341
342 fail:
343         ec_config_free(config);
344         return NULL;
345 }
346
347 static struct ec_node *
348 parse_ec_node(struct enode_tree *tree,
349         const yaml_document_t *document, const yaml_node_t *ynode)
350 {
351         const struct ec_config_schema *schema;
352         const struct ec_node_type *type = NULL;
353         const char *id = NULL, *help = NULL;
354         struct ec_config *config = NULL;
355         const yaml_node_t *attrs = NULL;
356         const yaml_node_t *key, *value;
357         const yaml_node_pair_t *pair;
358         const char *key_str, *value_str;
359         struct ec_node *enode = NULL;
360
361         if (ynode->type != YAML_MAPPING_NODE) {
362                 fprintf(stderr, "Ecoli node should be a yaml mapping node\n");
363                 goto fail;
364         }
365
366         for (pair = ynode->data.mapping.pairs.start;
367              pair < ynode->data.mapping.pairs.top; pair++) {
368                 key = document->nodes.start + pair->key - 1; // XXX -1 ?
369                 value = document->nodes.start + pair->value - 1;
370                 key_str = (const char *)key->data.scalar.value;
371                 value_str = (const char *)value->data.scalar.value;
372
373                 if (!strcmp(key_str, "type")) {
374                         if (type != NULL) {
375                                 fprintf(stderr, "Duplicate type\n");
376                                 goto fail;
377                         }
378                         if (value->type != YAML_SCALAR_NODE) {
379                                 fprintf(stderr, "Type must be a string\n");
380                                 goto fail;
381                         }
382                         type = ec_node_type_lookup(value_str);
383                         if (type == NULL) {
384                                 fprintf(stderr, "Cannot find type %s\n",
385                                         value_str);
386                                 goto fail;
387                         }
388                 } else if (!strcmp(key_str, "attrs")) {
389                         if (attrs != NULL) {
390                                 fprintf(stderr, "Duplicate attrs\n");
391                                 goto fail;
392                         }
393                         if (value->type != YAML_MAPPING_NODE) {
394                                 fprintf(stderr, "Attrs must be a maping\n");
395                                 goto fail;
396                         }
397                         attrs = value;
398                 } else if (!strcmp(key_str, "id")) {
399                         if (id != NULL) {
400                                 fprintf(stderr, "Duplicate id\n");
401                                 goto fail;
402                         }
403                         if (value->type != YAML_SCALAR_NODE) {
404                                 fprintf(stderr, "Id must be a scalar\n");
405                                 goto fail;
406                         }
407                         id = value_str;
408                 } else if (!strcmp(key_str, "help")) {
409                         if (help != NULL) {
410                                 fprintf(stderr, "Duplicate help\n");
411                                 goto fail;
412                         }
413                         if (value->type != YAML_SCALAR_NODE) {
414                                 fprintf(stderr, "Help must be a scalar\n");
415                                 goto fail;
416                         }
417                         help = value_str;
418                 }
419         }
420
421         /* create the ecoli node */
422         if (id == NULL)
423                 id = EC_NO_ID;
424         enode = ec_node_from_type(type, id);
425         if (enode == NULL) {
426                 fprintf(stderr, "Cannot create ecoli node\n");
427                 goto fail;
428         }
429         if (add_in_table(tree, ynode, enode) < 0) {
430                 fprintf(stderr, "Cannot add node in table\n");
431                 goto fail;
432         }
433         if (tree->root == NULL) {
434                 ec_node_clone(enode);
435                 tree->root = enode;
436         }
437
438         /* create its config */
439         schema = ec_node_type_schema(type);
440         if (schema == NULL) {
441                 fprintf(stderr, "No configuration schema for type %s\n",
442                         ec_node_type_name(type));
443                 goto fail;
444         }
445
446         config = parse_ec_config_dict(tree, schema, document, ynode);
447         if (config == NULL)
448                 goto fail;
449
450         if (ec_node_set_config(enode, config) < 0) {
451                 fprintf(stderr, "Failed to set config\n");
452                 goto fail;
453         }
454
455         /* add attributes (all as string) */
456         //XXX
457
458         return enode;
459
460 fail:
461         ec_node_free(enode);
462         ec_config_free(config);
463         return NULL;
464 }
465
466 static int
467 parse_document(struct enode_tree *tree, const yaml_document_t *document)
468 {
469         yaml_node_t *node;
470
471         node = document->nodes.start;
472         if (parse_ec_node(tree, document, node) == NULL)
473                 return -1;
474
475         return 0;
476 }
477
478 static int parse_file(struct enode_tree *tree, const char *filename)
479 {
480         FILE *file;
481         yaml_parser_t parser;
482         yaml_document_t document;
483
484         file = fopen(filename, "rb");
485         if (file == NULL) {
486                 fprintf(stderr, "Failed to open file %s\n", filename);
487                 goto fail_no_doc;
488         }
489
490         if (yaml_parser_initialize(&parser) == 0) {
491                 fprintf(stderr, "Failed to initialize yaml parser\n");
492                 goto fail_no_doc;
493         }
494
495         yaml_parser_set_input_file(&parser, file);
496
497         if (yaml_parser_load(&parser, &document) == 0) {
498                 fprintf(stderr, "Failed to load yaml document\n");
499                 goto fail_no_doc;
500         }
501
502         if (yaml_document_get_root_node(&document) == NULL) {
503                 fprintf(stderr, "Incomplete document\n"); //XXX check err
504                 goto fail;
505         }
506
507         if (parse_document(tree, &document) < 0) {
508                 fprintf(stderr, "Failed to parse document\n");
509                 goto fail;
510         }
511
512         yaml_document_delete(&document);
513         yaml_parser_delete(&parser);
514         fclose(file);
515
516         return 0;
517
518 fail:
519         yaml_document_delete(&document);
520 fail_no_doc:
521         yaml_parser_delete(&parser);
522         if (file != NULL)
523                 fclose(file);
524
525         return -1;
526 }
527
528 int
529 main(int argc, char *argv[])
530 {
531         struct enode_tree tree;
532
533         memset(&tree, 0, sizeof(tree));
534
535         if (argc != 2) {
536                 fprintf(stderr, "Invalid args\n");
537                 goto fail;
538         }
539         if (parse_file(&tree, argv[1]) < 0) {
540                 fprintf(stderr, "Failed to parse file\n");
541                 goto fail;
542         }
543         printf("root=%p len=%zd\n", tree.root, tree.table_len);
544         ec_node_dump(stdout, tree.root);
545         free_tree(&tree);
546
547         return 0;
548
549 fail:
550         free_tree(&tree);
551         return 1;
552 }