]> git.proxmox.com Git - mirror_frr.git/blob - lib/command_match.c
lib: allow all characters in WORD tokens
[mirror_frr.git] / lib / command_match.c
1 /*
2 * Input matching routines for CLI backend.
3 *
4 * --
5 * Copyright (C) 2016 Cumulus Networks, Inc.
6 *
7 * This file is part of GNU Zebra.
8 *
9 * GNU Zebra is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2, or (at your option) any
12 * later version.
13 *
14 * GNU Zebra is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with GNU Zebra; see the file COPYING. If not, write to the Free
21 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 * 02111-1307, USA.
23 */
24
25 #include <zebra.h>
26
27 #include "command_match.h"
28 #include "command_parse.h"
29 #include "memory.h"
30
31 DEFINE_MTYPE_STATIC(LIB, CMD_TOKENS, "Command Tokens")
32
33 /* matcher helper prototypes */
34 static int
35 add_nexthops (struct list *, struct graph_node *);
36
37 static struct list *
38 command_match_r (struct graph_node *, vector, unsigned int);
39
40 static int
41 score_precedence (enum cmd_token_type);
42
43 static enum match_type
44 min_match_level (enum cmd_token_type);
45
46 static void
47 del_arglist (struct list *);
48
49 static struct cmd_token *
50 disambiguate_tokens (struct cmd_token *, struct cmd_token *, char *);
51
52 static struct list *
53 disambiguate (struct list *, struct list *, vector, unsigned int);
54
55 int
56 compare_completions (const void *, const void *);
57
58 /* token matcher prototypes */
59 static enum match_type
60 match_token (struct cmd_token *, char *);
61
62 static enum match_type
63 match_ipv4 (const char *);
64
65 static enum match_type
66 match_ipv4_prefix (const char *);
67
68 static enum match_type
69 match_ipv6 (const char *);
70
71 static enum match_type
72 match_ipv6_prefix (const char *);
73
74 static enum match_type
75 match_range (struct cmd_token *, const char *);
76
77 static enum match_type
78 match_word (struct cmd_token *, const char *);
79
80 static enum match_type
81 match_variable (struct cmd_token *, const char *);
82
83 /* matching functions */
84 static enum matcher_rv matcher_rv;
85
86 enum matcher_rv
87 command_match (struct graph *cmdgraph,
88 vector vline,
89 struct list **argv,
90 const struct cmd_element **el)
91 {
92 matcher_rv = MATCHER_NO_MATCH;
93
94 // prepend a dummy token to match that pesky start node
95 vector vvline = vector_init (vline->alloced + 1);
96 vector_set_index (vvline, 0, (void *) XSTRDUP (MTYPE_TMP, "dummy"));
97 memcpy (vvline->index + 1, vline->index, sizeof (void *) * vline->alloced);
98 vvline->active = vline->active + 1;
99
100 struct graph_node *start = vector_slot (cmdgraph->nodes, 0);
101 if ((*argv = command_match_r (start, vvline, 0))) // successful match
102 {
103 struct listnode *head = listhead (*argv);
104 struct listnode *tail = listtail (*argv);
105
106 // delete dummy start node
107 del_cmd_token ((struct cmd_token *) head->data);
108 list_delete_node (*argv, head);
109
110 // get cmd_element out of list tail
111 *el = listgetdata (tail);
112 list_delete_node (*argv, tail);
113
114 // now argv is an ordered list of cmd_token matching the user
115 // input, with each cmd_token->arg holding the corresponding input
116 assert (*el);
117 }
118
119 #ifdef TRACE_MATCHER
120 if (!*el)
121 fprintf (stdout, "No match\n");
122 else
123 fprintf (stdout, "Matched command\n->string %s\n->desc %s\n", (*el)->string, (*el)->doc);
124 #endif
125
126 // free the leader token we alloc'd
127 XFREE (MTYPE_TMP, vector_slot (vvline, 0));
128 // free vector
129 vector_free (vvline);
130
131 return matcher_rv;
132 }
133
134 /**
135 * Builds an argument list given a DFA and a matching input line.
136 *
137 * First the function determines if the node it is passed matches the first
138 * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it
139 * does match, then it saves the input token as the head of an argument list.
140 *
141 * The next step is to see if there is further input in the input line. If
142 * there is not, the current node's children are searched to see if any of them
143 * are leaves (type END_TKN). If this is the case, then the bottom of the
144 * recursion stack has been reached, the leaf is pushed onto the argument list,
145 * the current node is pushed, and the resulting argument list is
146 * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
147 * that there is no match for the input along this path (MATCHER_INCOMPLETE).
148 *
149 * If there is further input, then the function recurses on each of the current
150 * node's children, passing them the input line minus the token that was just
151 * matched. For each child, the return value of the recursive call is
152 * inspected. If it is null, then there is no match for the input along the
153 * subgraph headed by that child. If it is not null, then there is at least one
154 * input match in that subgraph (more on this in a moment).
155 *
156 * If a recursive call on a child returns a non-null value, then it has matched
157 * the input given it on the subgraph that starts with that child. However, due
158 * to the flexibility of the grammar, it is sometimes the case that two or more
159 * child graphs match the same input (two or more of the recursive calls have
160 * non-NULL return values). This is not a valid state, since only one true
161 * match is possible. In order to resolve this conflict, the function keeps a
162 * reference to the child node that most specifically matches the input. This
163 * is done by assigning each node type a precedence. If a child is found to
164 * match the remaining input, then the precedence values of the current
165 * best-matching child and this new match are compared. The node with higher
166 * precedence is kept, and the other match is discarded. Due to the recursive
167 * nature of this function, it is only necessary to compare the precedence of
168 * immediate children, since all subsequent children will already have been
169 * disambiguated in this way.
170 *
171 * In the event that two children are found to match with the same precedence,
172 * then the input is ambiguous for the passed cmd_element and NULL is returned.
173 *
174 * @param[in] start the start node.
175 * @param[in] vline the vectorized input line.
176 * @param[in] n the index of the first input token.
177 * @return A linked list of n elements. The first n-1 elements are pointers to
178 * struct cmd_token and represent the sequence of tokens matched by the input.
179 * The ->arg field of each token points to a copy of the input matched on it.
180 * The final nth element is a pointer to struct cmd_element, which is the
181 * command that was matched.
182 *
183 * If no match was found, the return value is NULL.
184 */
185 static struct list *
186 command_match_r (struct graph_node *start, vector vline, unsigned int n)
187 {
188 assert (n < vector_active (vline));
189
190 // get the minimum match level that can count as a full match
191 struct cmd_token *token = start->data;
192 enum match_type minmatch = min_match_level (token->type);
193
194 // get the current operating input token
195 char *input_token = vector_slot (vline, n);
196
197 #ifdef TRACE_MATCHER
198 fprintf (stdout, "\"%-20s\" matches \"%-30s\" ? ", input_token, token->text);
199 enum match_type mt = match_token (token, input_token);
200 fprintf (stdout, "min: %d - ", minmatch);
201 switch (mt)
202 {
203 case trivial_match:
204 fprintf (stdout, "trivial_match ");
205 break;
206 case no_match:
207 fprintf (stdout, "no_match ");
208 break;
209 case partly_match:
210 fprintf (stdout, "partly_match ");
211 break;
212 case exact_match:
213 fprintf (stdout, "exact_match ");
214 break;
215 }
216 if (mt >= minmatch) fprintf (stdout, " MATCH");
217 fprintf (stdout, "\n");
218 #endif
219
220 // if we don't match this node, die
221 if (match_token (token, input_token) < minmatch)
222 return NULL;
223
224 // pointers for iterating linklist
225 struct listnode *ln;
226 struct graph_node *gn;
227
228 // get all possible nexthops
229 struct list *next = list_new();
230 add_nexthops (next, start);
231
232 // determine the best match
233 int ambiguous = 0;
234 struct list *currbest = NULL;
235 for (ALL_LIST_ELEMENTS_RO (next,ln,gn))
236 {
237 // if we've matched all input we're looking for END_TKN
238 if (n+1 == vector_active (vline))
239 {
240 struct cmd_token *tok = gn->data;
241 if (tok->type == END_TKN)
242 {
243 if (currbest) // there is more than one END_TKN in the follow set
244 {
245 ambiguous = 1;
246 break;
247 }
248 currbest = list_new();
249 // node should have one child node with the element
250 struct graph_node *leaf = vector_slot (gn->to, 0);
251 // last node in the list will hold the cmd_element;
252 // this is important because list_delete() expects
253 // that all nodes have the same data type, so when
254 // deleting this list the last node must be
255 // manually deleted
256 struct cmd_element *el = leaf->data;
257 listnode_add (currbest, el);
258 currbest->del = (void (*)(void *)) &del_cmd_token;
259 // do not break immediately; continue walking through the follow set
260 // to ensure that there is exactly one END_TKN
261 }
262 continue;
263 }
264
265 // else recurse on candidate child node
266 struct list *result = command_match_r (gn, vline, n+1);
267
268 // save the best match
269 if (result && currbest)
270 {
271 // pick the best of two matches
272 struct list *newbest = disambiguate (currbest, result, vline, n+1);
273 // set ambiguity flag
274 ambiguous = !newbest || (ambiguous && newbest == currbest);
275 // delete the unnecessary result
276 struct list *todelete = ((newbest && newbest == result) ? currbest : result);
277 del_arglist (todelete);
278
279 currbest = newbest ? newbest : currbest;
280 }
281 else if (result)
282 currbest = result;
283 }
284
285 if (currbest)
286 {
287 if (ambiguous)
288 {
289 del_arglist (currbest);
290 currbest = NULL;
291 matcher_rv = MATCHER_AMBIGUOUS;
292 }
293 else
294 {
295 // copy token, set arg and prepend to currbest
296 struct cmd_token *token = start->data;
297 struct cmd_token *copy = copy_cmd_token (token);
298 copy->arg = XSTRDUP (MTYPE_CMD_TOKENS, input_token);
299 listnode_add_before (currbest, currbest->head, copy);
300 matcher_rv = MATCHER_OK;
301 }
302 }
303 else if (n+1 == vector_active (vline) && matcher_rv == MATCHER_NO_MATCH)
304 matcher_rv = MATCHER_INCOMPLETE;
305
306 // cleanup
307 list_delete (next);
308
309 return currbest;
310 }
311
312 enum matcher_rv
313 command_complete (struct graph *graph,
314 vector vline,
315 struct list **completions)
316 {
317 // pointer to next input token to match
318 char *input_token;
319
320 struct list *current = list_new(), // current nodes to match input token against
321 *next = list_new(); // possible next hops after current input token
322
323 // pointers used for iterating lists
324 struct graph_node *gn;
325 struct listnode *node;
326
327 // add all children of start node to list
328 struct graph_node *start = vector_slot (graph->nodes, 0);
329 add_nexthops (next, start);
330
331 unsigned int idx;
332 for (idx = 0; idx < vector_active (vline) && next->count > 0; idx++)
333 {
334 list_delete (current);
335 current = next;
336 next = list_new();
337
338 input_token = vector_slot (vline, idx);
339
340 for (ALL_LIST_ELEMENTS_RO (current,node,gn))
341 {
342 struct cmd_token *token = gn->data;
343
344 if (token->attr == CMD_ATTR_HIDDEN || token->attr == CMD_ATTR_DEPRECATED)
345 continue;
346
347 enum match_type minmatch = min_match_level (token->type);
348 #ifdef TRACE_MATCHER
349 fprintf (stdout, "\"%s\" matches \"%s\" (%d) ? ", input_token, token->text, token->type);
350 #endif
351
352 switch (match_token (token, input_token))
353 {
354 case trivial_match:
355 #ifdef TRACE_MATCHER
356 fprintf (stdout, "trivial_match\n");
357 #endif
358 case partly_match:
359 #ifdef TRACE_MATCHER
360 fprintf (stdout, "partly_match\n");
361 #endif
362 if (idx == vector_active (vline) - 1)
363 {
364 listnode_add (next, gn);
365 break;
366 }
367 if (minmatch > partly_match)
368 break;
369 case exact_match:
370 #ifdef TRACE_MATCHER
371 fprintf (stdout, "exact_match\n");
372 #endif
373 add_nexthops (next, gn);
374 break;
375 default:
376 #ifdef TRACE_MATCHER
377 fprintf (stdout, "no_match\n");
378 #endif
379 break;
380 }
381 }
382 }
383
384 /* Variable summary
385 * -----------------------------------------------------------------
386 * token = last input token processed
387 * idx = index in `command` of last token processed
388 * current = set of all transitions from the previous input token
389 * next = set of all nodes reachable from all nodes in `matched`
390 */
391
392 matcher_rv =
393 idx == vector_active(vline) && next->count ?
394 MATCHER_OK :
395 MATCHER_NO_MATCH;
396
397 *completions = NULL;
398 if (!MATCHER_ERROR(matcher_rv))
399 {
400 // extract cmd_token into list
401 *completions = list_new ();
402 for (ALL_LIST_ELEMENTS_RO (next,node,gn))
403 listnode_add (*completions, gn->data);
404 }
405
406 list_delete (current);
407 list_delete (next);
408
409 return matcher_rv;
410 }
411
412 /**
413 * Adds all children that are reachable by one parser hop to the given list.
414 * NUL_TKN, SELECTOR_TKN, and OPTION_TKN nodes are treated as transparent.
415 *
416 * @param[in] list to add the nexthops to
417 * @param[in] node to start calculating nexthops from
418 * @return the number of children added to the list
419 */
420 static int
421 add_nexthops (struct list *list, struct graph_node *node)
422 {
423 int added = 0;
424 struct graph_node *child;
425 for (unsigned int i = 0; i < vector_active (node->to); i++)
426 {
427 child = vector_slot (node->to, i);
428 struct cmd_token *token = child->data;
429 switch (token->type)
430 {
431 case OPTION_TKN:
432 case SELECTOR_TKN:
433 case NUL_TKN:
434 added += add_nexthops (list, child);
435 break;
436 default:
437 listnode_add (list, child);
438 added++;
439 }
440 }
441
442 return added;
443 }
444
445 /**
446 * Determines the node types for which a partial match may count as a full
447 * match. Enables command abbrevations.
448 *
449 * @param[in] type node type
450 * @return minimum match level needed to for a token to fully match
451 */
452 static enum match_type
453 min_match_level (enum cmd_token_type type)
454 {
455 switch (type)
456 {
457 // anything matches a start node, for the sake of recursion
458 case START_TKN:
459 return no_match;
460 // allowing words to partly match enables command abbreviation
461 case WORD_TKN:
462 return partly_match;
463 default:
464 return exact_match;
465 }
466 }
467
468 /**
469 * Assigns precedence scores to node types.
470 *
471 * @param[in] type node type to score
472 * @return precedence score
473 */
474 static int
475 score_precedence (enum cmd_token_type type)
476 {
477 switch (type)
478 {
479 // some of these are mutually exclusive, so they share
480 // the same precedence value
481 case IPV4_TKN:
482 case IPV4_PREFIX_TKN:
483 case IPV6_TKN:
484 case IPV6_PREFIX_TKN:
485 case RANGE_TKN:
486 return 2;
487 case WORD_TKN:
488 return 3;
489 case VARIABLE_TKN:
490 return 4;
491 default:
492 return 10;
493 }
494 }
495
496 /**
497 * Picks the better of two possible matches for a token.
498 *
499 * @param[in] first candidate node matching token
500 * @param[in] second candidate node matching token
501 * @param[in] token the token being matched
502 * @return the best-matching node, or NULL if the two are entirely ambiguous
503 */
504 static struct cmd_token *
505 disambiguate_tokens (struct cmd_token *first,
506 struct cmd_token *second,
507 char *input_token)
508 {
509 // if the types are different, simply go off of type precedence
510 if (first->type != second->type)
511 {
512 int firstprec = score_precedence (first->type);
513 int secndprec = score_precedence (second->type);
514 if (firstprec != secndprec)
515 return firstprec < secndprec ? first : second;
516 else
517 return NULL;
518 }
519
520 // if they're the same, return the more exact match
521 enum match_type fmtype = match_token (first, input_token);
522 enum match_type smtype = match_token (second, input_token);
523 if (fmtype != smtype)
524 return fmtype > smtype ? first : second;
525
526 return NULL;
527 }
528
529 /**
530 * Picks the better of two possible matches for an input line.
531 *
532 * @param[in] first candidate list of cmd_token matching vline
533 * @param[in] second candidate list of cmd_token matching vline
534 * @param[in] vline the input line being matched
535 * @param[in] n index into vline to start comparing at
536 * @return the best-matching list, or NULL if the two are entirely ambiguous
537 */
538 static struct list *
539 disambiguate (struct list *first,
540 struct list *second,
541 vector vline,
542 unsigned int n)
543 {
544 // doesn't make sense for these to be inequal length
545 assert (first->count == second->count);
546 assert (first->count == vector_active (vline) - n+1);
547
548 struct listnode *fnode = listhead (first),
549 *snode = listhead (second);
550 struct cmd_token *ftok = listgetdata (fnode),
551 *stok = listgetdata (snode),
552 *best = NULL;
553
554 // compare each token, if one matches better use that one
555 for (unsigned int i = n; i < vector_active (vline); i++)
556 {
557 char *token = vector_slot(vline, i);
558 if ((best = disambiguate_tokens (ftok, stok, token)))
559 return best == ftok ? first : second;
560 fnode = listnextnode (fnode);
561 snode = listnextnode (snode);
562 ftok = listgetdata (fnode);
563 stok = listgetdata (snode);
564 }
565
566 return NULL;
567 }
568
569 /*
570 * Deletion function for arglist.
571 *
572 * Since list->del for arglists expects all listnode->data to hold cmd_token,
573 * but arglists have cmd_element as the data for the tail, this function
574 * manually deletes the tail before deleting the rest of the list as usual.
575 *
576 * The cmd_element at the end is *not* a copy. It is the one and only.
577 *
578 * @param list the arglist to delete
579 */
580 static void
581 del_arglist (struct list *list)
582 {
583 // manually delete last node
584 struct listnode *tail = listtail (list);
585 tail->data = NULL;
586 list_delete_node (list, tail);
587
588 // delete the rest of the list as usual
589 list_delete (list);
590 }
591
592 /*---------- token level matching functions ----------*/
593
594 static enum match_type
595 match_token (struct cmd_token *token, char *input_token)
596 {
597 // nothing trivially matches everything
598 if (!input_token)
599 return trivial_match;
600
601 switch (token->type) {
602 case WORD_TKN:
603 return match_word (token, input_token);
604 case IPV4_TKN:
605 return match_ipv4 (input_token);
606 case IPV4_PREFIX_TKN:
607 return match_ipv4_prefix (input_token);
608 case IPV6_TKN:
609 return match_ipv6 (input_token);
610 case IPV6_PREFIX_TKN:
611 return match_ipv6_prefix (input_token);
612 case RANGE_TKN:
613 return match_range (token, input_token);
614 case VARIABLE_TKN:
615 return match_variable (token, input_token);
616 case END_TKN:
617 default:
618 return no_match;
619 }
620 }
621
622 #define IPV4_ADDR_STR "0123456789."
623 #define IPV4_PREFIX_STR "0123456789./"
624
625 static enum match_type
626 match_ipv4 (const char *str)
627 {
628 const char *sp;
629 int dots = 0, nums = 0;
630 char buf[4];
631
632 for (;;)
633 {
634 memset (buf, 0, sizeof (buf));
635 sp = str;
636 while (*str != '\0')
637 {
638 if (*str == '.')
639 {
640 if (dots >= 3)
641 return no_match;
642
643 if (*(str + 1) == '.')
644 return no_match;
645
646 if (*(str + 1) == '\0')
647 return partly_match;
648
649 dots++;
650 break;
651 }
652 if (!isdigit ((int) *str))
653 return no_match;
654
655 str++;
656 }
657
658 if (str - sp > 3)
659 return no_match;
660
661 strncpy (buf, sp, str - sp);
662 if (atoi (buf) > 255)
663 return no_match;
664
665 nums++;
666
667 if (*str == '\0')
668 break;
669
670 str++;
671 }
672
673 if (nums < 4)
674 return partly_match;
675
676 return exact_match;
677 }
678
679 static enum match_type
680 match_ipv4_prefix (const char *str)
681 {
682 const char *sp;
683 int dots = 0;
684 char buf[4];
685
686 for (;;)
687 {
688 memset (buf, 0, sizeof (buf));
689 sp = str;
690 while (*str != '\0' && *str != '/')
691 {
692 if (*str == '.')
693 {
694 if (dots == 3)
695 return no_match;
696
697 if (*(str + 1) == '.' || *(str + 1) == '/')
698 return no_match;
699
700 if (*(str + 1) == '\0')
701 return partly_match;
702
703 dots++;
704 break;
705 }
706
707 if (!isdigit ((int) *str))
708 return no_match;
709
710 str++;
711 }
712
713 if (str - sp > 3)
714 return no_match;
715
716 strncpy (buf, sp, str - sp);
717 if (atoi (buf) > 255)
718 return no_match;
719
720 if (dots == 3)
721 {
722 if (*str == '/')
723 {
724 if (*(str + 1) == '\0')
725 return partly_match;
726
727 str++;
728 break;
729 }
730 else if (*str == '\0')
731 return partly_match;
732 }
733
734 if (*str == '\0')
735 return partly_match;
736
737 str++;
738 }
739
740 sp = str;
741 while (*str != '\0')
742 {
743 if (!isdigit ((int) *str))
744 return no_match;
745
746 str++;
747 }
748
749 if (atoi (sp) > 32)
750 return no_match;
751
752 return exact_match;
753 }
754
755 #ifdef HAVE_IPV6
756 #define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
757 #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
758
759 static enum match_type
760 match_ipv6 (const char *str)
761 {
762 struct sockaddr_in6 sin6_dummy;
763 int ret;
764
765 if (strspn (str, IPV6_ADDR_STR) != strlen (str))
766 return no_match;
767
768 ret = inet_pton(AF_INET6, str, &sin6_dummy.sin6_addr);
769
770 if (ret == 1)
771 return exact_match;
772
773 return no_match;
774 }
775
776 static enum match_type
777 match_ipv6_prefix (const char *str)
778 {
779 struct sockaddr_in6 sin6_dummy;
780 const char *delim = "/\0";
781 char *tofree, *dupe, *prefix, *mask, *endptr;
782 int nmask = -1;
783
784 if (strspn (str, IPV6_PREFIX_STR) != strlen (str))
785 return no_match;
786
787 /* tokenize to prefix + mask */
788 tofree = dupe = XSTRDUP (MTYPE_TMP, str);
789 prefix = strsep (&dupe, delim);
790 mask = dupe;
791
792 /* validate prefix */
793 if (inet_pton (AF_INET6, prefix, &sin6_dummy.sin6_addr) != 1)
794 {
795 XFREE (MTYPE_TMP, tofree);
796 return no_match;
797 }
798
799 /* validate mask */
800 if (!mask)
801 {
802 XFREE (MTYPE_TMP, tofree);
803 return partly_match;
804 }
805
806 nmask = strtoimax (mask, &endptr, 10);
807 if (*endptr != '\0' || nmask < 0 || nmask > 128)
808 {
809 XFREE (MTYPE_TMP, tofree);
810 return no_match;
811 }
812
813 XFREE (MTYPE_TMP, tofree);
814 return exact_match;
815 }
816 #endif
817
818 static enum match_type
819 match_range (struct cmd_token *token, const char *str)
820 {
821 assert (token->type == RANGE_TKN);
822
823 char *endptr = NULL;
824 long long val;
825
826 val = strtoll (str, &endptr, 10);
827 if (*endptr != '\0')
828 return no_match;
829
830 if (val < token->min || val > token->max)
831 return no_match;
832 else
833 return exact_match;
834 }
835
836 static enum match_type
837 match_word (struct cmd_token *token, const char *word)
838 {
839 assert (token->type == WORD_TKN);
840
841 // if the passed token is 0 length, partly match
842 if (!strlen(word))
843 return partly_match;
844
845 // if the passed token is strictly a prefix of the full word, partly match
846 if (strlen (word) < strlen (token->text))
847 return !strncmp (token->text, word, strlen (word)) ?
848 partly_match :
849 no_match;
850
851 // if they are the same length and exactly equal, exact match
852 else if (strlen (word) == strlen (token->text))
853 return !strncmp (token->text, word, strlen (word)) ? exact_match : no_match;
854
855 return no_match;
856 }
857
858 static enum match_type
859 match_variable (struct cmd_token *token, const char *word)
860 {
861 assert (token->type == VARIABLE_TKN);
862 return exact_match;
863 }