save
[protos/libecoli.git] / lib / ecoli_node_sh_lex.c
1 /*
2  * Copyright (c) 2016, Olivier MATZ <zer0@droids-corp.org>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <assert.h>
32 #include <stdarg.h>
33 #include <ctype.h>
34 #include <errno.h>
35
36 #include <ecoli_malloc.h>
37 #include <ecoli_log.h>
38 #include <ecoli_test.h>
39 #include <ecoli_strvec.h>
40 #include <ecoli_node.h>
41 #include <ecoli_node_seq.h>
42 #include <ecoli_node_str.h>
43 #include <ecoli_node_option.h>
44 #include <ecoli_node_sh_lex.h>
45
46 struct ec_node_sh_lex {
47         struct ec_node gen;
48         struct ec_node *child;
49 };
50
51 static size_t eat_spaces(const char *str)
52 {
53         size_t i = 0;
54
55         /* skip spaces */
56         while (isblank(str[i]))
57                 i++;
58
59         return i;
60 }
61
62 /*
63  * Allocate a new string which is a copy of the input string with quotes
64  * removed. If quotes are not closed properly, set missing_quote to the
65  * missing quote char.
66  */
67 static char *unquote_str(const char *str, size_t n, int allow_missing_quote,
68         char *missing_quote)
69 {
70         unsigned s = 1, d = 0;
71         char quote = str[0];
72         char *dst;
73         int closed = 0;
74
75         dst = ec_malloc(n);
76         if (dst == NULL) {
77                 errno = ENOMEM;
78                 return NULL;
79         }
80
81         /* copy string and remove quotes */
82         while (s < n && d < n && str[s] != '\0') {
83                 if (str[s] == '\\' && str[s+1] == quote) {
84                         dst[d++] = quote;
85                         s += 2;
86                         continue;
87                 }
88                 if (str[s] == '\\' && str[s+1] == '\\') {
89                         dst[d++] = '\\';
90                         s += 2;
91                         continue;
92                 }
93                 if (str[s] == quote) {
94                         s++;
95                         closed = 1;
96                         break;
97                 }
98                 dst[d++] = str[s++];
99         }
100
101         /* not enough room in dst buffer (should not happen) */
102         if (d >= n) {
103                 ec_free(dst);
104                 errno = EMSGSIZE;
105                 return NULL;
106         }
107
108         /* quote not closed */
109         if (closed == 0) {
110                 if (missing_quote != NULL)
111                         *missing_quote = str[0];
112                 if (allow_missing_quote == 0) {
113                         ec_free(dst);
114                         errno = EINVAL;
115                         return NULL;
116                 }
117         }
118         dst[d++] = '\0';
119
120         return dst;
121 }
122
123 static size_t eat_quoted_str(const char *str)
124 {
125         size_t i = 0;
126         char quote = str[0];
127
128         while (str[i] != '\0') {
129                 if (str[i] != '\\' && str[i+1] == quote)
130                         return i + 2;
131                 i++;
132         }
133
134         /* unclosed quote, will be detected later */
135         return i;
136 }
137
138 static size_t eat_str(const char *str)
139 {
140         size_t i = 0;
141
142         /* skip spaces */
143         while (!isblank(str[i]) && str[i] != '\0')
144                 i++;
145
146         return i;
147 }
148
149 static struct ec_strvec *tokenize(const char *str, int completion,
150         int allow_missing_quote, char *missing_quote)
151 {
152         struct ec_strvec *strvec = NULL;
153         size_t off = 0, len, suboff, sublen;
154         char *word = NULL, *concat = NULL, *tmp;
155         int last_is_space = 1;
156
157 //      printf("str=%s\n", str);
158
159         strvec = ec_strvec();
160         if (strvec == NULL)
161                 goto fail;
162
163         while (str[off] != '\0') {
164                 len = eat_spaces(&str[off]);
165                 if (len > 0)
166                         last_is_space = 1;
167 //              printf("space=%zd\n", len);
168                 off += len;
169
170                 len = 0;
171                 suboff = off;
172                 while (str[suboff] != '\0') {
173                         last_is_space = 0;
174                         if (str[suboff] == '"' || str[suboff] == '\'') {
175                                 sublen = eat_quoted_str(&str[suboff]);
176 //                              printf("sublen=%zd\n", sublen);
177                                 word = unquote_str(&str[suboff], sublen,
178                                         allow_missing_quote, missing_quote);
179                         } else {
180                                 sublen = eat_str(&str[suboff]);
181 //                              printf("sublen=%zd\n", sublen);
182                                 if (sublen == 0)
183                                         break;
184                                 word = ec_strndup(&str[suboff], sublen);
185                         }
186
187                         if (word == NULL)
188                                 goto fail;
189 //                      printf("word=%s\n", word);
190
191                         len += sublen;
192                         suboff += sublen;
193
194                         if (concat == NULL) {
195                                 concat = word;
196                                 word = NULL;
197                         } else {
198                                 tmp = ec_realloc(concat, len + 1);
199                                 if (tmp == NULL)
200                                         goto fail;
201                                 concat = tmp;
202                                 strcat(concat, word);
203                                 ec_free(word);
204                                 word = NULL;
205                         }
206                 }
207
208                 if (concat != NULL) {
209                         if (ec_strvec_add(strvec, concat) < 0)
210                                 goto fail;
211                         ec_free(concat);
212                         concat = NULL;
213                 }
214
215                 /* XXX remove all printf comments */
216 //              printf("str off=%zd len=%zd\n", off, len);
217                 off += len;
218         }
219
220         /* in completion mode, append an empty string in the vector if
221          * the input string ends with space */
222         if (completion && last_is_space) {
223                 if (ec_strvec_add(strvec, "") < 0)
224                         goto fail;
225         }
226
227         return strvec;
228
229  fail:
230         ec_free(word);
231         ec_free(concat);
232         ec_strvec_free(strvec);
233         return NULL;
234 }
235
236 static struct ec_parsed *ec_node_sh_lex_parse(const struct ec_node *gen_node,
237         const struct ec_strvec *strvec)
238 {
239         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
240         struct ec_strvec *new_vec = NULL, *match_strvec;
241         struct ec_parsed *parsed = NULL, *child_parsed;
242         const char *str;
243
244         parsed = ec_parsed();
245         if (parsed == NULL)
246                 return NULL;
247
248         if (ec_strvec_len(strvec) == 0)
249                 return parsed;
250
251         str = ec_strvec_val(strvec, 0);
252         new_vec = tokenize(str, 0, 0, NULL);
253         if (new_vec == NULL)
254                 goto fail;
255
256         child_parsed = ec_node_parse_strvec(node->child, new_vec);
257         if (child_parsed == NULL)
258                 goto fail;
259
260         if (!ec_parsed_matches(child_parsed) ||
261                         ec_parsed_len(child_parsed) !=
262                                 ec_strvec_len(new_vec)) {
263                 ec_strvec_free(new_vec);
264                 ec_parsed_free(child_parsed);
265                 return parsed;
266         }
267         ec_strvec_free(new_vec);
268         new_vec = NULL;
269
270         ec_parsed_add_child(parsed, child_parsed);
271         match_strvec = ec_strvec_ndup(strvec, 0, 1);
272         if (match_strvec == NULL)
273                 goto fail;
274         ec_parsed_set_match(parsed, gen_node, match_strvec);
275
276         return parsed;
277
278  fail:
279         ec_strvec_free(new_vec);
280         ec_parsed_free(parsed);
281
282         return NULL;
283 }
284
285 static struct ec_completed *ec_node_sh_lex_complete(const struct ec_node *gen_node,
286         const struct ec_strvec *strvec)
287 {
288         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
289         struct ec_completed *completed, *child_completed = NULL;
290         struct ec_strvec *new_vec = NULL;
291         const char *str;
292         char missing_quote;
293
294 //      printf("==================\n");
295         completed = ec_completed();
296         if (completed == NULL)
297                 return NULL;
298
299         if (ec_strvec_len(strvec) != 1)
300                 return completed;
301
302         str = ec_strvec_val(strvec, 0);
303         new_vec = tokenize(str, 1, 1, &missing_quote);
304         if (new_vec == NULL)
305                 goto fail;
306
307 //      ec_strvec_dump(new_vec, stdout);
308
309         child_completed = ec_node_complete_strvec(node->child, new_vec);
310         if (child_completed == NULL)
311                 goto fail;
312
313         ec_strvec_free(new_vec);
314         new_vec = NULL;
315         ec_completed_merge(completed, child_completed);
316
317         return completed;
318
319  fail:
320         ec_strvec_free(new_vec);
321         ec_completed_free(completed);
322         return NULL;
323 }
324
325 static void ec_node_sh_lex_free_priv(struct ec_node *gen_node)
326 {
327         struct ec_node_sh_lex *node = (struct ec_node_sh_lex *)gen_node;
328
329         ec_node_free(node->child);
330 }
331
332 static struct ec_node_type ec_node_sh_lex_type = {
333         .name = "sh_lex",
334         .parse = ec_node_sh_lex_parse,
335         .complete = ec_node_sh_lex_complete,
336         .size = sizeof(struct ec_node_sh_lex),
337         .free_priv = ec_node_sh_lex_free_priv,
338 };
339
340 EC_NODE_TYPE_REGISTER(ec_node_sh_lex_type);
341
342 struct ec_node *ec_node_sh_lex(const char *id, struct ec_node *child)
343 {
344         struct ec_node_sh_lex *node = NULL;
345
346         if (child == NULL)
347                 return NULL;
348
349         node = (struct ec_node_sh_lex *)__ec_node(&ec_node_sh_lex_type, id);
350         if (node == NULL) {
351                 ec_node_free(child);
352                 return NULL;
353         }
354
355         node->child = child;
356
357         return &node->gen;
358 }
359
360 static int ec_node_sh_lex_testcase(void)
361 {
362         struct ec_node *node;
363         int ret = 0;
364
365         node = ec_node_sh_lex(NULL,
366                 EC_NODE_SEQ(NULL,
367                         ec_node_str(NULL, "foo"),
368                         ec_node_option(NULL,
369                                 ec_node_str(NULL, "toto")
370                         ),
371                         ec_node_str(NULL, "bar")
372                 )
373         );
374         if (node == NULL) {
375                 ec_log(EC_LOG_ERR, "cannot create node\n");
376                 return -1;
377         }
378         ret |= EC_TEST_CHECK_PARSE(node, 1, "foo bar");
379         ret |= EC_TEST_CHECK_PARSE(node, 1, "  foo   bar");
380         ret |= EC_TEST_CHECK_PARSE(node, 1, "  'foo' \"bar\"");
381         ret |= EC_TEST_CHECK_PARSE(node, 1, "  'f'oo 'toto' bar");
382         ec_node_free(node);
383
384         /* test completion */
385         node = ec_node_sh_lex(NULL,
386                 EC_NODE_SEQ(NULL,
387                         ec_node_str(NULL, "foo"),
388                         ec_node_option(NULL,
389                                 ec_node_str(NULL, "toto")
390                         ),
391                         ec_node_str(NULL, "bar"),
392                         ec_node_str(NULL, "titi")
393                 )
394         );
395         if (node == NULL) {
396                 ec_log(EC_LOG_ERR, "cannot create node\n");
397                 return -1;
398         }
399         ret |= EC_TEST_CHECK_COMPLETE(node,
400                 "", EC_NODE_ENDLIST,
401                 "foo", EC_NODE_ENDLIST,
402                 "foo");
403         ret |= EC_TEST_CHECK_COMPLETE(node,
404                 " ", EC_NODE_ENDLIST,
405                 "foo", EC_NODE_ENDLIST,
406                 "foo");
407         ret |= EC_TEST_CHECK_COMPLETE(node,
408                 "f", EC_NODE_ENDLIST,
409                 "oo", EC_NODE_ENDLIST,
410                 "oo");
411         ret |= EC_TEST_CHECK_COMPLETE(node,
412                 "foo", EC_NODE_ENDLIST,
413                 "", EC_NODE_ENDLIST,
414                 "");
415         ret |= EC_TEST_CHECK_COMPLETE(node,
416                 "foo ", EC_NODE_ENDLIST,
417                 "bar", "toto", EC_NODE_ENDLIST,
418                 "");
419         ret |= EC_TEST_CHECK_COMPLETE(node,
420                 "foo t", EC_NODE_ENDLIST,
421                 "oto", EC_NODE_ENDLIST,
422                 "oto");
423         ret |= EC_TEST_CHECK_COMPLETE(node,
424                 "foo b", EC_NODE_ENDLIST,
425                 "ar", EC_NODE_ENDLIST,
426                 "ar");
427         ret |= EC_TEST_CHECK_COMPLETE(node,
428                 "foo bar", EC_NODE_ENDLIST,
429                 "", EC_NODE_ENDLIST,
430                 "");
431         ret |= EC_TEST_CHECK_COMPLETE(node,
432                 "foo bar ", EC_NODE_ENDLIST,
433                 "titi", EC_NODE_ENDLIST,
434                 "titi");
435         ret |= EC_TEST_CHECK_COMPLETE(node,
436                 "foo toto bar ", EC_NODE_ENDLIST,
437                 "titi", EC_NODE_ENDLIST,
438                 "titi");
439         ret |= EC_TEST_CHECK_COMPLETE(node,
440                 "x", EC_NODE_ENDLIST,
441                 EC_NODE_ENDLIST,
442                 "");
443         ret |= EC_TEST_CHECK_COMPLETE(node,
444                 "foo barx", EC_NODE_ENDLIST,
445                 EC_NODE_ENDLIST,
446                 "");
447
448         ec_node_free(node);
449         return ret;
450 }
451
452 static struct ec_test ec_node_sh_lex_test = {
453         .name = "node_sh_lex",
454         .test = ec_node_sh_lex_testcase,
455 };
456
457 EC_TEST_REGISTER(ec_node_sh_lex_test);