initial revision
[ucgine.git] / lib / cmd / ucg_cmd_parse.c
1 /*
2  * Copyright (c) 2009-2015, 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 /*-
29  * Copyright (c) <2010>, Intel Corporation
30  * All rights reserved.
31  *
32  * Redistribution and use in source and binary forms, with or without
33  * modification, are permitted provided that the following conditions
34  * are met:
35  *
36  * - Redistributions of source code must retain the above copyright
37  *   notice, this list of conditions and the following disclaimer.
38  *
39  * - Redistributions in binary form must reproduce the above copyright
40  *   notice, this list of conditions and the following disclaimer in
41  *   the documentation and/or other materials provided with the
42  *   distribution.
43  *
44  * - Neither the name of Intel Corporation nor the names of its
45  *   contributors may be used to endorse or promote products derived
46  *   from this software without specific prior written permission.
47  *
48  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
49  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
50  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
51  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
52  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
53  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
54  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
55  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
56  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
57  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
58  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
59  * OF THE POSSIBILITY OF SUCH DAMAGE.
60  */
61
62 #include <stdio.h>
63 #include <string.h>
64 #include <inttypes.h>
65 #include <ctype.h>
66 #include <errno.h>
67 #include <stdarg.h>
68
69 #include "ucg_cmd_parse.h"
70 #include "ucg_cmd.h"
71
72 //#define debug_printf printf
73 #define debug_printf(args...) do {} while(0)
74
75 /* used internally for ucg_cmd_help() and ucg_cmd_complete() */
76 struct cmd_preparse {
77         int nb_valid_tok; /* number of valid tokens in the buffer */
78         void *opaque;     /* pointer to opaque data */
79         char comp_tok_buf[UCG_CMD_MAX_TOKEN_SIZE]; /* token to complete */
80         size_t comp_tok_len;    /* length of the token to complete */
81         size_t comp_tok_offset; /* offset of token to complete in the line buf */
82 };
83
84 /* isblank() needs _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE, so use our
85  * own. */
86 static int
87 isblank2(char c)
88 {
89         if (c == ' ' || c == '\t' )
90                 return 1;
91         return 0;
92 }
93
94 static int
95 isendofline(char c)
96 {
97         if (c == '\n' || c == '\r' )
98                 return 1;
99         return 0;
100 }
101
102 static int
103 iscomment(char c)
104 {
105         if (c == '#')
106                 return 1;
107         return 0;
108 }
109
110 int
111 ucg_cmd_isendoftoken(char c)
112 {
113         if (!c || iscomment(c) || isblank2(c) || isendofline(c))
114                 return 1;
115         return 0;
116 }
117
118 static unsigned int
119 nb_common_chars(const char * s1, const char * s2)
120 {
121         unsigned int i=0;
122
123         while (*s1==*s2 && *s1 && *s2) {
124                 s1++;
125                 s2++;
126                 i++;
127         }
128         return i;
129 }
130
131 /* quote a string and escape original quotes */
132 int ucg_cmd_quote_token(char *dst, unsigned dstlen, const char *src)
133 {
134         unsigned s = 0, d = 0;
135
136         /* the 2 quotes + '\0' */
137         if (dstlen < 3)
138                 return -EMSGSIZE;
139
140         dst[d++] = '"';
141         while (src[s] != '\0') {
142                 if (d >= (dstlen-2))
143                         return -EMSGSIZE;
144
145                 if (src[s] == '"')
146                         dst[d++] = '\\';
147                 if (src[s] == '\\' && src[s+1] == '"')
148                         dst[d++] = '\\';
149
150                 dst[d++] = src[s++];
151         }
152
153         if (d >= (dstlen-2))
154                 return -EMSGSIZE;
155         dst[d++] = '"';
156         dst[d++] = '\0';
157         return 0;
158 }
159
160 /* Remove quotes and stop when we reach the end of token. Return the
161  * number of "eaten" bytes from the source buffer, or a negative value
162  * on error */
163 int ucg_cmd_get_token(char *dst, unsigned dstlen, const char *src)
164 {
165         unsigned s = 0, d = 0;
166         int quoted = 0;
167
168         /* skip spaces */
169         while (isblank2(src[s]))
170                 s++;
171
172         /* empty token */
173         if (ucg_cmd_isendoftoken(src[s]))
174                 return -EINVAL;
175
176         /* copy token and remove quotes */
177         while (src[s] != '\0') {
178                 if (d >= dstlen)
179                         return -EMSGSIZE;
180
181                 if (ucg_cmd_isendoftoken(src[s]) && quoted == 0)
182                         break;
183
184                 if (src[s] == '\\' && src[s+1] == '"') {
185                         dst[d++] = '"';
186                         s += 2;
187                         continue;
188                 }
189                 if (src[s] == '\\' && src[s+1] == '\\') {
190                         dst[d++] = '\\';
191                         s += 2;
192                         continue;
193                 }
194                 if (src[s] == '"') {
195                         s++;
196                         quoted = !quoted;
197                         continue;
198                 }
199                 dst[d++] = src[s++];
200         }
201
202         /* not enough room in dst buffer */
203         if (d >= (dstlen-1))
204                 return -EMSGSIZE;
205
206         /* end of string during quote */
207         if (quoted)
208                 return -EINVAL;
209
210         dst[d++] = '\0';
211         return  s;
212 }
213
214 /* return the nth token from src and copy it in dst. Return the offset
215  * of the token in src, or a negative value on error. Note: the index
216  * of the first token is 0. */
217 static int cmd_get_nth_token(char *dst, unsigned dstlen, int n,
218         const char *src)
219 {
220         int ret = 0, offset = 0;
221
222         do {
223                 offset += ret;
224
225                 /* skip spaces */
226                 while (isblank2(src[offset]))
227                         offset++;
228
229                 /* get the token starting at offset */
230                 ret = ucg_cmd_get_token(dst, dstlen, src + offset);
231                 if (ret < 0)
232                         return ret;
233
234         } while (n--);
235
236         return offset;
237 }
238
239 /*
240  * try to match the buffer with an instruction (only the first
241  * nb_match_token tokens if != 0).
242  *
243  * Return 0 if we match all the tokens, else the number of matched
244  * tokens, or a negative value on error.
245 */
246 static int
247 match_inst(const ucg_cmd_inst_t *inst, const char *linebuf,
248         unsigned int nb_match_token, void *resbuf, unsigned resbuf_size)
249 {
250         unsigned int token_num = 0;
251         ucg_cmd_tk_hdr_t *token;
252         int n = 0, res;
253         char token_str[UCG_CMD_MAX_TOKEN_SIZE];
254
255         token = inst->tokens[token_num];
256
257         /* check if we match all tokens of inst */
258         while (token) {
259
260                 /* we matched enough tokens, return success */
261                 if (nb_match_token != 0 && token_num >= nb_match_token)
262                         return 0;
263
264                 debug_printf("TK\n");
265
266                 /* copy token and remove quotes */
267                 n = ucg_cmd_get_token(token_str, sizeof(token_str), linebuf);
268                 if (n < 0)
269                         break;
270
271                 /* parse this token */
272                 if (resbuf == NULL)
273                         res = token->ops->parse(token, token_str, NULL, 0);
274                 else {
275                         unsigned rb_sz;
276                         void *rb = (char *)resbuf + token->offset;
277
278                         /* not enough room to store result */
279                         if (token->offset > resbuf_size)
280                                 return -ENOBUFS;
281
282                         rb_sz = resbuf_size - token->offset;
283                         res = token->ops->parse(token, token_str, rb, rb_sz);
284                 }
285
286                 /* does not match this token */
287                 if (res < 0)
288                         break;
289
290                 debug_printf("TK parsed (len=%d)\n", n);
291                 linebuf += n;
292                 token_num ++;
293                 token = inst->tokens[token_num];
294         }
295
296         /* does not match */
297         if (token_num == 0)
298                 return -ENOENT;
299
300         /* we don't match all the tokens */
301         if (token)
302                 return token_num;
303
304         /* are there are some tokens more */
305         while (isblank2(*linebuf))
306                 linebuf++;
307
308         /* end of buf, we match all inst  */
309         if (*linebuf == '\0' || isendofline(*linebuf) || iscomment(*linebuf))
310                 return 0;
311
312         /* garbage after inst */
313         return token_num;
314 }
315
316
317 /* Check if a line buffer is valid and can be parsed or completed. The
318  * parsing stops when \n or \0 is reached. The also function checks
319  * that tokens are correctly quoted. The number of tokens in the
320  * buffer is returned. */
321 static int validate_linebuf(const char *linebuf)
322 {
323         int quoted = 0, comment = 0;
324         int i = 0, nbtok = 0, token = 0;
325
326         while (linebuf[i] != '\0') {
327                 if (isendofline(linebuf[i]) && quoted == 0)
328                         break;
329                 if (comment == 1) {
330                         i++;
331                         continue;
332                 }
333                 if (iscomment(linebuf[i]) && quoted == 0) {
334                         comment = 1;
335                         i ++;
336                         continue;
337                 }
338
339                 /* end of token */
340                 if (isblank2(linebuf[i]) && quoted == 0)
341                         token = 0;
342                 /* new token */
343                 if (!isblank2(linebuf[i]) && token == 0) {
344                         token = 1;
345                         nbtok++;
346                 }
347
348                 if (linebuf[i] == '\\' && linebuf[i+1] == '"') {
349                         i += 2;
350                         continue;
351                 }
352                 if (linebuf[i] == '\\' && linebuf[i+1] == '\\') {
353                         i += 2;
354                         continue;
355                 }
356                 if (linebuf[i] == '"') {
357                         i++;
358                         quoted = !quoted;
359                         continue;
360                 }
361                 i++;
362         }
363         if (quoted)
364                 return UCG_CMD_PARSE_UNTERMINATED_QUOTE;
365         return nbtok;
366 }
367
368 /* Try to parse a buffer according to the specified context. The
369  * argument linebuf must end with \n or \0. */
370 int
371 ucg_cmd_parse(struct ucg_cmd *cl, const char *linebuf,
372         void *opaque)
373 {
374         ucg_cmd_ctx_t *ctx = cl->ctx;
375         const ucg_cmd_inst_t **pinst;
376         const ucg_cmd_inst_t *inst;
377         char result_buf[UCG_CMD_MAX_DSTBUF_SIZE];
378         void (*f)(void *, struct ucg_cmd *, void *) = NULL;
379         void *data = NULL;
380         int ret;
381
382         ret = validate_linebuf(linebuf);
383         if (ret < 0)
384                 return ret;
385         if (ret == 0)
386                 return UCG_CMD_PARSE_EMPTY;
387
388
389         /* parse it !! */
390         for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) {
391                 inst = *pinst;
392                 debug_printf("INST\n");
393
394                 /* fully parsed */
395                 ret = match_inst(inst, linebuf, 0, result_buf,
396                         sizeof(result_buf));
397
398                 if (ret != 0)
399                         continue;
400
401                 debug_printf("INST fully parsed\n");
402
403                 /* if end of buf -> there is no garbage after inst */
404                 if (f != NULL) {
405                         /* more than 1 inst matches */
406                         debug_printf("Ambiguous cmd\n");
407                         return UCG_CMD_PARSE_AMBIGUOUS;
408                 }
409                 f = inst->f;
410                 data = inst->data;
411         }
412
413         /* call func */
414         if (f == NULL)
415                 return UCG_CMD_PARSE_NOMATCH;
416
417         f(result_buf, opaque, data);
418         return UCG_CMD_PARSE_SUCCESS;
419 }
420
421 /* called by ucg_cmd_help() and ucg_cmd_complete() to preparse the
422  * line buffer (the operations done are common to these functions) */
423 static int cmd_preparse(struct cmd_preparse *preparse, const char *buf)
424 {
425         int ret, len, nb_tok;
426
427         /* count the number of tokens in the line buffer */
428         ret = validate_linebuf(buf);
429         if (ret < 0)
430                 return ret;
431         nb_tok = ret;
432
433         /* if last token is not complete, decrement nb_valid_tok */
434         len = strlen(buf);
435         if (nb_tok == 0 || isblank2(buf[len - 1])) {
436                 preparse->nb_valid_tok = nb_tok;
437                 preparse->comp_tok_offset = len;
438                 preparse->comp_tok_buf[0] = '\0';
439                 preparse->comp_tok_len = 0;
440         }
441         else {
442                 preparse->nb_valid_tok = nb_tok - 1;
443
444                 /* get the incomplete token (can be empty) and return its
445                  * offset in the buffer */
446                 preparse->comp_tok_offset =
447                         cmd_get_nth_token(preparse->comp_tok_buf,
448                                 sizeof(preparse->comp_tok_buf),
449                                 preparse->nb_valid_tok,
450                                 buf);
451                 preparse->comp_tok_len = strlen(preparse->comp_tok_buf);
452         }
453
454         return 0;
455 }
456
457 /* Display a contextual help in the command line. The contextual help
458  * depends on the buffer given. */
459 void ucg_cmd_help(struct ucg_cmd *cl, const char *buf)
460 {
461         ucg_cmd_ctx_t *ctx = cl->ctx;
462         ucg_cmd_tk_hdr_t *token;
463         const ucg_cmd_inst_t **pinst;
464         const ucg_cmd_inst_t *inst;
465         struct cmd_preparse preparse;
466         char tmpbuf[UCG_CMD_MAX_DSTBUF_SIZE];
467         char *help_str;
468         size_t n;
469         int iterate;
470
471         cmd_preparse(&preparse, buf);
472
473         debug_printf("display contextual help\n");
474
475         for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) {
476                 inst = *pinst;
477
478                 /* get instruction static help string */
479                 help_str = inst->help_str;
480                 if (help_str == NULL)
481                         help_str = "No help";
482
483                 /* match the beginning of the command */
484                 if (preparse.nb_valid_tok != 0 &&
485                         match_inst(inst, buf, preparse.nb_valid_tok,
486                                 NULL, 0) != 0)
487                         continue;
488
489                 token = inst->tokens[preparse.nb_valid_tok];
490
491                 /* end of inst */
492                 if (token == NULL) {
493                         ucg_cmd_printf(cl, "%-20s %s\n", "<Return>", help_str);
494                         continue;
495                 }
496
497                 /* token matches, but no completion */
498                 if (token->ops->complete_start == NULL ||
499                         token->ops->complete_iterate == NULL)
500                         iterate = 0;
501                 else
502                         iterate = 1;
503
504                 /* store the incomplete token in tmpbuf */
505                 n = preparse.comp_tok_len + 1;
506                 if (n > sizeof(tmpbuf))
507                         n = sizeof(tmpbuf);
508                 snprintf(tmpbuf, sizeof(tmpbuf), "%s", preparse.comp_tok_buf);
509
510                 if (iterate == 1 &&
511                         token->ops->complete_start(token, tmpbuf,
512                                 &preparse.opaque) < 0) {
513                         /* cancel iteration, complete_start() returned
514                          * a negative value, meaning no completion  */
515                         iterate = 0;
516                         if (token->ops->complete_end != NULL)
517                                 token->ops->complete_end(token,
518                                         &preparse.opaque);
519                 }
520
521                 debug_printf("   iterate = %d\n", iterate);
522
523                 if (iterate == 0) {
524                         /* get token dynamic help string */
525                         if ((token->ops->help == NULL) ||
526                                 (token->ops->help(token, tmpbuf,
527                                         sizeof(tmpbuf)) < 0))
528                                 snprintf(tmpbuf, sizeof(tmpbuf), "unknown");
529
530                         ucg_cmd_printf(cl, "%-20s %s\n", tmpbuf, help_str);
531                         continue;
532                 }
533
534                 /* iterate over all possible completion for this inst */
535                 while (token->ops->complete_iterate(token,
536                                 &preparse.opaque,
537                                 tmpbuf,
538                                 sizeof(tmpbuf)) >= 0) {
539
540
541                         debug_printf("   choice <%s>\n", tmpbuf);
542
543                         /* get the token and add it in help buffer */
544                         ucg_cmd_printf(cl, "%-20s %s\n", tmpbuf,
545                                 iterate != 0? help_str : "''");
546
547                         /* don't display help next time */
548                         iterate = 0;
549                 }
550
551                 /* no more completion, go to next inst */
552                 if (token->ops->complete_end != NULL)
553                         token->ops->complete_end(token, &preparse.opaque);
554         }
555 }
556
557 /* try to complete the buffer given as a parameter */
558 int
559 ucg_cmd_complete(struct ucg_cmd *cl, const char *buf,
560         char *dst, unsigned int dstsize)
561 {
562         ucg_cmd_ctx_t *ctx = cl->ctx;
563         ucg_cmd_tk_hdr_t *token;
564         const ucg_cmd_inst_t **pinst;
565         const ucg_cmd_inst_t *inst;
566         struct cmd_preparse preparse;
567         int nb_match = 0;
568         int nb_completion = 0;
569         char completion_buf[UCG_CMD_MAX_TOKEN_SIZE];
570         char tmpbuf[UCG_CMD_MAX_TOKEN_SIZE];
571         int ret;
572         size_t n, completion_len = UCG_CMD_MAX_TOKEN_SIZE;
573
574         debug_printf("%s called\n", __FUNCTION__);
575
576         /* fill the preparse structure that contains infos that will
577          * help us to complete the buffer */
578         ret = cmd_preparse(&preparse, buf);
579         if (ret < 0)
580                 return UCG_CMD_COMPLETE_NONE;
581
582         /* try to complete ! */
583         for (pinst = &ctx->insts[0]; *pinst != NULL; pinst++) {
584                 inst = *pinst;
585
586                 debug_printf("INST\n");
587
588                 /* try to match the first tokens */
589                 if (preparse.nb_valid_tok != 0 &&
590                         match_inst(inst, buf, preparse.nb_valid_tok,
591                                 NULL, 0) != 0)
592                         continue;
593
594                 nb_match ++;
595                 token = inst->tokens[preparse.nb_valid_tok];
596
597                 /* non completable */
598                 if (token == NULL ||
599                         token->ops->complete_start == NULL ||
600                         token->ops->complete_iterate == NULL)
601                         continue;
602
603                 /* store the incomplete token in tmpbuf */
604                 n = preparse.comp_tok_len + 1;
605                 if (n > sizeof(tmpbuf))
606                         n = sizeof(tmpbuf);
607                 snprintf(tmpbuf, n, "%s", preparse.comp_tok_buf);
608
609                 /* non completable */
610                 if (token->ops->complete_start(token, tmpbuf,
611                                 &preparse.opaque) < 0) {
612                         if (token->ops->complete_end != NULL)
613                                 token->ops->complete_end(token,
614                                         &preparse.opaque);
615                         continue;
616                 }
617
618                 /* all possible completion for this token */
619                 while (1) {
620
621                         ret = token->ops->complete_iterate(token,
622                                 &preparse.opaque,
623                                 tmpbuf,
624                                 sizeof(tmpbuf)-1);
625
626                         if (ret < 0)
627                                 break;
628
629                         debug_printf("Completion %s\n", tmpbuf);
630
631                         /* we kept at least the room for one char */
632                         if (ret == 0)
633                                 strcat(tmpbuf, " ");
634
635                         debug_printf("   choice <%s>\n", tmpbuf);
636
637                         /* does the completion match the beginning of
638                          * the word ? */
639                         if (strncmp(preparse.comp_tok_buf, tmpbuf,
640                                         preparse.comp_tok_len))
641                                 continue;
642
643                         /* first one, save the buffer */
644                         if (nb_completion == 0) {
645                                 completion_len = snprintf(completion_buf,
646                                         sizeof(completion_buf),
647                                         "%s", tmpbuf);
648                         }
649                         else {
650                                 n = nb_common_chars(completion_buf, tmpbuf);
651                                 if (n < completion_len)
652                                         completion_len = n;
653                         }
654                         nb_completion ++;
655
656                         /* we cannot add any char, just display help */
657                         if (completion_len == preparse.comp_tok_len)
658                                 break;
659                 }
660                 if (token->ops->complete_end != NULL)
661                         token->ops->complete_end(token, &preparse.opaque);
662
663                 if (completion_len == preparse.comp_tok_len)
664                         break;
665         }
666
667         debug_printf("nb_completion=%d, completion_len=%d\n",
668                 (int)nb_completion, (int)completion_len);
669
670         /* one choice, append chars and return */
671         if (nb_completion == 1) {
672                 snprintf(dst, dstsize, "%s",
673                         completion_buf + preparse.comp_tok_len);
674                 return UCG_CMD_COMPLETE_APPEND;
675         }
676
677         /* many choices, but starting with same chars: append chars
678          * and return */
679         if (nb_completion != 0 && completion_len > preparse.comp_tok_len) {
680                 if (completion_len >= dstsize)
681                         completion_len = dstsize - 1;
682                 strncpy(dst, completion_buf + preparse.comp_tok_len,
683                         completion_len - preparse.comp_tok_len);
684                 dst[completion_len - preparse.comp_tok_len] = '\0';
685                 return UCG_CMD_COMPLETE_APPEND;
686         }
687
688         /* no match, nothing to do */
689         if (nb_match == 0 || nb_completion == 0)
690                 return UCG_CMD_COMPLETE_NONE;
691
692         return UCG_CMD_COMPLETE_MANY;
693 }
694