del -> unlink + const macro
[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_parsed.h>
20 #include <ecoli_completed.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 = EINVAL;
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_parsed *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_parsed *child_parsed;
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) {
233                 if (errno == EINVAL)
234                         ret = EC_PARSED_NOMATCH;
235                 else
236                         ret = -ENOMEM;
237                 goto fail;
238         }
239
240         ret = ec_node_parse_child(node->child, state, new_vec);
241         if (ret < 0)
242                 goto fail;
243
244         if ((unsigned)ret == ec_strvec_len(new_vec)) {
245                 ret = 1;
246         } else if (ret != EC_PARSED_NOMATCH) {
247                 child_parsed = ec_parsed_get_last_child(state);
248                 ec_parsed_unlink_child(state, child_parsed);
249                 ec_parsed_free(child_parsed);
250                 ret = EC_PARSED_NOMATCH;
251         }
252
253         ec_strvec_free(new_vec);
254         new_vec = NULL;
255
256         return ret;
257
258  fail:
259         ec_strvec_free(new_vec);
260         return ret;
261 }
262
263 static int
264 ec_node_sh_lex_complete(const struct ec_node *gen_node,
265                         struct ec_completed *completed,
266                         const struct ec_strvec *strvec)
267 {
268         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
269         struct ec_completed *tmp_completed = NULL;
270         struct ec_strvec *new_vec = NULL;
271         struct ec_completed_iter *iter = NULL;
272         struct ec_completed_item *item = NULL;
273         char *new_str = NULL;
274         const char *str;
275         char missing_quote = '\0';
276         int ret;
277
278         if (ec_strvec_len(strvec) != 1)
279                 return 0;
280
281         str = ec_strvec_val(strvec, 0);
282         new_vec = tokenize(str, 1, 1, &missing_quote);
283         if (new_vec == NULL)
284                 goto fail;
285
286         /* we will store the completions in a temporary struct, because
287          * we want to update them (ex: add missing quotes) */
288         tmp_completed = ec_completed(ec_completed_get_state(completed));
289         if (tmp_completed == NULL)
290                 goto fail;
291
292         ret = ec_node_complete_child(node->child, tmp_completed, new_vec);
293         if (ret < 0)
294                 goto fail;
295
296         /* add missing quote for full completions  */
297         if (missing_quote != '\0') {
298                 iter = ec_completed_iter(tmp_completed, EC_COMP_FULL);
299                 if (iter == NULL)
300                         goto fail;
301                 while ((item = ec_completed_iter_next(iter)) != NULL) {
302                         str = ec_completed_item_get_str(item);
303                         if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str,
304                                         missing_quote) < 0) {
305                                 new_str = NULL;
306                                 goto fail;
307                         }
308                         if (ec_completed_item_set_str(item, new_str) < 0)
309                                 goto fail;
310                         ec_free(new_str);
311                         new_str = NULL;
312
313                         str = ec_completed_item_get_completion(item);
314                         if (ec_asprintf(&new_str, "%s%c", str,
315                                         missing_quote) < 0) {
316                                 new_str = NULL;
317                                 goto fail;
318                         }
319                         if (ec_completed_item_set_completion(item, new_str) < 0)
320                                 goto fail;
321                         ec_free(new_str);
322                         new_str = NULL;
323                 }
324         }
325
326         ec_completed_iter_free(iter);
327         ec_strvec_free(new_vec);
328
329         ec_completed_merge(completed, tmp_completed);
330
331         return 0;
332
333  fail:
334         ec_completed_free(tmp_completed);
335         ec_completed_iter_free(iter);
336         ec_strvec_free(new_vec);
337         ec_free(new_str);
338
339         return -1;
340 }
341
342 static void ec_node_sh_lex_free_priv(struct ec_node *gen_node)
343 {
344         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
345
346         ec_node_free(node->child);
347 }
348
349 static struct ec_node_type ec_node_sh_lex_type = {
350         .name = "sh_lex",
351         .parse = ec_node_sh_lex_parse,
352         .complete = ec_node_sh_lex_complete,
353         .size = sizeof(struct ec_node_sh_lex),
354         .free_priv = ec_node_sh_lex_free_priv,
355 };
356
357 EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
358
359 struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
360 {
361         struct ec_node_sh_lex *node = NULL;
362
363         if (child == NULL)
364                 return NULL;
365
366         node = (struct ec_node_sh_lex *)__ec_node(&ec_node_sh_lex_type, id);
367         if (node == NULL) {
368                 ec_node_free(child);
369                 return NULL;
370         }
371
372         node->child = child;
373
374         return &node->gen;
375 }
376
377 /* LCOV_EXCL_START */
378 static int ec_node_sh_lex_testcase(void)
379 {
380         struct ec_node *node;
381         int testres = 0;
382
383         node = ec_node_sh_lex(EC_NO_ID,
384                 EC_NODE_SEQ(EC_NO_ID,
385                         ec_node_str(EC_NO_ID, "foo"),
386                         ec_node_option(EC_NO_ID,
387                                 ec_node_str(EC_NO_ID, "toto")
388                         ),
389                         ec_node_str(EC_NO_ID, "bar")
390                 )
391         );
392         if (node == NULL) {
393                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
394                 return -1;
395         }
396         testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
397         testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
398         testres |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
399         testres |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
400         testres |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
401         ec_node_free(node);
402
403         /* test completion */
404         node = ec_node_sh_lex(EC_NO_ID,
405                 EC_NODE_SEQ(EC_NO_ID,
406                         ec_node_str(EC_NO_ID, "foo"),
407                         ec_node_option(EC_NO_ID,
408                                 ec_node_str(EC_NO_ID, "toto")
409                         ),
410                         ec_node_str(EC_NO_ID, "bar"),
411                         ec_node_str(EC_NO_ID, "titi")
412                 )
413         );
414         if (node == NULL) {
415                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
416                 return -1;
417         }
418         testres |= EC_TEST_CHECK_COMPLETE(node,
419                 "", EC_NODE_ENDLIST,
420                 "foo", EC_NODE_ENDLIST);
421         testres |= EC_TEST_CHECK_COMPLETE(node,
422                 " ", EC_NODE_ENDLIST,
423                 "foo", EC_NODE_ENDLIST);
424         testres |= EC_TEST_CHECK_COMPLETE(node,
425                 "f", EC_NODE_ENDLIST,
426                 "foo", EC_NODE_ENDLIST);
427         testres |= EC_TEST_CHECK_COMPLETE(node,
428                 "foo", EC_NODE_ENDLIST,
429                 "foo", EC_NODE_ENDLIST);
430         testres |= EC_TEST_CHECK_COMPLETE(node,
431                 "foo ", EC_NODE_ENDLIST,
432                 "bar", "toto", EC_NODE_ENDLIST);
433         testres |= EC_TEST_CHECK_COMPLETE(node,
434                 "foo t", EC_NODE_ENDLIST,
435                 "toto", EC_NODE_ENDLIST);
436         testres |= EC_TEST_CHECK_COMPLETE(node,
437                 "foo b", EC_NODE_ENDLIST,
438                 "bar", EC_NODE_ENDLIST);
439         testres |= EC_TEST_CHECK_COMPLETE(node,
440                 "foo bar", EC_NODE_ENDLIST,
441                 "bar", EC_NODE_ENDLIST);
442         testres |= EC_TEST_CHECK_COMPLETE(node,
443                 "foo bar ", EC_NODE_ENDLIST,
444                 "titi", EC_NODE_ENDLIST);
445         testres |= EC_TEST_CHECK_COMPLETE(node,
446                 "foo toto bar ", EC_NODE_ENDLIST,
447                 "titi", EC_NODE_ENDLIST);
448         testres |= EC_TEST_CHECK_COMPLETE(node,
449                 "x", EC_NODE_ENDLIST,
450                 EC_NODE_ENDLIST);
451         testres |= EC_TEST_CHECK_COMPLETE(node,
452                 "foo barx", EC_NODE_ENDLIST,
453                 EC_NODE_ENDLIST);
454         testres |= EC_TEST_CHECK_COMPLETE(node,
455                 "foo 'b", EC_NODE_ENDLIST,
456                 "'bar'", EC_NODE_ENDLIST);
457
458         ec_node_free(node);
459         return testres;
460 }
461 /* LCOV_EXCL_STOP */
462
463 static struct ec_test ec_node_sh_lex_test = {
464         .name = "node_sh_lex",
465         .test = ec_node_sh_lex_testcase,
466 };
467
468 EC_TEST_REGISTER(ec_node_sh_lex_test);