1 /* $NetBSD: enum.c,v 1.10 2024/08/18 20:47:24 christos Exp $ */ 2 3 4 /** 5 * \file enumeration.c 6 * 7 * Handle options with enumeration names and bit mask bit names 8 * for their arguments. 9 * 10 * @addtogroup autoopts 11 * @{ 12 */ 13 /* 14 * This routine will run run-on options through a pager so the 15 * user may examine, print or edit them at their leisure. 16 * 17 * This file is part of AutoOpts, a companion to AutoGen. 18 * AutoOpts is free software. 19 * AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved 20 * 21 * AutoOpts is available under any one of two licenses. The license 22 * in use must be one of these two and the choice is under the control 23 * of the user of the license. 24 * 25 * The GNU Lesser General Public License, version 3 or later 26 * See the files "COPYING.lgplv3" and "COPYING.gplv3" 27 * 28 * The Modified Berkeley Software Distribution License 29 * See the file "COPYING.mbsd" 30 * 31 * These files have the following sha256 sums: 32 * 33 * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 34 * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 35 * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 36 */ 37 38 static void 39 enum_err(tOptions * pOpts, tOptDesc * pOD, 40 char const * const * paz_names, int name_ct) 41 { 42 size_t max_len = 0; 43 size_t ttl_len = 0; 44 int ct_down = name_ct; 45 int hidden = 0; 46 47 /* 48 * A real "pOpts" pointer means someone messed up. Give a real error. 49 */ 50 if (pOpts > OPTPROC_EMIT_LIMIT) 51 fprintf(option_usage_fp, pz_enum_err_fmt, pOpts->pzProgName, 52 pOD->optArg.argString, pOD->pz_Name); 53 54 fprintf(option_usage_fp, zValidKeys, pOD->pz_Name); 55 56 /* 57 * If the first name starts with this funny character, then we have 58 * a first value with an unspellable name. You cannot specify it. 59 * So, we don't list it either. 60 */ 61 if (**paz_names == 0x7F) { 62 paz_names++; 63 hidden = 1; 64 ct_down = --name_ct; 65 } 66 67 /* 68 * Figure out the maximum length of any name, plus the total length 69 * of all the names. 70 */ 71 { 72 char const * const * paz = paz_names; 73 74 do { 75 size_t len = strlen(*(paz++)) + 1; 76 if (len > max_len) 77 max_len = len; 78 ttl_len += len; 79 } while (--ct_down > 0); 80 81 ct_down = name_ct; 82 } 83 84 /* 85 * IF any one entry is about 1/2 line or longer, print one per line 86 */ 87 if (max_len > 35) { 88 do { 89 fprintf(option_usage_fp, ENUM_ERR_LINE, *(paz_names++)); 90 } while (--ct_down > 0); 91 } 92 93 /* 94 * ELSE IF they all fit on one line, then do so. 95 */ 96 else if (ttl_len < 76) { 97 fputc(' ', option_usage_fp); 98 do { 99 fputc(' ', option_usage_fp); 100 fputs(*(paz_names++), option_usage_fp); 101 } while (--ct_down > 0); 102 fputc(NL, option_usage_fp); 103 } 104 105 /* 106 * Otherwise, columnize the output 107 */ 108 else { 109 unsigned int ent_no = 0; 110 char fmt[16]; /* format for all-but-last entries on a line */ 111 112 if (snprintf(fmt, 16, ENUM_ERR_WIDTH, (int)max_len) >= 16) 113 option_exits(EXIT_FAILURE); 114 max_len = 78 / max_len; /* max_len is now max entries on a line */ 115 fputs(TWO_SPACES_STR, option_usage_fp); 116 117 /* 118 * Loop through all but the last entry 119 */ 120 ct_down = name_ct; 121 while (--ct_down > 0) { 122 if (++ent_no == max_len) { 123 /* 124 * Last entry on a line. Start next line, too. 125 */ 126 fprintf(option_usage_fp, NLSTR_SPACE_FMT, *(paz_names++)); 127 ent_no = 0; 128 } 129 130 else 131 fprintf(option_usage_fp, fmt, *(paz_names++) ); 132 } 133 fprintf(option_usage_fp, NLSTR_FMT, *paz_names); 134 } 135 136 if (pOpts > OPTPROC_EMIT_LIMIT) { 137 fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden); 138 139 (*(pOpts->pUsageProc))(pOpts, EXIT_FAILURE); 140 /* NOTREACHED */ 141 } 142 143 if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_MEMBERSHIP) { 144 fprintf(option_usage_fp, zLowerBits, name_ct); 145 fputs(zSetMemberSettings, option_usage_fp); 146 } else { 147 fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden); 148 } 149 } 150 151 /** 152 * Convert a name or number into a binary number. 153 * "~0" and "-1" will be converted to the largest value in the enumeration. 154 * 155 * @param name the keyword name (number) to convert 156 * @param pOpts the program's option descriptor 157 * @param pOD the option descriptor for this option 158 * @param paz_names the list of keywords for this option 159 * @param name_ct the count of keywords 160 */ 161 static uintptr_t 162 find_name(char const * name, tOptions * pOpts, tOptDesc * pOD, 163 char const * const * paz_names, unsigned int name_ct) 164 { 165 /* 166 * Return the matching index as a pointer sized integer. 167 * The result gets stashed in a char * pointer. 168 */ 169 uintptr_t res = name_ct; 170 size_t len = strlen(name); 171 uintptr_t idx; 172 173 if (IS_DEC_DIGIT_CHAR(*name)) { 174 char * pz = VOIDP(name); 175 unsigned long val = strtoul(pz, &pz, 0); 176 if ((*pz == NUL) && (val < name_ct)) 177 return (uintptr_t)val; 178 pz_enum_err_fmt = znum_too_large; 179 option_usage_fp = stderr; 180 enum_err(pOpts, pOD, paz_names, (int)name_ct); 181 return name_ct; 182 } 183 184 if (IS_INVERSION_CHAR(*name) && (name[2] == NUL)) { 185 if ( ((name[0] == '~') && (name[1] == '0')) 186 || ((name[0] == '-') && (name[1] == '1'))) 187 return (uintptr_t)(name_ct - 1); 188 goto oops; 189 } 190 191 /* 192 * Look for an exact match, but remember any partial matches. 193 * Multiple partial matches means we have an ambiguous match. 194 */ 195 for (idx = 0; idx < name_ct; idx++) { 196 if (strncmp(paz_names[idx], name, len) == 0) { 197 if (paz_names[idx][len] == NUL) 198 return idx; /* full match */ 199 200 if (res == name_ct) 201 res = idx; /* save partial match */ 202 else 203 res = (uintptr_t)~0; /* may yet find full match */ 204 } 205 } 206 207 if (res < name_ct) 208 return res; /* partial match */ 209 210 oops: 211 212 pz_enum_err_fmt = (res == name_ct) ? zNoKey : zambiguous_key; 213 option_usage_fp = stderr; 214 enum_err(pOpts, pOD, paz_names, (int)name_ct); 215 return name_ct; 216 } 217 218 219 /*=export_func optionKeywordName 220 * what: Convert between enumeration values and strings 221 * private: 222 * 223 * arg: tOptDesc *, pOD, enumeration option description 224 * arg: unsigned int, enum_val, the enumeration value to map 225 * 226 * ret_type: char const * 227 * ret_desc: the enumeration name from const memory 228 * 229 * doc: This converts an enumeration value into the matching string. 230 =*/ 231 char const * 232 optionKeywordName(tOptDesc * pOD, unsigned int enum_val) 233 { 234 tOptDesc od = { .optIndex = 0 }; 235 od.optArg.argEnum = enum_val; 236 237 (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, &od ); 238 return od.optArg.argString; 239 } 240 241 242 /*=export_func optionEnumerationVal 243 * what: Convert from a string to an enumeration value 244 * private: 245 * 246 * arg: tOptions *, pOpts, the program options descriptor 247 * arg: tOptDesc *, pOD, enumeration option description 248 * arg: char const * const *, paz_names, list of enumeration names 249 * arg: unsigned int, name_ct, number of names in list 250 * 251 * ret_type: uintptr_t 252 * ret_desc: the enumeration value 253 * 254 * doc: This converts the optArg.argString string from the option description 255 * into the index corresponding to an entry in the name list. 256 * This will match the generated enumeration value. 257 * Full matches are always accepted. Partial matches are accepted 258 * if there is only one partial match. 259 =*/ 260 uintptr_t 261 optionEnumerationVal(tOptions * pOpts, tOptDesc * pOD, 262 char const * const * paz_names, unsigned int name_ct) 263 { 264 uintptr_t res = 0UL; 265 266 /* 267 * IF the program option descriptor pointer is invalid, 268 * then it is some sort of special request. 269 */ 270 switch ((uintptr_t)pOpts) { 271 case (uintptr_t)OPTPROC_EMIT_USAGE: 272 /* 273 * print the list of enumeration names. 274 */ 275 enum_err(pOpts, pOD, paz_names, (int)name_ct); 276 break; 277 278 case (uintptr_t)OPTPROC_EMIT_SHELL: 279 { 280 unsigned int ix = (unsigned int)pOD->optArg.argEnum; 281 /* 282 * print the name string. 283 */ 284 if (ix >= name_ct) 285 printf(INVALID_FMT, ix); 286 else 287 fputs(paz_names[ ix ], stdout); 288 289 break; 290 } 291 292 case (uintptr_t)OPTPROC_RETURN_VALNAME: 293 { 294 unsigned int ix = (unsigned int)pOD->optArg.argEnum; 295 /* 296 * Replace the enumeration value with the name string. 297 */ 298 if (ix >= name_ct) 299 return (uintptr_t)INVALID_STR; 300 301 pOD->optArg.argString = paz_names[ix]; 302 break; 303 } 304 305 default: 306 if ((pOD->fOptState & OPTST_RESET) != 0) 307 break; 308 309 res = find_name(pOD->optArg.argString, pOpts, pOD, paz_names, name_ct); 310 311 if (pOD->fOptState & OPTST_ALLOC_ARG) { 312 AGFREE(pOD->optArg.argString); 313 pOD->fOptState &= ~OPTST_ALLOC_ARG; 314 pOD->optArg.argString = NULL; 315 } 316 } 317 318 return res; 319 } 320 321 static void 322 set_memb_shell(tOptions * pOpts, tOptDesc * pOD, char const * const * paz_names, 323 unsigned int name_ct) 324 { 325 /* 326 * print the name string. 327 */ 328 unsigned int ix = 0; 329 uintptr_t bits = (uintptr_t)pOD->optCookie; 330 size_t len = 0; 331 332 (void)pOpts; 333 bits &= ((uintptr_t)1 << (uintptr_t)name_ct) - (uintptr_t)1; 334 335 while (bits != 0) { 336 if (bits & 1) { 337 if (len++ > 0) fputs(OR_STR, stdout); 338 fputs(paz_names[ix], stdout); 339 } 340 if (++ix >= name_ct) break; 341 bits >>= 1; 342 } 343 } 344 345 static void 346 set_memb_names(tOptions * opts, tOptDesc * od, char const * const * nm_list, 347 unsigned int nm_ct) 348 { 349 char * pz; 350 uintptr_t mask = (1UL << (uintptr_t)nm_ct) - 1UL; 351 uintptr_t bits = (uintptr_t)od->optCookie & mask; 352 unsigned int ix = 0; 353 size_t len = 1; 354 355 /* 356 * Replace the enumeration value with the name string. 357 * First, determine the needed length, then allocate and fill in. 358 */ 359 while (bits != 0) { 360 if (bits & 1) 361 len += strlen(nm_list[ix]) + PLUS_STR_LEN + 1; 362 if (++ix >= nm_ct) break; 363 bits >>= 1; 364 } 365 366 od->optArg.argString = pz = AGALOC(len, "enum"); 367 bits = (uintptr_t)od->optCookie & mask; 368 if (bits == 0) { 369 *pz = NUL; 370 return; 371 } 372 373 for (ix = 0; ; ix++) { 374 size_t nln; 375 int doit = bits & 1; 376 377 bits >>= 1; 378 if (doit == 0) 379 continue; 380 381 nln = strlen(nm_list[ix]); 382 memcpy(pz, nm_list[ix], nln); 383 pz += nln; 384 if (bits == 0) 385 break; 386 memcpy(pz, PLUS_STR, PLUS_STR_LEN); 387 pz += PLUS_STR_LEN; 388 } 389 *pz = NUL; 390 (void)opts; 391 } 392 393 /** 394 * Check membership start conditions. An equal character (@samp{=}) says to 395 * clear the result and not carry over any residual value. A carat 396 * (@samp{^}), which may follow the equal character, says to invert the 397 * result. The scanning pointer is advanced past these characters and any 398 * leading white space. Invalid sequences are indicated by setting the 399 * scanning pointer to NULL. 400 * 401 * @param od the set membership option description 402 * @param argp a pointer to the string scanning pointer 403 * @param invert a pointer to the boolean inversion indicator 404 * 405 * @returns either zero or the original value for the optCookie. 406 */ 407 static uintptr_t 408 check_membership_start(tOptDesc * od, char const ** argp, bool * invert) 409 { 410 uintptr_t res = (uintptr_t)od->optCookie; 411 char const * arg = SPN_WHITESPACE_CHARS(od->optArg.argString); 412 if ((arg == NULL) || (*arg == NUL)) 413 goto member_start_fail; 414 415 *invert = false; 416 417 switch (*arg) { 418 case '=': 419 res = 0UL; 420 arg = SPN_WHITESPACE_CHARS(arg + 1); 421 switch (*arg) { 422 case '=': case ',': 423 goto member_start_fail; 424 case '^': 425 goto inversion; 426 default: 427 break; 428 } 429 break; 430 431 case '^': 432 inversion: 433 *invert = true; 434 arg = SPN_WHITESPACE_CHARS(arg + 1); 435 if (*arg != ',') 436 break; 437 /* FALLTHROUGH */ 438 439 case ',': 440 goto member_start_fail; 441 442 default: 443 break; 444 } 445 446 *argp = arg; 447 return res; 448 449 member_start_fail: 450 *argp = NULL; 451 return 0UL; 452 } 453 454 /** 455 * convert a name to a bit. Look up a name string to get a bit number 456 * and shift the value "1" left that number of bits. 457 * 458 * @param opts program options descriptor 459 * @param od the set membership option description 460 * @param pz address of the start of the bit name 461 * @param nm_list the list of names for this option 462 * @param nm_ct the number of entries in this list 463 * 464 * @returns 0UL on error, other an unsigned long with the correct bit set. 465 */ 466 static uintptr_t 467 find_member_bit(tOptions * opts, tOptDesc * od, char const * pz, int len, 468 char const * const * nm_list, unsigned int nm_ct) 469 { 470 char nm_buf[ AO_NAME_SIZE ]; 471 472 memcpy(nm_buf, pz, len); 473 nm_buf[len] = NUL; 474 475 { 476 unsigned int shift_ct = (unsigned int) 477 find_name(nm_buf, opts, od, nm_list, nm_ct); 478 if (shift_ct >= nm_ct) 479 return 0UL; 480 481 return 1UL << shift_ct; 482 } 483 } 484 485 /*=export_func optionMemberList 486 * what: Get the list of members of a bit mask set 487 * 488 * arg: tOptDesc *, od, the set membership option description 489 * 490 * ret_type: char * 491 * ret_desc: the names of the set bits 492 * 493 * doc: This converts the OPT_VALUE_name mask value to a allocated string. 494 * It is the caller's responsibility to free the string. 495 =*/ 496 char * 497 optionMemberList(tOptDesc * od) 498 { 499 uintptr_t sv = od->optArg.argIntptr; 500 char * res; 501 (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od); 502 res = VOIDP(od->optArg.argString); 503 od->optArg.argIntptr = sv; 504 return res; 505 } 506 507 /*=export_func optionSetMembers 508 * what: Convert between bit flag values and strings 509 * private: 510 * 511 * arg: tOptions *, opts, the program options descriptor 512 * arg: tOptDesc *, od, the set membership option description 513 * arg: char const * const *, 514 * nm_list, list of enumeration names 515 * arg: unsigned int, nm_ct, number of names in list 516 * 517 * doc: This converts the optArg.argString string from the option description 518 * into the index corresponding to an entry in the name list. 519 * This will match the generated enumeration value. 520 * Full matches are always accepted. Partial matches are accepted 521 * if there is only one partial match. 522 =*/ 523 void 524 optionSetMembers(tOptions * opts, tOptDesc * od, 525 char const * const * nm_list, unsigned int nm_ct) 526 { 527 /* 528 * IF the program option descriptor pointer is invalid, 529 * then it is some sort of special request. 530 */ 531 switch ((uintptr_t)opts) { 532 case (uintptr_t)OPTPROC_EMIT_USAGE: 533 enum_err(OPTPROC_EMIT_USAGE, od, nm_list, nm_ct); 534 return; 535 536 case (uintptr_t)OPTPROC_EMIT_SHELL: 537 set_memb_shell(opts, od, nm_list, nm_ct); 538 return; 539 540 case (uintptr_t)OPTPROC_RETURN_VALNAME: 541 set_memb_names(opts, od, nm_list, nm_ct); 542 return; 543 544 default: 545 break; 546 } 547 548 if ((od->fOptState & OPTST_RESET) != 0) 549 return; 550 551 { 552 char const * arg; 553 bool invert; 554 uintptr_t res = check_membership_start(od, &arg, &invert); 555 if (arg == NULL) 556 goto fail_return; 557 558 while (*arg != NUL) { 559 bool inv_val = false; 560 int len; 561 562 switch (*arg) { 563 case ',': 564 arg = SPN_WHITESPACE_CHARS(arg+1); 565 if ((*arg == ',') || (*arg == '|')) 566 goto fail_return; 567 continue; 568 569 case '-': 570 case '!': 571 inv_val = true; 572 /* FALLTHROUGH */ 573 574 case '+': 575 case '|': 576 arg = SPN_WHITESPACE_CHARS(arg+1); 577 } 578 579 len = (int)(BRK_SET_SEPARATOR_CHARS(arg) - arg); 580 if (len == 0) 581 break; 582 583 if ((len == 3) && (strncmp(arg, zAll, 3) == 0)) { 584 if (inv_val) 585 res = 0; 586 else res = ~0UL; 587 } 588 else if ((len == 4) && (strncmp(arg, zNone, 4) == 0)) { 589 if (! inv_val) 590 res = 0; 591 } 592 else do { 593 char * pz; 594 uintptr_t bit = strtoul(arg, &pz, 0); 595 596 if (pz != arg + len) { 597 bit = find_member_bit(opts, od, pz, len, nm_list, nm_ct); 598 if (bit == 0UL) 599 goto fail_return; 600 } 601 if (inv_val) 602 res &= ~bit; 603 else res |= bit; 604 } while (false); 605 606 arg = SPN_WHITESPACE_CHARS(arg + len); 607 } 608 609 if (invert) 610 res ^= ~0UL; 611 612 if (nm_ct < (8 * sizeof(uintptr_t))) 613 res &= (1UL << nm_ct) - 1UL; 614 615 od->optCookie = VOIDP(res); 616 } 617 return; 618 619 fail_return: 620 od->optCookie = VOIDP(0); 621 } 622 623 /** @} 624 * 625 * Local Variables: 626 * mode: C 627 * c-file-style: "stroustrup" 628 * indent-tabs-mode: nil 629 * End: 630 * end of autoopts/enum.c */ 631