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