api documentation for ec_parse
[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_htable.h>
19 #include <ecoli_node.h>
20 #include <ecoli_parse.h>
21 #include <ecoli_complete.h>
22 #include <ecoli_node_seq.h>
23 #include <ecoli_node_str.h>
24 #include <ecoli_node_option.h>
25 #include <ecoli_node_sh_lex.h>
26
27 EC_LOG_TYPE_REGISTER(node_sh_lex);
28
29 struct ec_node_sh_lex {
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 *node,
217                 struct ec_pnode *pstate,
218                 const struct ec_strvec *strvec)
219 {
220         struct ec_node_sh_lex *priv = ec_node_priv(node);
221         struct ec_strvec *new_vec = NULL;
222         struct ec_pnode *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_parse_child(priv->child, pstate, 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_pnode_get_last_child(pstate);
245                 ec_pnode_unlink_child(child_parse);
246                 ec_pnode_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 *node,
262                         struct ec_comp *comp,
263                         const struct ec_strvec *strvec)
264 {
265         struct ec_node_sh_lex *priv = ec_node_priv(node);
266         struct ec_strvec *new_vec = NULL;
267         struct ec_comp_item *item = NULL;
268         struct ec_htable *htable = 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         /* let's store the existing full completions in a htable */
283         htable = ec_htable();
284         if (htable == NULL)
285                 goto fail;
286
287         EC_COMP_FOREACH(item, comp, EC_COMP_FULL) {
288                 if (ec_htable_set(htable, &item, sizeof(item), NULL, NULL) < 0)
289                         goto fail;
290         }
291
292         /* do the completion */
293         ret = ec_complete_child(priv->child, comp, new_vec);
294         if (ret < 0)
295                 goto fail;
296
297         /* add missing quote for any new full completions */
298         if (missing_quote != '\0') {
299                 EC_COMP_FOREACH(item, comp, EC_COMP_FULL) {
300                         if (ec_htable_has_key(htable, &item, sizeof(item)))
301                                 continue;
302
303                         str = ec_comp_item_get_str(item);
304                         if (ec_asprintf(&new_str, "%c%s%c", missing_quote, str,
305                                         missing_quote) < 0) {
306                                 new_str = NULL;
307                                 goto fail;
308                         }
309                         if (ec_comp_item_set_str(item, new_str) < 0)
310                                 goto fail;
311                         ec_free(new_str);
312                         new_str = NULL;
313
314                         str = ec_comp_item_get_completion(item);
315                         if (ec_asprintf(&new_str, "%s%c", str,
316                                         missing_quote) < 0) {
317                                 new_str = NULL;
318                                 goto fail;
319                         }
320                         if (ec_comp_item_set_completion(item, new_str) < 0)
321                                 goto fail;
322                         ec_free(new_str);
323                         new_str = NULL;
324                 }
325         }
326
327         ec_strvec_free(new_vec);
328         ec_htable_free(htable);
329
330         return 0;
331
332  fail:
333         ec_strvec_free(new_vec);
334         ec_free(new_str);
335         ec_htable_free(htable);
336
337         return -1;
338 }
339
340 static void ec_node_sh_lex_free_priv(struct ec_node *node)
341 {
342         struct ec_node_sh_lex *priv = ec_node_priv(node);
343
344         ec_node_free(priv->child);
345 }
346
347 static size_t
348 ec_node_sh_lex_get_children_count(const struct ec_node *node)
349 {
350         struct ec_node_sh_lex *priv = ec_node_priv(node);
351
352         if (priv->child)
353                 return 1;
354         return 0;
355 }
356
357 static int
358 ec_node_sh_lex_get_child(const struct ec_node *node, size_t i,
359         struct ec_node **child, unsigned int *refs)
360 {
361         struct ec_node_sh_lex *priv = ec_node_priv(node);
362
363         if (i >= 1)
364                 return -1;
365
366         *refs = 1;
367         *child = priv->child;
368         return 0;
369 }
370
371 static struct ec_node_type ec_node_sh_lex_type = {
372         .name = "sh_lex",
373         .parse = ec_node_sh_lex_parse,
374         .complete = ec_node_sh_lex_complete,
375         .size = sizeof(struct ec_node_sh_lex),
376         .free_priv = ec_node_sh_lex_free_priv,
377         .get_children_count = ec_node_sh_lex_get_children_count,
378         .get_child = ec_node_sh_lex_get_child,
379 };
380
381 EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
382
383 struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
384 {
385         struct ec_node *node = NULL;
386         struct ec_node_sh_lex *priv;
387
388         if (child == NULL)
389                 return NULL;
390
391         node = ec_node_from_type(&ec_node_sh_lex_type, id);
392         if (node == NULL) {
393                 ec_node_free(child);
394                 return NULL;
395         }
396
397         priv = ec_node_priv(node);
398         priv->child = child;
399
400         return node;
401 }
402
403 /* LCOV_EXCL_START */
404 static int ec_node_sh_lex_testcase(void)
405 {
406         struct ec_node *node;
407         int testres = 0;
408
409         node = ec_node_sh_lex(EC_NO_ID,
410                 EC_NODE_SEQ(EC_NO_ID,
411                         ec_node_str(EC_NO_ID, "foo"),
412                         ec_node_option(EC_NO_ID,
413                                 ec_node_str(EC_NO_ID, "toto")
414                         ),
415                         ec_node_str(EC_NO_ID, "bar")
416                 )
417         );
418         if (node == NULL) {
419                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
420                 return -1;
421         }
422         testres |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
423         testres |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
424         testres |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
425         testres |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
426         testres |= EC_TEST_CHECK_PARSE(node, -1, "  foo toto bar'");
427         ec_node_free(node);
428
429         /* test completion */
430         node = ec_node_sh_lex(EC_NO_ID,
431                 EC_NODE_SEQ(EC_NO_ID,
432                         ec_node_str(EC_NO_ID, "foo"),
433                         ec_node_option(EC_NO_ID,
434                                 ec_node_str(EC_NO_ID, "toto")
435                         ),
436                         ec_node_str(EC_NO_ID, "bar"),
437                         ec_node_str(EC_NO_ID, "titi")
438                 )
439         );
440         if (node == NULL) {
441                 EC_LOG(EC_LOG_ERR, "cannot create node\n");
442                 return -1;
443         }
444         testres |= EC_TEST_CHECK_COMPLETE(node,
445                 "", EC_VA_END,
446                 "foo", EC_VA_END);
447         testres |= EC_TEST_CHECK_COMPLETE(node,
448                 " ", EC_VA_END,
449                 "foo", EC_VA_END);
450         testres |= EC_TEST_CHECK_COMPLETE(node,
451                 "f", EC_VA_END,
452                 "foo", EC_VA_END);
453         testres |= EC_TEST_CHECK_COMPLETE(node,
454                 "foo", EC_VA_END,
455                 "foo", EC_VA_END);
456         testres |= EC_TEST_CHECK_COMPLETE(node,
457                 "foo ", EC_VA_END,
458                 "bar", "toto", EC_VA_END);
459         testres |= EC_TEST_CHECK_COMPLETE(node,
460                 "foo t", EC_VA_END,
461                 "toto", EC_VA_END);
462         testres |= EC_TEST_CHECK_COMPLETE(node,
463                 "foo b", EC_VA_END,
464                 "bar", EC_VA_END);
465         testres |= EC_TEST_CHECK_COMPLETE(node,
466                 "foo bar", EC_VA_END,
467                 "bar", EC_VA_END);
468         testres |= EC_TEST_CHECK_COMPLETE(node,
469                 "foo bar ", EC_VA_END,
470                 "titi", EC_VA_END);
471         testres |= EC_TEST_CHECK_COMPLETE(node,
472                 "foo toto bar ", EC_VA_END,
473                 "titi", EC_VA_END);
474         testres |= EC_TEST_CHECK_COMPLETE(node,
475                 "x", EC_VA_END,
476                 EC_VA_END);
477         testres |= EC_TEST_CHECK_COMPLETE(node,
478                 "foo barx", EC_VA_END,
479                 EC_VA_END);
480         testres |= EC_TEST_CHECK_COMPLETE(node,
481                 "foo 'b", EC_VA_END,
482                 "'bar'", EC_VA_END);
483
484         ec_node_free(node);
485         return testres;
486 }
487 /* LCOV_EXCL_STOP */
488
489 static struct ec_test ec_node_sh_lex_test = {
490         .name = "node_sh_lex",
491         .test = ec_node_sh_lex_testcase,
492 };
493
494 EC_TEST_REGISTER(ec_node_sh_lex_test);