completion.c (14403B)
1 /* 2 * 3 * This license is set out in https://raw.githubusercontent.com/Broadcom-Network-Switching-Software/OpenBCM/master/Legal/LICENSE file. 4 * 5 * Copyright 2007-2019 Broadcom Inc. All rights reserved. 6 * 7 * File: completion.c 8 * Purpose: API mode completion 9 */ 10 11 #include "bcm/port.h" 12 #include "sal/core/alloc.h" 13 #include "sal/core/libc.h" 14 #include "sal/appl/io.h" 15 #include "shared/util.h" 16 #include "appl/diag/shell.h" 17 #include "appl/diag/system.h" 18 #include "sal/appl/editline/editline.h" 19 #include "sal/appl/sal.h" 20 #include "shared/util.h" 21 #include "context.h" 22 #include "completion.h" 23 #include "cint_porting.h" 24 25 #include <cint_porting.h> 26 #include <cint_internal.h> 27 #include <cint_ast.h> 28 #include <cint_eval_asts.h> 29 #include <cint_eval_ast_print.h> 30 31 /* forward references */ 32 33 STATIC char *no_complete(char *pathname, int *unique); 34 STATIC int no_list(char *pathname, char ***avp); 35 STATIC char *cmd_api_complete(char *pathname, int *unique); 36 STATIC int cmd_api_list_possib(char *pathname, char ***avp); 37 38 39 /* Area to save editline info */ 40 41 STATIC char *(*cmd_api_rl_complete_save)(char *, int *); 42 STATIC int (*cmd_api_rl_list_possib_save)(char *, char ***); 43 44 typedef char *(*rl_complete_f)(char *, int *); 45 typedef int (*rl_list_possib_f)(char *, char ***); 46 47 typedef struct cmd_api_ctx_s { 48 api_mode_completion_type_t ctype; /* completion type */ 49 } cmd_api_ctx_t; 50 51 typedef struct rl_completion_handler_s { 52 rl_complete_f complete; 53 rl_list_possib_f list; 54 } rl_completion_handler_t; 55 56 STATIC rl_completion_handler_t rl_handler[] = { 57 { no_complete, no_list }, 58 { cmd_api_complete, cmd_api_list_possib } 59 }; 60 61 STATIC cmd_api_ctx_t cmd_api_context; 62 63 #define MAX_FUNC_LEN CINT_CONFIG_MAX_VARIABLE_NAME 64 65 typedef struct { 66 char *alloc; /* memory allocation */ 67 char *line; /* input line to complete */ 68 char *match; /* tokenized and reassembled input line */ 69 int ilen; /* length of input line */ 70 int ispace; /* input line space terminated */ 71 int mlen; /* length of match */ 72 int *start; /* completion stem start */ 73 int *end; /* completion stem end */ 74 char *func; /* func scratch buffer */ 75 api_mode_scanner_t scan; /* tokens */ 76 api_mode_context_t ctx; /* token context */ 77 } completion_data_t; 78 79 STATIC int 80 completion_func(api_mode_cint_dt_db_entry_t *entry) 81 { 82 return 83 (entry->private != NULL) || 84 ((entry->dt.flags & CINT_DATATYPE_FLAGS_FUNC) != 0); 85 } 86 87 STATIC int 88 completion_data_free(completion_data_t *cd) 89 { 90 if (cd->alloc != NULL) { 91 sal_free(cd->alloc); 92 cd->alloc = NULL; 93 } 94 api_mode_contextualizer_free(&cd->ctx); 95 (void)api_mode_tokenizer_free(&cd->scan.tok); 96 97 return 0; 98 } 99 100 STATIC int 101 completion_data_init(completion_data_t *cd) 102 { 103 sal_memset(cd, 0, sizeof(*cd)); 104 cd->alloc = sal_alloc(MAX_FUNC_LEN, "api_mode"); 105 106 if (cd->alloc != NULL) { 107 cd->func = cd->alloc; 108 } 109 110 return (cd->alloc == NULL) ? -1 : 0; 111 } 112 113 STATIC void 114 strtr(char *s, int from, int to) 115 { 116 for (;*s != 0;s++) { 117 if (*s == from) { 118 *s = to; 119 } 120 } 121 } 122 123 STATIC int 124 try_match(const char *s1, const char *s2, int start, int end) 125 { 126 int try; 127 128 for (try = start; try < end; try++) { 129 if (s1[try] != s2[try]) { 130 /* char didn't match. back up and try the next name. */ 131 end = try; 132 break; 133 } 134 } 135 136 return end; 137 } 138 139 STATIC char * 140 cmd_api_try_completion(completion_data_t *cd) 141 { 142 int start, end, idx, midx; 143 const char *p; 144 char *cpl; 145 146 idx = cd->ctx.idx; 147 cpl = NULL; 148 sal_strncpy(cd->func, cd->ctx.db->entry[idx].name, MAX_FUNC_LEN-1); 149 start = cd->mlen; 150 end = sal_strlen(cd->func); 151 152 midx = -1; 153 for (;idx < cd->ctx.db->count; idx++) { 154 if (!completion_func(cd->ctx.db->entry + idx)) { 155 continue; 156 } 157 p = cd->ctx.db->entry[idx].name; 158 /* 159 If this is still a substring of what is being completed, 160 then find a completion suffix. This will be the longest 161 substring in common with all function names that have the 162 string being completed as a prefix. 163 164 The completion will not be longer than the first function 165 name at the search point, so it is sufficient to base the 166 completion buffer size string on that. The reason it won't 167 be any longer is because any longer completion would 168 necessarily have the current name as a substring. 169 170 */ 171 if (strncmp(p, cd->match, cd->mlen)) { 172 /* 173 Input is not a substring of the current function at all, 174 so a match is no longer possible. 175 */ 176 break; 177 } 178 179 end = try_match(cd->func, p, start, end); 180 181 if (end <= start) { 182 /* nothing common, so nothing to complete */ 183 cpl = NULL; 184 break; 185 } else { 186 /* found something that will complete. terminate and return 187 the substring */ 188 cd->func[end] = 0; 189 cpl = cd->func + start; 190 midx = idx; /* save index of match */ 191 } 192 } 193 194 if (cd->line && cpl) { 195 int trim = 1; 196 /* if this completion fully matched something, then append a 197 trailing space */ 198 if (cpl && midx >= 0) { 199 /* if the function is fully the same as the function matched, 200 and is not a substring of a subsequent match, ass a separator */ 201 if (!sal_strcmp(cd->func, cd->ctx.db->entry[midx].name) && 202 strncmp(cd->func, cd->ctx.db->entry[midx+1].name, 203 sal_strlen(cd->func))) { 204 CINT_STRCAT(cd->func, " "); 205 trim = 0; 206 } 207 } 208 209 /* Sorry, there *was* nothing to complete if the input line 210 ended in whitespace and the completion was more 211 whitespace. */ 212 if (trim && cd->ispace && cpl[0] == '_') { 213 cpl = NULL; 214 } 215 } 216 217 return cpl; 218 } 219 220 STATIC int 221 near_match(completion_data_t *cd, char *line) 222 { 223 int err; 224 225 err = api_mode_tokenizer(line, &cd->scan.tok); 226 if (!err) { 227 /* contextualizer may not match anything, but that's OK; 228 it returns enough information to allow listing and 229 completion to work. */ 230 (void)api_mode_contextualizer(&cd->scan.tok, &cd->ctx); 231 } 232 233 if (cd->ctx.dt0 == NULL || !completion_func(cd->ctx.dt0)) { 234 /* no datatype found, or datatype was not a function */ 235 return -1; 236 } 237 238 cd->line = line; 239 cd->ilen = sal_strlen(cd->line); 240 cd->match = cd->ctx.match; 241 cd->mlen = sal_strlen(cd->match); 242 cd->start = NULL; 243 cd->end = NULL; 244 cd->ispace = (cd->ilen > 0) && (line[cd->ilen - 1] == ' '); 245 return 0; 246 } 247 248 STATIC int 249 over_match(completion_data_t *cd, char ***listp) 250 { 251 char **list; 252 int len; 253 254 len = 0; 255 list = sal_alloc(sizeof(char *)*1, "api"); 256 if (list) { 257 list[0] = sal_strdup("complete but not unique"); 258 len++; 259 *listp = list; 260 } 261 return len; 262 } 263 264 265 266 STATIC int 267 exact_match(completion_data_t *cd, char ***listp) 268 { 269 char **list; 270 int len; 271 272 len = 0; 273 list = sal_alloc(sizeof(char *)*1, "api"); 274 if (list) { 275 list[0] = sal_strdup("complete"); 276 len++; 277 *listp = list; 278 } 279 280 return len; 281 } 282 283 #define MAXMATCH 100 284 285 STATIC int 286 list_collect(completion_data_t *cd, int start, char **list) 287 { 288 int idx, g_start, fnlen; 289 int add_group, g_end; 290 char *group; 291 const char *fn; 292 int count; 293 294 group = NULL; 295 g_start = -1; 296 count = 0; 297 for (idx = start; idx < cd->ctx.db->count; idx++) { 298 299 /* only match functions */ 300 if (!completion_func(cd->ctx.db->entry + idx)) { 301 continue; 302 } 303 fn = cd->ctx.db->entry[idx].name; 304 305 if (strncmp(fn, cd->match, cd->mlen)) { 306 /* no longer matching prefix */ 307 break; 308 } 309 310 fnlen = sal_strlen(fn); 311 if (fnlen <= cd->mlen) { 312 /* function is the same length or shorter than the input, 313 to there's nothing additional that can match. */ 314 continue; 315 } 316 317 g_start = cd->mlen; 318 /* if not at the end of a word */ 319 if (fn[g_start] != '_') { 320 /* search backwards for beginning of word */ 321 for (; g_start > 0; g_start--) { 322 if (fn[g_start] == '_') { 323 g_start++; 324 break; 325 } 326 } 327 } else /* if (cd->ispace) */ { 328 /* at a word break, so skip the break and start on the 329 following word. */ 330 g_start++; 331 } 332 add_group = 0; 333 334 /* 335 336 Find the end of the word in the current function. Match 337 against the current group if there is one to see if the word 338 in this function needs to be added as a new group. 339 */ 340 for (g_end = g_start; g_end < fnlen; g_end++) { 341 if (fn[g_end] == 0 || fn[g_end] == '_') { 342 /* end-of-string or word boundary */ 343 break; 344 } else if (!add_group && 345 (group == NULL || 346 group[g_end - g_start] != fn[g_end])) { 347 add_group = 1; 348 } 349 } 350 351 if (add_group) { 352 sal_strncpy(cd->func, fn, MAX_FUNC_LEN-1); 353 cd->func[g_end] = 0; 354 group = sal_strdup(cd->func + g_start); 355 list[count] = group; 356 count++; 357 if (count >= MAXMATCH) { 358 break; 359 } 360 } 361 } 362 363 return count; 364 } 365 366 367 /* Function names are composed of words separated by '_' characters. 368 List are either lists of suffix words that are at the same level 369 as the input being matched, or lists at the next level. 370 371 */ 372 373 STATIC int 374 list_matches(completion_data_t *cd, char ***listp) 375 { 376 int count; 377 char **list; 378 379 *cd->func = 0; /* terminate 'func' buffer */ 380 list = sal_alloc(sizeof(char *)*MAXMATCH, "api"); 381 if (list == NULL) { 382 return 0; 383 } 384 385 count = list_collect(cd, cd->ctx.idx, list); 386 387 *listp = list; 388 return count; 389 } 390 391 /* Called from <TAB> */ 392 STATIC char * 393 cmd_api_complete(char *pathname, int *unique) 394 { 395 rl_input_state_t state; 396 char *cpl; 397 const char *target; 398 completion_data_t cd; 399 int err; 400 401 COMPILER_REFERENCE(pathname); 402 rl_input_state(&state); 403 404 if (completion_data_init(&cd) < 0) { 405 return NULL; 406 } 407 408 cpl = NULL; 409 err = near_match(&cd, (char *)state.line); 410 411 if (err) { 412 completion_data_free(&cd); 413 return NULL; 414 } 415 416 if (!completion_func(cd.ctx.dt0)) { 417 /* matched, but not a function */ 418 return NULL; 419 } 420 421 if (!cd.ctx.exact) { 422 /* No match; determine if there is a completion possible */ 423 target = cd.ctx.dt0->name; 424 if (!strncmp(target, cd.match, cd.mlen)) { 425 cpl = cmd_api_try_completion(&cd); 426 if (cpl) { 427 /* copy and convert to 'command line' format */ 428 cpl = sal_strdup(cpl); 429 if (cpl) { 430 strtr(cpl, '_', ' '); 431 } 432 } 433 } 434 } else { 435 /* there is an exact match, so expand the rest of the entry, 436 and add a trailing space. */ 437 char *rest = (char *)cd.ctx.dt0->name + cd.mlen; 438 int rlen = sal_strlen(rest) + 1 + 1; 439 440 cpl = sal_alloc(rlen, "apimode"); 441 sal_strncpy(cpl,rest,rlen-1); 442 if (cd.line[cd.ilen - 1] != ' ') { 443 CINT_STRCAT(cpl, " "); 444 } 445 } 446 *unique = 0; 447 completion_data_free(&cd); 448 return cpl; 449 } 450 451 /* Called from ^[^[ */ 452 STATIC int 453 cmd_api_list_possib(char *pathname, char ***avp) 454 { 455 rl_input_state_t state; 456 char **list = NULL; 457 completion_data_t cd; 458 int llen, err; 459 460 COMPILER_REFERENCE(pathname); 461 rl_input_state(&state); 462 463 if (completion_data_init(&cd) < 0) { 464 return 0; 465 } 466 467 err = near_match(&cd, (char *)state.line); 468 if (err) { 469 completion_data_free(&cd); 470 return 0; 471 } 472 if (!cd.ctx.exact) { 473 /* inexact match */ 474 llen = list_matches(&cd, &list); 475 } else if (cd.ctx.more) { 476 llen = over_match(&cd, &list); 477 } else { 478 llen = exact_match(&cd, &list); 479 } 480 *avp = list; 481 completion_data_free(&cd); 482 return llen; 483 } 484 485 STATIC char * 486 no_complete(char *pathname, int *unique) 487 { 488 COMPILER_REFERENCE(pathname); 489 COMPILER_REFERENCE(unique); 490 return NULL; 491 } 492 493 /* Called from ^[^[ */ 494 STATIC int 495 no_list(char *pathname, char ***avp) 496 { 497 COMPILER_REFERENCE(pathname); 498 COMPILER_REFERENCE(avp); 499 return 0; 500 } 501 502 STATIC char * 503 call_complete(char *pathname, int *unique) 504 { 505 return rl_handler[cmd_api_context.ctype].complete(pathname, unique); 506 } 507 508 /* Called from ^[^[ */ 509 STATIC int 510 call_list_possib(char *pathname, char ***avp) 511 { 512 return rl_handler[cmd_api_context.ctype].list(pathname, avp); 513 } 514 515 int 516 api_mode_completion_initialize(void) 517 { 518 /* Save readline state. This is called during initialization and 519 after command processing (in case other commands use their own 520 completion functions), so this function is very conservative 521 and only saves an existing completion function if it is not a 522 API mode function. */ 523 524 if (rl_complete != call_complete) { 525 /* Use our completion function */ 526 cmd_api_rl_complete_save = rl_complete; 527 rl_complete = call_complete; 528 } 529 530 if (rl_list_possib != call_list_possib) { 531 /* Use our listing function */ 532 cmd_api_rl_list_possib_save = rl_list_possib; 533 rl_list_possib = call_list_possib; 534 } 535 536 api_mode_completion_set(api_mode_completion_api); 537 538 return BCM_E_NONE; 539 } 540 541 int 542 api_mode_completion_uninitialize(void) 543 { 544 /* Restore readline state */ 545 rl_complete = cmd_api_rl_complete_save; 546 rl_list_possib = cmd_api_rl_list_possib_save; 547 548 return BCM_E_NONE; 549 } 550 551 /* set completion type to that indicated, returning previous */ 552 int 553 api_mode_completion_set(api_mode_completion_type_t ctype) 554 { 555 api_mode_completion_type_t current = cmd_api_context.ctype; 556 cmd_api_context.ctype = ctype; 557 558 return current; 559 } 560