add meson support
[protos/libecoli.git] / src / ecoli_node_sh_lex.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 <string.h>
8 #include <assert.h>
9 #include <stdarg.h>
10 #include <ctype.h>
11 #include <errno.h>
12
13 #include <ecoli_malloc.h>
14 #include <ecoli_log.h>
15 #include <ecoli_string.h>
16 #include <ecoli_test.h>
17 #include <ecoli_strvec.h>
18 #include <ecoli_node.h>
19 #include <ecoli_parse.h>
20 #include <ecoli_complete.h>
21 #include <ecoli_node_seq.h>
22 #include <ecoli_node_str.h>
23 #include <ecoli_node_option.h>
24 #include <ecoli_node_sh_lex.h>
25
26 EC_LOG_TYPE_REGISTER(node_sh_lex);
27
28 struct ec_node_sh_lex {
29         struct ec_node gen;
30         struct ec_node *child;
31 };
32
33 static size_t eat_spaces(const char *str)
34 {
35         size_t i = 0;
36
37         /* skip spaces */
38         while (isblank(str[i]))
39                 i++;
40
41         return i;
42 }
43
44 /*
45  * Allocate a new string which is a copy of the input string with quotes
46  * removed. If quotes are not closed properly, set missing_quote to the
47  * missing quote char.
48  */
49 static char *unquote_str(const char *str, size_t n, int allow_missing_quote,
50         char *missing_quote)
51 {
52         unsigned s = 1, d = 0;
53         char quote = str[0];
54         char *dst;
55         int closed = 0;
56
57         dst = ec_malloc(n);
58         if (dst == NULL) {
59                 errno = ENOMEM;
60                 return NULL;
61         }
62
63         /* copy string and remove quotes */
64         while (s < n && d < n && str[s] != '\0') {
65                 if (str[s] == '\\' && str[s+1] == quote) {
66                         dst[d++] = quote;
67                         s += 2;
68                         continue;
69                 }
70                 if (str[s] == '\\' && str[s+1] == '\\') {
71                         dst[d++] = '\\';
72                         s += 2;
73                         continue;
74                 }
75                 if (str[s] == quote) {
76                         s++;
77                         closed = 1;
78                         break;
79                 }
80                 dst[d++] = str[s++];
81         }
82
83         /* not enough room in dst buffer (should not happen) */
84         if (d >= n) {
85                 ec_free(dst);
86                 errno = EMSGSIZE;
87                 return NULL;
88         }
89
90         /* quote not closed */
91         if (closed == 0) {
92                 if (missing_quote != NULL)
93                         *missing_quote = str[0];
94                 if (allow_missing_quote == 0) {
95                         ec_free(dst);
96                         errno = EBADMSG;
97                         return NULL;
98                 }
99         }
100         dst[d++] = '\0';
101
102         return dst;
103 }
104
105 static size_t eat_quoted_str(const char *str)
106 {
107         size_t i = 0;
108         char quote = str[0];
109
110         while (str[i] != '\0') {
111                 if (str[i] != '\\' && str[i+1] == quote)
112                         return i + 2;
113                 i++;
114         }
115
116         /* unclosed quote, will be detected later */
117         return i;
118 }
119
120 static size_t eat_str(const char *str)
121 {
122         size_t i = 0;
123
124         /* eat chars until we find a quote, space, or end of string  */
125         while (!isblank(str[i]) && str[i] != '\0' &&
126                         str[i] != '"' && str[i] != '\'')
127                 i++;
128
129         return i;
130 }
131
132 static struct ec_strvec *tokenize(const char *str, int completion,
133         int allow_missing_quote, char *missing_quote)
134 {
135         struct ec_strvec *strvec = NULL;
136         size_t off = 0, len, suboff, sublen;
137         char *word = NULL, *concat = NULL, *tmp;
138         int last_is_space = 1;
139
140         strvec = ec_strvec();
141         if (strvec == NULL)
142                 goto fail;
143
144         while (str[off] != '\0') {
145                 if (missing_quote != NULL)
146                         *missing_quote = '\0';
147                 len = eat_spaces(&str[off]);
148                 if (len > 0)
149                         last_is_space = 1;
150                 off += len;
151
152                 len = 0;
153                 suboff = off;
154                 while (str[suboff] != '\0') {
155                         if (missing_quote != NULL)
156                                 *missing_quote = '\0';
157                         last_is_space = 0;
158                         if (str[suboff] == '"' || str[suboff] == '\'') {
159                                 sublen = eat_quoted_str(&str[suboff]);
160                                 word = unquote_str(&str[suboff], sublen,
161                                         allow_missing_quote, missing_quote);
162                         } else {
163                                 sublen = eat_str(&str[suboff]);
164                                 if (sublen == 0)
165                                         break;
166                                 word = ec_strndup(&str[suboff], sublen);
167                         }
168
169                         if (word == NULL)
170                                 goto fail;
171
172                         len += sublen;
173                         suboff += sublen;
174
175                         if (concat == NULL) {
176                                 concat = word;
177                                 word = NULL;
178                         } else {
179                                 tmp = ec_realloc(concat, len + 1);
180                                 if (tmp == NULL)
181                                         goto fail;
182                                 concat = tmp;
183                                 strcat(concat, word);
184                                 ec_free(word);
185                                 word = NULL;
186                         }
187                 }
188
189                 if (concat != NULL) {
190                         if (ec_strvec_add(strvec, concat) < 0)
191                                 goto fail;
192                         ec_free(concat);
193                         concat = NULL;
194                 }
195
196                 off += len;
197         }
198
199         /* in completion mode, append an empty string in the vector if
200          * the input string ends with space */
201         if (completion && last_is_space) {
202                 if (ec_strvec_add(strvec, "") < 0)
203                         goto fail;
204         }
205
206         return strvec;
207
208  fail:
209         ec_free(word);
210         ec_free(concat);
211         ec_strvec_free(strvec);
212         return NULL;
213 }
214
215 static int
216 ec_node_sh_lex_parse(const struct ec_node *gen_node,
217                 struct ec_parse *state,
218                 const struct ec_strvec *strvec)
219 {
220         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
221         struct ec_strvec *new_vec = NULL;
222         struct ec_parse *child_parse;
223         const char *str;
224         int ret;
225
226         if (ec_strvec_len(strvec) == 0) {
227                 new_vec = ec_strvec();
228         } else {
229                 str = ec_strvec_val(strvec, 0);
230                 new_vec = tokenize(str, 0, 0, NULL);
231         }
232         if (new_vec == NULL && errno == EBADMSG) /* quotes not closed */
233                 return EC_PARSE_NOMATCH;
234         if (new_vec == NULL)
235                 goto fail;
236
237         ret = ec_node_parse_child(node->child, state, new_vec);
238         if (ret < 0)
239                 goto fail;
240
241         if ((unsigned)ret == ec_strvec_len(new_vec)) {
242                 ret = 1;
243         } else if (ret != EC_PARSE_NOMATCH) {
244                 child_parse = ec_parse_get_last_child(state);
245                 ec_parse_unlink_child(state, child_parse);
246                 ec_parse_free(child_parse);
247                 ret = EC_PARSE_NOMATCH;
248         }
249
250         ec_strvec_free(new_vec);
251         new_vec = NULL;
252
253         return ret;
254
255  fail:
256         ec_strvec_free(new_vec);
257         return -1;
258 }
259
260 static int
261 ec_node_sh_lex_complete(const struct ec_node *gen_node,
262                         struct ec_comp *comp,
263                         const struct ec_strvec *strvec)
264 {
265         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
266         struct ec_comp *tmp_comp = NULL;
267         struct ec_strvec *new_vec = NULL;
268         struct ec_comp_iter *iter = NULL;
269         struct ec_comp_item *item = NULL;
270         char *new_str = NULL;
271         const char *str;
272         char missing_quote = '\0';
273         int ret;
274
275         if (ec_strvec_len(strvec) != 1)
276                 return 0;
277
278         str = ec_strvec_val(strvec, 0);
279         new_vec = tokenize(str, 1, 1, &missing_quote);
280         if (new_vec == NULL)
281                 goto fail;
282
283         /* we will store the completions in a temporary struct, because
284          * we want to update them (ex: add missing quotes) */
285         tmp_comp = ec_comp(ec_comp_get_state(comp));
286         if (tmp_comp == NULL)
287                 goto fail;
288
289         ret = ec_node_complete_child(node->child, tmp_comp, new_vec);
290         if (ret < 0)
291                 goto fail;
292
293         /* add missing quote for full completions  */
294         if (missing_quote != '\0') {
295                 iter = ec_comp_iter(tmp_comp, EC_COMP_FULL);
296                 if (iter == NULL)
297                         goto fail;
298                 while ((item = ec_comp_iter_next(iter)) != NULL) {
299                         str = ec_comp_item_get_str(item);
300                         if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str,
301                                         missing_quote) < 0) {
302                                 new_str = NULL;
303                                 goto fail;
304                         }
305                         if (ec_comp_item_set_str(item, new_str) < 0)
306                                 goto fail;
307                         ec_free(new_str);
308                         new_str = NULL;
309
310                         str = ec_comp_item_get_completion(item);
311                         if (ec_asprintf(&new_str, "%s%c", str,
312                                         missing_quote) < 0) {
313                                 new_str = NULL;
314                                 goto fail;
315                         }
316                         if (ec_comp_item_set_completion(item, new_str) < 0)
317                                 goto fail;
318                         ec_free(new_str);
319                         new_str = NULL;
320                 }
321         }
322
323         ec_comp_iter_free(iter);
324         ec_strvec_free(new_vec);
325
326         ec_comp_merge(comp, tmp_comp);
327
328         return 0;
329
330  fail:
331         ec_comp_free(tmp_comp);
332         ec_comp_iter_free(iter);
333         ec_strvec_free(new_vec);
334         ec_free(new_str);
335
336         return -1;
337 }
338
339 static void ec_node_sh_lex_free_priv(struct ec_node *gen_node)
340 {
341         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
342
343         ec_node_free(node->child);
344 }
345
346 static size_t
347 ec_node_sh_lex_get_children_count(const struct ec_node *gen_node)
348 {
349         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
350
351         if (node->child)
352                 return 1;
353         return 0;
354 }
355
356 static int
357 ec_node_sh_lex_get_child(const struct ec_node *gen_node, size_t i,
358         struct ec_node **child, unsigned int *refs)
359 {
360         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
361
362         if (i >= 1)
363                 return -1;
364
365         *refs = 1;
366         *child = node->child;
367         return 0;
368 }
369
370 static struct ec_node_type ec_node_sh_lex_type = {
371         .name = "sh_lex",
372         .parse = ec_node_sh_lex_parse,
373         .complete = ec_node_sh_lex_complete,
374         .size = sizeof(struct ec_node_sh_lex),
375         .free_priv = ec_node_sh_lex_free_priv,
376         .get_children_count = ec_node_sh_lex_get_children_count,
377         .get_child = ec_node_sh_lex_get_child,
378 };
379
380 EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
381
382 struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
383 {
384         struct ec_node_sh_lex *node = NULL;
385
386         if (child == NULL)
387                 return NULL;
388
389         node = (struct ec_node_sh_lex *)ec_node_from_type(&ec_node_sh_lex_type, id);
390         if (node == NULL) {
391                 ec_node_free(child);
392                 return NULL;
393         }
394
395         node->child = child;
396
397         return &node->gen;
398 }
399
400 /* LCOV_EXCL_START */
401 static int ec_node_sh_lex_testcase(void)
402 {
403         struct ec_node *node;
404         int testres = 0;
405
406         node = ec_node_sh_lex(EC_NO_ID,
407                 EC_NODE_SEQ(EC_NO_ID,
408                         ec_node_str(EC_NO_ID, "foo"),
409                         ec_node_option(EC_NO_ID,
410                                 ec_node_str(EC_NO_ID, "toto")
411                         ),
412                         ec_node_str(EC_NO_ID, "bar")
413                 )
414         );
415         if (node == NULL) {
416                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
417                 return -1;
418         }
419         testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
420         testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
421         testres |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
422         testres |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
423         testres |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
424         ec_node_free(node);
425
426         /* test completion */
427         node = ec_node_sh_lex(EC_NO_ID,
428                 EC_NODE_SEQ(EC_NO_ID,
429                         ec_node_str(EC_NO_ID, "foo"),
430                         ec_node_option(EC_NO_ID,
431                                 ec_node_str(EC_NO_ID, "toto")
432                         ),
433                         ec_node_str(EC_NO_ID, "bar"),
434                         ec_node_str(EC_NO_ID, "titi")
435                 )
436         );
437         if (node == NULL) {
438                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
439                 return -1;
440         }
441         testres |= EC_TEST_CHECK_COMPLETE(node,
442                 "", EC_NODE_ENDLIST,
443                 "foo", EC_NODE_ENDLIST);
444         testres |= EC_TEST_CHECK_COMPLETE(node,
445                 " ", EC_NODE_ENDLIST,
446                 "foo", EC_NODE_ENDLIST);
447         testres |= EC_TEST_CHECK_COMPLETE(node,
448                 "f", EC_NODE_ENDLIST,
449                 "foo", EC_NODE_ENDLIST);
450         testres |= EC_TEST_CHECK_COMPLETE(node,
451                 "foo", EC_NODE_ENDLIST,
452                 "foo", EC_NODE_ENDLIST);
453         testres |= EC_TEST_CHECK_COMPLETE(node,
454                 "foo ", EC_NODE_ENDLIST,
455                 "bar", "toto", EC_NODE_ENDLIST);
456         testres |= EC_TEST_CHECK_COMPLETE(node,
457                 "foo t", EC_NODE_ENDLIST,
458                 "toto", EC_NODE_ENDLIST);
459         testres |= EC_TEST_CHECK_COMPLETE(node,
460                 "foo b", EC_NODE_ENDLIST,
461                 "bar", EC_NODE_ENDLIST);
462         testres |= EC_TEST_CHECK_COMPLETE(node,
463                 "foo bar", EC_NODE_ENDLIST,
464                 "bar", EC_NODE_ENDLIST);
465         testres |= EC_TEST_CHECK_COMPLETE(node,
466                 "foo bar ", EC_NODE_ENDLIST,
467                 "titi", EC_NODE_ENDLIST);
468         testres |= EC_TEST_CHECK_COMPLETE(node,
469                 "foo toto bar ", EC_NODE_ENDLIST,
470                 "titi", EC_NODE_ENDLIST);
471         testres |= EC_TEST_CHECK_COMPLETE(node,
472                 "x", EC_NODE_ENDLIST,
473                 EC_NODE_ENDLIST);
474         testres |= EC_TEST_CHECK_COMPLETE(node,
475                 "foo barx", EC_NODE_ENDLIST,
476                 EC_NODE_ENDLIST);
477         testres |= EC_TEST_CHECK_COMPLETE(node,
478                 "foo 'b", EC_NODE_ENDLIST,
479                 "'bar'", EC_NODE_ENDLIST);
480
481         ec_node_free(node);
482         return testres;
483 }
484 /* LCOV_EXCL_STOP */
485
486 static struct ec_test ec_node_sh_lex_test = {
487         .name = "node_sh_lex",
488         .test = ec_node_sh_lex_testcase,
489 };
490
491 EC_TEST_REGISTER(ec_node_sh_lex_test);