a0b7a055f4401f2ca8bd1d887d52523455a78054
[protos/libecoli.git] / lib / 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 size_t
340 ec_node_sh_lex_get_children_count(const struct ec_node *gen_node)
341 {
342         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
343
344         if (node->child)
345                 return 1;
346         return 0;
347 }
348
349 static struct ec_node *
350 ec_node_sh_lex_get_child(const struct ec_node *gen_node, size_t i)
351 {
352         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
353
354         if (i >= 1)
355                 return NULL;
356
357         return node->child;
358 }
359
360 static struct ec_node_type ec_node_sh_lex_type = {
361         .name = "sh_lex",
362         .parse = ec_node_sh_lex_parse,
363         .complete = ec_node_sh_lex_complete,
364         .size = sizeof(struct ec_node_sh_lex),
365         .get_children_count = ec_node_sh_lex_get_children_count,
366         .get_child = ec_node_sh_lex_get_child,
367 };
368
369 EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
370
371 struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
372 {
373         struct ec_node_sh_lex *node = NULL;
374
375         if (child == NULL)
376                 return NULL;
377
378         node = (struct ec_node_sh_lex *)__ec_node(&ec_node_sh_lex_type, id);
379         if (node == NULL) {
380                 ec_node_free(child);
381                 return NULL;
382         }
383
384         node->child = child;
385
386         return &node->gen;
387 }
388
389 /* LCOV_EXCL_START */
390 static int ec_node_sh_lex_testcase(void)
391 {
392         struct ec_node *node;
393         int testres = 0;
394
395         node = ec_node_sh_lex(EC_NO_ID,
396                 EC_NODE_SEQ(EC_NO_ID,
397                         ec_node_str(EC_NO_ID, "foo"),
398                         ec_node_option(EC_NO_ID,
399                                 ec_node_str(EC_NO_ID, "toto")
400                         ),
401                         ec_node_str(EC_NO_ID, "bar")
402                 )
403         );
404         if (node == NULL) {
405                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
406                 return -1;
407         }
408         testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
409         testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
410         testres |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
411         testres |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
412         testres |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
413         ec_node_free(node);
414
415         /* test completion */
416         node = ec_node_sh_lex(EC_NO_ID,
417                 EC_NODE_SEQ(EC_NO_ID,
418                         ec_node_str(EC_NO_ID, "foo"),
419                         ec_node_option(EC_NO_ID,
420                                 ec_node_str(EC_NO_ID, "toto")
421                         ),
422                         ec_node_str(EC_NO_ID, "bar"),
423                         ec_node_str(EC_NO_ID, "titi")
424                 )
425         );
426         if (node == NULL) {
427                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
428                 return -1;
429         }
430         testres |= EC_TEST_CHECK_COMPLETE(node,
431                 "", EC_NODE_ENDLIST,
432                 "foo", EC_NODE_ENDLIST);
433         testres |= EC_TEST_CHECK_COMPLETE(node,
434                 " ", EC_NODE_ENDLIST,
435                 "foo", EC_NODE_ENDLIST);
436         testres |= EC_TEST_CHECK_COMPLETE(node,
437                 "f", EC_NODE_ENDLIST,
438                 "foo", EC_NODE_ENDLIST);
439         testres |= EC_TEST_CHECK_COMPLETE(node,
440                 "foo", EC_NODE_ENDLIST,
441                 "foo", EC_NODE_ENDLIST);
442         testres |= EC_TEST_CHECK_COMPLETE(node,
443                 "foo ", EC_NODE_ENDLIST,
444                 "bar", "toto", EC_NODE_ENDLIST);
445         testres |= EC_TEST_CHECK_COMPLETE(node,
446                 "foo t", EC_NODE_ENDLIST,
447                 "toto", EC_NODE_ENDLIST);
448         testres |= EC_TEST_CHECK_COMPLETE(node,
449                 "foo b", EC_NODE_ENDLIST,
450                 "bar", EC_NODE_ENDLIST);
451         testres |= EC_TEST_CHECK_COMPLETE(node,
452                 "foo bar", EC_NODE_ENDLIST,
453                 "bar", EC_NODE_ENDLIST);
454         testres |= EC_TEST_CHECK_COMPLETE(node,
455                 "foo bar ", EC_NODE_ENDLIST,
456                 "titi", EC_NODE_ENDLIST);
457         testres |= EC_TEST_CHECK_COMPLETE(node,
458                 "foo toto bar ", EC_NODE_ENDLIST,
459                 "titi", EC_NODE_ENDLIST);
460         testres |= EC_TEST_CHECK_COMPLETE(node,
461                 "x", EC_NODE_ENDLIST,
462                 EC_NODE_ENDLIST);
463         testres |= EC_TEST_CHECK_COMPLETE(node,
464                 "foo barx", EC_NODE_ENDLIST,
465                 EC_NODE_ENDLIST);
466         testres |= EC_TEST_CHECK_COMPLETE(node,
467                 "foo 'b", EC_NODE_ENDLIST,
468                 "'bar'", EC_NODE_ENDLIST);
469
470         ec_node_free(node);
471         return testres;
472 }
473 /* LCOV_EXCL_STOP */
474
475 static struct ec_test ec_node_sh_lex_test = {
476         .name = "node_sh_lex",
477         .test = ec_node_sh_lex_testcase,
478 };
479
480 EC_TEST_REGISTER(ec_node_sh_lex_test);