save
[protos/libecoli.git] / lib / ecoli_tk_cmd.c
1 /*
2  * Copyright (c) 2016-2017, Olivier MATZ <zer0@droids-corp.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <sys/queue.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <assert.h>
33 #include <stdarg.h>
34 #include <errno.h>
35 #include <limits.h>
36
37 #include <ecoli_malloc.h>
38 #include <ecoli_log.h>
39 #include <ecoli_test.h>
40 #include <ecoli_strvec.h>
41 #include <ecoli_tk_expr.h>
42 #include <ecoli_tk_str.h>
43 #include <ecoli_tk_or.h>
44 #include <ecoli_tk_int.h>
45 #include <ecoli_tk_many.h>
46 #include <ecoli_tk_seq.h>
47 #include <ecoli_tk_option.h>
48 #include <ecoli_tk_re.h>
49 #include <ecoli_tk_re_lex.h>
50 #include <ecoli_tk_cmd.h>
51
52 struct ec_tk_cmd {
53         struct ec_tk gen;
54         char *cmd_str;           /* the command string. */
55         struct ec_tk *cmd;       /* the command token. */
56         struct ec_tk *lex;       /* the lexer token. */
57         struct ec_tk *expr;      /* the expression parser. */
58         struct ec_tk **table;    /* table of tk referenced in command. */
59         unsigned int len;        /* len of the table. */
60 };
61
62 static int
63 ec_tk_cmd_eval_var(void **result, void *userctx,
64         const struct ec_parsed_tk *var)
65 {
66         const struct ec_strvec *vec;
67         struct ec_tk_cmd *tk = userctx;
68         struct ec_tk *eval = NULL;
69         const char *str, *id;
70         unsigned int i;
71
72         (void)userctx;
73
74         /* get parsed string vector, it should contain only one str */
75         vec = ec_parsed_tk_strvec(var);
76         if (ec_strvec_len(vec) != 1)
77                 return -EINVAL;
78         str = ec_strvec_val(vec, 0);
79
80         for (i = 0; i < tk->len; i++) {
81                 id = ec_tk_id(tk->table[i]);
82                 printf("i=%d id=%s\n", i, id);
83                 if (id == NULL)
84                         continue;
85                 if (strcmp(str, id))
86                         continue;
87                 /* if id matches, use a tk provided by the user... */
88                 eval = ec_tk_clone(tk->table[i]);
89                 if (eval == NULL)
90                         return -ENOMEM;
91                 break;
92         }
93
94         /* ...or create a string token */
95         if (eval == NULL) {
96                 eval = ec_tk_str(NULL, str);
97                 if (eval == NULL)
98                         return -ENOMEM;
99         }
100
101         printf("eval var %s %p\n", str, eval);
102         *result = eval;
103
104         return 0;
105 }
106
107 static int
108 ec_tk_cmd_eval_pre_op(void **result, void *userctx, void *operand,
109         const struct ec_parsed_tk *operator)
110 {
111         (void)result;
112         (void)userctx;
113         (void)operand;
114         (void)operator;
115
116         return -EINVAL;
117 }
118
119 static int
120 ec_tk_cmd_eval_post_op(void **result, void *userctx, void *operand,
121         const struct ec_parsed_tk *operator)
122 {
123         const struct ec_strvec *vec;
124         struct ec_tk *eval = operand;;
125
126         (void)userctx;
127
128         /* get parsed string vector, it should contain only one str */
129         vec = ec_parsed_tk_strvec(operator);
130         if (ec_strvec_len(vec) != 1)
131                 return -EINVAL;
132
133         if (!strcmp(ec_strvec_val(vec, 0), "*"))
134                 eval = NULL; //XXX
135         else
136                 return -EINVAL;
137
138         printf("eval post_op %p\n", eval);
139         *result = eval;
140
141         return 0;
142 }
143
144 static int
145 ec_tk_cmd_eval_bin_op(void **result, void *userctx, void *operand1,
146         const struct ec_parsed_tk *operator, void *operand2)
147
148 {
149         const struct ec_strvec *vec;
150         struct ec_tk *out = NULL;
151         struct ec_tk *in1 = operand1;
152         struct ec_tk *in2 = operand2;
153
154         (void)userctx;
155
156         printf("eval bin_op %p %p\n", in1, in2);
157
158         /* get parsed string vector, it should contain only one str */
159         vec = ec_parsed_tk_strvec(operator);
160         if (ec_strvec_len(vec) != 1)
161                 return -EINVAL;
162
163         if (!strcmp(ec_strvec_val(vec, 0), "|")) {
164                 out = EC_TK_OR(NULL, ec_tk_clone(in1), ec_tk_clone(in2));
165                 if (out == NULL)
166                         return -EINVAL;
167                 ec_tk_free(in1);
168                 ec_tk_free(in2);
169                 *result = out;
170         } else if (!strcmp(ec_strvec_val(vec, 0), ",")) {
171                 ec_tk_free(in1);
172                 ec_tk_free(in2);
173                 *result = NULL; //XXX
174         } else {
175                 return -EINVAL;
176         }
177
178         return 0;
179 }
180
181 static int
182 ec_tk_cmd_eval_parenthesis(void **result, void *userctx,
183         const struct ec_parsed_tk *open_paren,
184         const struct ec_parsed_tk *close_paren,
185         void *value)
186 {
187         const struct ec_strvec *vec;
188         struct ec_tk *in = value;;
189         struct ec_tk *out = NULL;;
190
191         (void)userctx;
192         (void)close_paren;
193
194         /* get parsed string vector, it should contain only one str */
195         vec = ec_parsed_tk_strvec(open_paren);
196         if (ec_strvec_len(vec) != 1)
197                 return -EINVAL;
198
199         if (!strcmp(ec_strvec_val(vec, 0), "[")) {
200                 out = ec_tk_option_new(NULL, ec_tk_clone(in));
201                 if (out == NULL)
202                         return -EINVAL;
203                 ec_tk_free(in);
204         } else {
205                 return -EINVAL;
206         }
207
208         printf("eval paren\n");
209         *result = out;
210
211         return 0;
212 }
213
214 static void
215 ec_tk_cmd_eval_free(void *result, void *userctx)
216 {
217         (void)userctx;
218         ec_free(result);
219 }
220
221 static const struct ec_tk_expr_eval_ops test_ops = {
222         .eval_var = ec_tk_cmd_eval_var,
223         .eval_pre_op = ec_tk_cmd_eval_pre_op,
224         .eval_post_op = ec_tk_cmd_eval_post_op,
225         .eval_bin_op = ec_tk_cmd_eval_bin_op,
226         .eval_parenthesis = ec_tk_cmd_eval_parenthesis,
227         .eval_free = ec_tk_cmd_eval_free,
228 };
229
230 static struct ec_parsed_tk *ec_tk_cmd_parse(const struct ec_tk *gen_tk,
231         const struct ec_strvec *strvec)
232 {
233         struct ec_tk_cmd *tk = (struct ec_tk_cmd *)gen_tk;
234
235         return ec_tk_parse_tokens(tk->cmd, strvec);
236 }
237
238 static struct ec_completed_tk *ec_tk_cmd_complete(const struct ec_tk *gen_tk,
239         const struct ec_strvec *strvec)
240 {
241         struct ec_tk_cmd *tk = (struct ec_tk_cmd *)gen_tk;
242
243         return ec_tk_complete_tokens(tk->cmd, strvec);
244 }
245
246 static void ec_tk_cmd_free_priv(struct ec_tk *gen_tk)
247 {
248         struct ec_tk_cmd *tk = (struct ec_tk_cmd *)gen_tk;
249         unsigned int i;
250
251         ec_free(tk->cmd_str);
252         ec_tk_free(tk->cmd);
253         ec_tk_free(tk->expr);
254         ec_tk_free(tk->lex);
255         for (i = 0; i < tk->len; i++)
256                 ec_tk_free(tk->table[i]);
257         ec_free(tk->table);
258 }
259
260 static int ec_tk_cmd_build(struct ec_tk *gen_tk)
261 {
262         struct ec_tk *expr = NULL, *lex = NULL, *cmd = NULL;
263         struct ec_parsed_tk *p, *child;
264         struct ec_tk_cmd *tk = (struct ec_tk_cmd *)gen_tk;
265         void *result;
266         int ret;
267
268         /* build the expression parser */
269         ret = -ENOMEM;
270         expr = ec_tk_expr("expr");
271         if (expr == NULL)
272                 goto fail;
273         ret = ec_tk_expr_set_val_tk(expr, ec_tk_re(NULL, "[a-zA-Z0-9]+"));
274         if (ret < 0)
275                 goto fail;
276         ret = ec_tk_expr_add_bin_op(expr, ec_tk_str(NULL, ","));
277         if (ret < 0)
278                 goto fail;
279         ret = ec_tk_expr_add_bin_op(expr, ec_tk_str(NULL, "|"));
280         if (ret < 0)
281                 goto fail;
282         ret = ec_tk_expr_add_post_op(expr, ec_tk_str(NULL, "+"));
283         if (ret < 0)
284                 goto fail;
285         ret = ec_tk_expr_add_post_op(expr, ec_tk_str(NULL, "*"));
286         if (ret < 0)
287                 goto fail;
288         ret = ec_tk_expr_add_parenthesis(expr, ec_tk_str(NULL, "["),
289                 ec_tk_str(NULL, "]"));
290         if (ret < 0)
291                 goto fail;
292         ec_tk_expr_add_parenthesis(expr, ec_tk_str(NULL, "("),
293                 ec_tk_str(NULL, ")"));
294         if (ret < 0)
295                 goto fail;
296
297         /* prepend a lexer and a "many" to the expression token */
298         ret = -ENOMEM;
299         lex = ec_tk_re_lex(NULL,
300                 ec_tk_many(NULL, ec_tk_clone(expr), 1, 0));
301         if (lex == NULL)
302                 goto fail;
303
304         ret = ec_tk_re_lex_add(lex, "[a-zA-Z0-9]+", 1);
305         if (ret < 0)
306                 goto fail;
307         ret = ec_tk_re_lex_add(lex, "[*|,()]", 1);
308         if (ret < 0)
309                 goto fail;
310         ret = ec_tk_re_lex_add(lex, "\\[", 1);
311         if (ret < 0)
312                 goto fail;
313         ret = ec_tk_re_lex_add(lex, "\\]", 1);
314         if (ret < 0)
315                 goto fail;
316         ret = ec_tk_re_lex_add(lex, "[   ]+", 0);
317         if (ret < 0)
318                 goto fail;
319
320         /* parse the command expression */
321         ret = -ENOMEM;
322         p = ec_tk_parse(lex, tk->cmd_str);
323         if (p == NULL)
324                 goto fail;
325
326         ret = -EINVAL;
327         if (!ec_parsed_tk_matches(p))
328                 goto fail;
329         if (TAILQ_EMPTY(&p->children))
330                 goto fail;
331         if (TAILQ_EMPTY(&TAILQ_FIRST(&p->children)->children))
332                 goto fail;
333
334         ret = -ENOMEM;
335         cmd = ec_tk_seq(NULL);
336         if (cmd == NULL)
337                 goto fail;
338
339         TAILQ_FOREACH(child, &TAILQ_FIRST(&p->children)->children, next) {
340                 ret = ec_tk_expr_eval(&result, expr, child,
341                         &test_ops, tk);
342                 if (ret < 0)
343                         goto fail;
344                 ret = ec_tk_seq_add(cmd, result);
345                 if (ret < 0)
346                         goto fail;
347         }
348         ec_parsed_tk_free(p);
349         ec_tk_dump(stdout, cmd);
350
351         ec_tk_free(tk->expr);
352         tk->expr = expr;
353         ec_tk_free(tk->lex);
354         tk->lex = lex;
355         ec_tk_free(tk->cmd);
356         tk->cmd = cmd;
357
358         return 0;
359
360 fail:
361         ec_tk_free(expr);
362         ec_tk_free(lex);
363         ec_tk_free(cmd);
364         return ret;
365 }
366
367 static struct ec_tk_type ec_tk_cmd_type = {
368         .name = "cmd",
369         .build = ec_tk_cmd_build,
370         .parse = ec_tk_cmd_parse,
371         .complete = ec_tk_cmd_complete,
372         .free_priv = ec_tk_cmd_free_priv,
373 };
374
375 EC_TK_TYPE_REGISTER(ec_tk_cmd_type);
376
377 int ec_tk_cmd_add_child(struct ec_tk *gen_tk, struct ec_tk *child)
378 {
379         struct ec_tk_cmd *tk = (struct ec_tk_cmd *)gen_tk;
380         struct ec_tk **table;
381
382         // XXX check tk type
383
384         assert(tk != NULL);
385
386         printf("add child %s\n", child->id);
387         if (child == NULL)
388                 return -EINVAL;
389
390         gen_tk->flags &= ~EC_TK_F_BUILT;
391
392         table = ec_realloc(tk->table, (tk->len + 1) * sizeof(*tk->table));
393         if (table == NULL) {
394                 ec_tk_free(child);
395                 return -ENOMEM;
396         }
397
398         tk->table = table;
399         table[tk->len] = child;
400         tk->len++;
401
402         child->parent = gen_tk;
403         TAILQ_INSERT_TAIL(&gen_tk->children, child, next); // XXX really needed?
404
405         return 0;
406 }
407
408 struct ec_tk *ec_tk_cmd(const char *id, const char *cmd_str)
409 {
410         struct ec_tk *gen_tk = NULL;
411         struct ec_tk_cmd *tk = NULL;
412
413         gen_tk = ec_tk_new(id, &ec_tk_cmd_type, sizeof(*tk));
414         if (gen_tk == NULL)
415                 goto fail;
416
417         tk = (struct ec_tk_cmd *)gen_tk;
418         tk->cmd_str = ec_strdup(cmd_str);
419         if (tk->cmd_str == NULL)
420                 goto fail;
421
422         return gen_tk;
423
424 fail:
425         ec_tk_free(gen_tk);
426         return NULL;
427 }
428
429 struct ec_tk *__ec_tk_cmd(const char *id, const char *cmd, ...)
430 {
431         struct ec_tk *gen_tk = NULL;
432         struct ec_tk_cmd *tk = NULL;
433         struct ec_tk *child;
434         va_list ap;
435         int fail = 0;
436
437         va_start(ap, cmd);
438
439         gen_tk = ec_tk_cmd(id, cmd);
440         tk = (struct ec_tk_cmd *)gen_tk;
441         if (tk == NULL)
442                 fail = 1;;
443
444         for (child = va_arg(ap, struct ec_tk *);
445              child != EC_TK_ENDLIST;
446              child = va_arg(ap, struct ec_tk *)) {
447
448                 /* on error, don't quit the loop to avoid leaks */
449                 if (fail == 1 || child == NULL ||
450                                 ec_tk_cmd_add_child(&tk->gen, child) < 0) {
451                         fail = 1;
452                         ec_tk_free(child);
453                 }
454         }
455
456         if (fail == 1)
457                 goto fail;
458
459         va_end(ap);
460         return gen_tk;
461
462 fail:
463         ec_tk_free(gen_tk); /* will also free children */
464         va_end(ap);
465         return NULL;
466 }
467
468 static int ec_tk_cmd_testcase(void)
469 {
470         struct ec_tk *tk;
471         int ret = 0;
472
473         tk = EC_TK_CMD(NULL,
474                 "add [toto] x | y",
475                 ec_tk_int("x", 0, 10, 10),
476                 ec_tk_int("y", 20, 30, 10)
477         );
478         if (tk == NULL) {
479                 ec_log(EC_LOG_ERR, "cannot create tk\n");
480                 return -1;
481         }
482         ret |= EC_TEST_CHECK_TK_PARSE(tk, 2, "add", "1");
483         ret |= EC_TEST_CHECK_TK_PARSE(tk, 2, "add", "23");
484         ret |= EC_TEST_CHECK_TK_PARSE(tk, 3, "add", "toto", "23");
485         ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "add", "15");
486         ret |= EC_TEST_CHECK_TK_PARSE(tk, -1, "foo");
487         ec_tk_free(tk);
488
489         // XXX completion
490
491         return ret;
492 }
493
494 static struct ec_test ec_tk_cmd_test = {
495         .name = "tk_cmd",
496         .test = ec_tk_cmd_testcase,
497 };
498
499 EC_TEST_REGISTER(ec_tk_cmd_test);