api documentation for ec_parse
[protos/libecoli.git] / src / ecoli_parse.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <stdint.h>
8 #include <string.h>
9 #include <assert.h>
10 #include <errno.h>
11
12 #include <ecoli_assert.h>
13 #include <ecoli_malloc.h>
14 #include <ecoli_strvec.h>
15 #include <ecoli_dict.h>
16 #include <ecoli_log.h>
17 #include <ecoli_test.h>
18 #include <ecoli_node.h>
19 #include <ecoli_node_sh_lex.h>
20 #include <ecoli_node_str.h>
21 #include <ecoli_node_seq.h>
22 #include <ecoli_parse.h>
23
24 EC_LOG_TYPE_REGISTER(parse);
25
26 TAILQ_HEAD(ec_pnode_list, ec_pnode);
27
28 struct ec_pnode {
29         TAILQ_ENTRY(ec_pnode) next;
30         struct ec_pnode_list children;
31         struct ec_pnode *parent;
32         const struct ec_node *node;
33         struct ec_strvec *strvec;
34         struct ec_dict *attrs;
35 };
36
37 static int __ec_parse_child(const struct ec_node *node,
38                                 struct ec_pnode *pstate,
39                                 bool is_root, const struct ec_strvec *strvec)
40 {
41         struct ec_strvec *match_strvec;
42         struct ec_pnode *child = NULL;
43         int ret;
44
45         // XXX limit max number of recursions to avoid segfault
46
47         if (ec_node_type(node)->parse == NULL) {
48                 errno = ENOTSUP;
49                 return -1;
50         }
51
52         if (!is_root) {
53                 child = ec_pnode(node);
54                 if (child == NULL)
55                         return -1;
56
57                 ec_pnode_link_child(pstate, child);
58         } else {
59                 child = pstate;
60         }
61         ret = ec_node_type(node)->parse(node, child, strvec);
62         if (ret < 0)
63                 goto fail;
64
65         if (ret == EC_PARSE_NOMATCH) {
66                 if (!is_root) {
67                         ec_pnode_unlink_child(child);
68                         ec_pnode_free(child);
69                 }
70                 return ret;
71         }
72
73         match_strvec = ec_strvec_ndup(strvec, 0, ret);
74         if (match_strvec == NULL)
75                 goto fail;
76
77         child->strvec = match_strvec;
78
79         return ret;
80
81 fail:
82         if (!is_root) {
83                 ec_pnode_unlink_child(child);
84                 ec_pnode_free(child);
85         }
86         return -1;
87 }
88
89 int ec_parse_child(const struct ec_node *node, struct ec_pnode *pstate,
90                         const struct ec_strvec *strvec)
91 {
92         assert(pstate != NULL);
93         return __ec_parse_child(node, pstate, false, strvec);
94 }
95
96 struct ec_pnode *ec_parse_strvec(const struct ec_node *node,
97                                 const struct ec_strvec *strvec)
98 {
99         struct ec_pnode *pnode = ec_pnode(node);
100         int ret;
101
102         if (pnode == NULL)
103                 return NULL;
104
105         ret = __ec_parse_child(node, pnode, true, strvec);
106         if (ret < 0) {
107                 ec_pnode_free(pnode);
108                 return NULL;
109         }
110
111         return pnode;
112 }
113
114 struct ec_pnode *ec_parse(const struct ec_node *node, const char *str)
115 {
116         struct ec_strvec *strvec = NULL;
117         struct ec_pnode *pnode = NULL;
118
119         errno = ENOMEM;
120         strvec = ec_strvec();
121         if (strvec == NULL)
122                 goto fail;
123
124         if (ec_strvec_add(strvec, str) < 0)
125                 goto fail;
126
127         pnode = ec_parse_strvec(node, strvec);
128         if (pnode == NULL)
129                 goto fail;
130
131         ec_strvec_free(strvec);
132         return pnode;
133
134  fail:
135         ec_strvec_free(strvec);
136         ec_pnode_free(pnode);
137         return NULL;
138 }
139
140 struct ec_pnode *ec_pnode(const struct ec_node *node)
141 {
142         struct ec_pnode *pnode = NULL;
143
144         pnode = ec_calloc(1, sizeof(*pnode));
145         if (pnode == NULL)
146                 goto fail;
147
148         TAILQ_INIT(&pnode->children);
149
150         pnode->node = node;
151         pnode->attrs = ec_dict();
152         if (pnode->attrs == NULL)
153                 goto fail;
154
155         return pnode;
156
157  fail:
158         if (pnode != NULL)
159                 ec_dict_free(pnode->attrs);
160         ec_free(pnode);
161
162         return NULL;
163 }
164
165 static struct ec_pnode *
166 __ec_pnode_dup(const struct ec_pnode *root, const struct ec_pnode *ref,
167                 struct ec_pnode **new_ref)
168 {
169         struct ec_pnode *dup = NULL;
170         struct ec_pnode *child, *dup_child;
171         struct ec_dict *attrs = NULL;
172
173         if (root == NULL)
174                 return NULL;
175
176         dup = ec_pnode(root->node);
177         if (dup == NULL)
178                 return NULL;
179
180         if (root == ref)
181                 *new_ref = dup;
182
183         attrs = ec_dict_dup(root->attrs);
184         if (attrs == NULL)
185                 goto fail;
186         ec_dict_free(dup->attrs);
187         dup->attrs = attrs;
188
189         if (root->strvec != NULL) {
190                 dup->strvec = ec_strvec_dup(root->strvec);
191                 if (dup->strvec == NULL)
192                         goto fail;
193         }
194
195         TAILQ_FOREACH(child, &root->children, next) {
196                 dup_child = __ec_pnode_dup(child, ref, new_ref);
197                 if (dup_child == NULL)
198                         goto fail;
199                 ec_pnode_link_child(dup, dup_child);
200         }
201
202         return dup;
203
204 fail:
205         ec_pnode_free(dup);
206         return NULL;
207 }
208
209 struct ec_pnode *ec_pnode_dup(const struct ec_pnode *pnode)
210 {
211         const struct ec_pnode *root;
212         struct ec_pnode *dup_root, *dup = NULL;
213
214         root = EC_PNODE_GET_ROOT(pnode);
215         dup_root = __ec_pnode_dup(root, pnode, &dup);
216         if (dup_root == NULL)
217                 return NULL;
218         assert(dup != NULL);
219
220         return dup;
221 }
222
223 void ec_pnode_free_children(struct ec_pnode *pnode)
224 {
225         struct ec_pnode *child;
226
227         if (pnode == NULL)
228                 return;
229
230         while (!TAILQ_EMPTY(&pnode->children)) {
231                 child = TAILQ_FIRST(&pnode->children);
232                 TAILQ_REMOVE(&pnode->children, child, next);
233                 child->parent = NULL;
234                 ec_pnode_free(child);
235         }
236 }
237
238 void ec_pnode_free(struct ec_pnode *pnode)
239 {
240         if (pnode == NULL)
241                 return;
242
243         ec_assert_print(pnode->parent == NULL,
244                         "parent not NULL in ec_pnode_free()");
245
246         ec_pnode_free_children(pnode);
247         ec_strvec_free(pnode->strvec);
248         ec_dict_free(pnode->attrs);
249         ec_free(pnode);
250 }
251
252 static void __ec_pnode_dump(FILE *out,
253         const struct ec_pnode *pnode, size_t indent)
254 {
255         struct ec_pnode *child;
256         const struct ec_strvec *vec;
257         const char *id = "none", *typename = "none";
258
259         /* node can be null when parsing is incomplete */
260         if (pnode->node != NULL) {
261                 id = ec_node_id(pnode->node);
262                 typename = ec_node_type(pnode->node)->name;
263         }
264
265         fprintf(out, "%*s" "type=%s id=%s vec=",
266                 (int)indent * 4, "", typename, id);
267         vec = ec_pnode_get_strvec(pnode);
268         ec_strvec_dump(out, vec);
269
270         TAILQ_FOREACH(child, &pnode->children, next)
271                 __ec_pnode_dump(out, child, indent + 1);
272 }
273
274 // XXX dump in other formats? yaml? json?
275 void ec_pnode_dump(FILE *out, const struct ec_pnode *pnode)
276 {
277         fprintf(out, "------------------- parse dump:\n");
278
279         if (pnode == NULL) {
280                 fprintf(out, "pnode is NULL\n");
281                 return;
282         }
283
284         /* Do not dump if it does not match (strvec == NULL) and if it
285          * does not have children. Note that an incomplete parsing tree,
286          * like those generated by complete(), don't match but have
287          * children that may match, and we want to dump them. */
288         if (!ec_pnode_matches(pnode) && TAILQ_EMPTY(&pnode->children)) {
289                 fprintf(out, "no match\n");
290                 return;
291         }
292
293         __ec_pnode_dump(out, pnode, 0);
294 }
295
296 void ec_pnode_link_child(struct ec_pnode *pnode,
297         struct ec_pnode *child)
298 {
299         TAILQ_INSERT_TAIL(&pnode->children, child, next);
300         child->parent = pnode;
301 }
302
303 void ec_pnode_unlink_child(struct ec_pnode *child)
304 {
305         struct ec_pnode *parent = child->parent;
306
307         if (parent != NULL) {
308                 TAILQ_REMOVE(&parent->children, child, next);
309                 child->parent = NULL;
310         }
311 }
312
313 struct ec_pnode *
314 ec_pnode_get_first_child(const struct ec_pnode *pnode)
315 {
316         return TAILQ_FIRST(&pnode->children);
317 }
318
319 struct ec_pnode *
320 ec_pnode_get_last_child(const struct ec_pnode *pnode)
321 {
322         return TAILQ_LAST(&pnode->children, ec_pnode_list);
323 }
324
325 struct ec_pnode *ec_pnode_next(const struct ec_pnode *pnode)
326 {
327         return TAILQ_NEXT(pnode, next);
328 }
329
330 const struct ec_node *ec_pnode_get_node(const struct ec_pnode *pnode)
331 {
332         if (pnode == NULL)
333                 return NULL;
334
335         return pnode->node;
336 }
337
338 void ec_pnode_del_last_child(struct ec_pnode *pnode)
339 {
340         struct ec_pnode *child;
341
342         child = ec_pnode_get_last_child(pnode);
343         if (child != NULL) {
344                 ec_pnode_unlink_child(child);
345                 ec_pnode_free(child);
346         }
347 }
348
349 struct ec_pnode *ec_pnode_get_root(struct ec_pnode *pnode)
350 {
351         if (pnode == NULL)
352                 return NULL;
353
354         while (pnode->parent != NULL)
355                 pnode = pnode->parent;
356
357         return pnode;
358 }
359
360 struct ec_pnode *ec_pnode_get_parent(const struct ec_pnode *pnode)
361 {
362         if (pnode == NULL)
363                 return NULL;
364
365         return pnode->parent;
366 }
367
368 struct ec_pnode *__ec_pnode_iter_next(const struct ec_pnode *root,
369                                 struct ec_pnode *pnode, bool iter_children)
370 {
371         struct ec_pnode *child, *parent, *next;
372
373         if (iter_children) {
374                 child = TAILQ_FIRST(&pnode->children);
375                 if (child != NULL)
376                         return child;
377         }
378         parent = pnode->parent;
379         while (parent != NULL && pnode != root) {
380                 next = TAILQ_NEXT(pnode, next);
381                 if (next != NULL)
382                         return next;
383                 pnode = parent;
384                 parent = pnode->parent;
385         }
386         return NULL;
387 }
388
389 struct ec_pnode *
390 ec_pnode_find_next(struct ec_pnode *root, struct ec_pnode *prev,
391                 const char *id, bool iter_children)
392 {
393         struct ec_pnode *iter;
394
395         if (root == NULL)
396                 return NULL;
397         if (prev == NULL)
398                 prev = root;
399         else
400                 prev = EC_PNODE_ITER_NEXT(root, prev, iter_children);
401
402         for (iter = prev; iter != NULL;
403              iter = EC_PNODE_ITER_NEXT(root, iter, 1)) {
404                 if (iter->node != NULL &&
405                                 !strcmp(ec_node_id(iter->node), id))
406                         return iter;
407         }
408
409         return NULL;
410 }
411
412 struct ec_pnode *ec_pnode_find(struct ec_pnode *root, const char *id)
413 {
414         return ec_pnode_find_next(root, NULL, id, 1);
415 }
416
417 struct ec_dict *
418 ec_pnode_get_attrs(struct ec_pnode *pnode)
419 {
420         if (pnode == NULL)
421                 return NULL;
422
423         return pnode->attrs;
424 }
425
426 const struct ec_strvec *ec_pnode_get_strvec(const struct ec_pnode *pnode)
427 {
428         if (pnode == NULL)
429                 return NULL;
430
431         return pnode->strvec;
432 }
433
434 /* number of strings in the parsed string vector */
435 size_t ec_pnode_len(const struct ec_pnode *pnode)
436 {
437         if (pnode == NULL || pnode->strvec == NULL)
438                 return 0;
439
440         return ec_strvec_len(pnode->strvec);
441 }
442
443 size_t ec_pnode_matches(const struct ec_pnode *pnode)
444 {
445         if (pnode == NULL)
446                 return 0;
447
448         if (pnode->strvec == NULL)
449                 return 0;
450
451         return 1;
452 }
453
454 /* LCOV_EXCL_START */
455 static int ec_pnode_testcase(void)
456 {
457         struct ec_node *node = NULL;
458         struct ec_pnode *p = NULL, *p2 = NULL;
459         const struct ec_pnode *pc;
460         FILE *f = NULL;
461         char *buf = NULL;
462         size_t buflen = 0;
463         int testres = 0;
464         int ret;
465
466         node = ec_node_sh_lex(EC_NO_ID,
467                         EC_NODE_SEQ(EC_NO_ID,
468                                 ec_node_str("id_x", "x"),
469                                 ec_node_str("id_y", "y")));
470         if (node == NULL)
471                 goto fail;
472
473         p = ec_parse(node, "xcdscds");
474         testres |= EC_TEST_CHECK(
475                 p != NULL && !ec_pnode_matches(p),
476                 "parse should not match\n");
477
478         f = open_memstream(&buf, &buflen);
479         if (f == NULL)
480                 goto fail;
481         ec_pnode_dump(f, p);
482         fclose(f);
483         f = NULL;
484
485         testres |= EC_TEST_CHECK(
486                 strstr(buf, "no match"), "bad dump\n");
487         free(buf);
488         buf = NULL;
489         ec_pnode_free(p);
490
491         p = ec_parse(node, "x y");
492         testres |= EC_TEST_CHECK(
493                 p != NULL && ec_pnode_matches(p),
494                 "parse should match\n");
495         testres |= EC_TEST_CHECK(
496                 ec_pnode_len(p) == 1, "bad parse len\n");
497
498         ret = ec_dict_set(ec_pnode_get_attrs(p), "key", "val", NULL);
499         testres |= EC_TEST_CHECK(ret == 0,
500                 "cannot set parse attribute\n");
501
502         p2 = ec_pnode_dup(p);
503         testres |= EC_TEST_CHECK(
504                 p2 != NULL && ec_pnode_matches(p2),
505                 "parse should match\n");
506         ec_pnode_free(p2);
507         p2 = NULL;
508
509         pc = ec_pnode_find(p, "id_x");
510         testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_x");
511         testres |= EC_TEST_CHECK(pc != NULL &&
512                 ec_pnode_get_parent(pc) != NULL &&
513                 ec_pnode_get_parent(ec_pnode_get_parent(pc)) == p,
514                 "invalid parent\n");
515
516         pc = ec_pnode_find(p, "id_y");
517         testres |= EC_TEST_CHECK(pc != NULL, "cannot find id_y");
518         pc = ec_pnode_find(p, "id_dezdezdez");
519         testres |= EC_TEST_CHECK(pc == NULL, "should not find bad id");
520
521
522         f = open_memstream(&buf, &buflen);
523         if (f == NULL)
524                 goto fail;
525         ec_pnode_dump(f, p);
526         fclose(f);
527         f = NULL;
528
529         testres |= EC_TEST_CHECK(
530                 strstr(buf, "type=sh_lex id=no-id") &&
531                 strstr(buf, "type=seq id=no-id") &&
532                 strstr(buf, "type=str id=id_x") &&
533                 strstr(buf, "type=str id=id_x"),
534                 "bad dump\n");
535         free(buf);
536         buf = NULL;
537
538         ec_pnode_free(p);
539         ec_node_free(node);
540         return testres;
541
542 fail:
543         ec_pnode_free(p2);
544         ec_pnode_free(p);
545         ec_node_free(node);
546         if (f != NULL)
547                 fclose(f);
548         free(buf);
549
550         return -1;
551 }
552 /* LCOV_EXCL_STOP */
553
554 static struct ec_test ec_pnode_test = {
555         .name = "parse",
556         .test = ec_pnode_testcase,
557 };
558
559 EC_TEST_REGISTER(ec_pnode_test);