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