enum.c revision 290001
1218885Sdim 2218885Sdim/** 3218885Sdim * \file enumeration.c 4218885Sdim * 5218885Sdim * Handle options with enumeration names and bit mask bit names 6218885Sdim * for their arguments. 7218885Sdim * 8218885Sdim * @addtogroup autoopts 9218885Sdim * @{ 10276479Sdim */ 11218885Sdim/* 12218885Sdim * This routine will run run-on options through a pager so the 13218885Sdim * user may examine, print or edit them at their leisure. 14249423Sdim * 15276479Sdim * This file is part of AutoOpts, a companion to AutoGen. 16249423Sdim * AutoOpts is free software. 17321369Sdim * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 18226633Sdim * 19249423Sdim * AutoOpts is available under any one of two licenses. The license 20276479Sdim * in use must be one of these two and the choice is under the control 21218885Sdim * of the user of the license. 22218885Sdim * 23218885Sdim * The GNU Lesser General Public License, version 3 or later 24321369Sdim * See the files "COPYING.lgplv3" and "COPYING.gplv3" 25218885Sdim * 26218885Sdim * The Modified Berkeley Software Distribution License 27218885Sdim * See the file "COPYING.mbsd" 28218885Sdim * 29321369Sdim * These files have the following sha256 sums: 30321369Sdim * 31321369Sdim * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 32321369Sdim * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 33321369Sdim * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 34218885Sdim */ 35321369Sdim 36321369Sdim/* = = = START-STATIC-FORWARD = = = */ 37321369Sdimstatic void 38321369Sdimenum_err(tOptions * pOpts, tOptDesc * pOD, 39218885Sdim char const * const * paz_names, int name_ct); 40321369Sdim 41321369Sdimstatic uintptr_t 42218885Sdimfind_name(char const * name, tOptions * pOpts, tOptDesc * pOD, 43321369Sdim char const * const * paz_names, unsigned int name_ct); 44321369Sdim 45226633Sdimstatic void 46218885Sdimset_memb_shell(tOptions * pOpts, tOptDesc * pOD, char const * const * paz_names, 47321369Sdim unsigned int name_ct); 48321369Sdim 49321369Sdimstatic void 50321369Sdimset_memb_names(tOptions * opts, tOptDesc * od, char const * const * nm_list, 51321369Sdim unsigned int nm_ct); 52341825Sdim 53321369Sdimstatic uintptr_t 54218885Sdimcheck_membership_start(tOptDesc * od, char const ** argp, bool * invert); 55226633Sdim 56321369Sdimstatic uintptr_t 57321369Sdimfind_member_bit(tOptions * opts, tOptDesc * od, char const * pz, int len, 58321369Sdim char const * const * nm_list, unsigned int nm_ct); 59321369Sdim/* = = = END-STATIC-FORWARD = = = */ 60321369Sdim 61321369Sdimstatic void 62321369Sdimenum_err(tOptions * pOpts, tOptDesc * pOD, 63321369Sdim char const * const * paz_names, int name_ct) 64341825Sdim{ 65321369Sdim size_t max_len = 0; 66321369Sdim size_t ttl_len = 0; 67321369Sdim int ct_down = name_ct; 68321369Sdim int hidden = 0; 69321369Sdim 70321369Sdim /* 71321369Sdim * A real "pOpts" pointer means someone messed up. Give a real error. 72321369Sdim */ 73321369Sdim if (pOpts > OPTPROC_EMIT_LIMIT) 74321369Sdim fprintf(option_usage_fp, pz_enum_err_fmt, pOpts->pzProgName, 75321369Sdim pOD->optArg.argString, pOD->pz_Name); 76226633Sdim 77321369Sdim fprintf(option_usage_fp, zValidKeys, pOD->pz_Name); 78321369Sdim 79321369Sdim /* 80321369Sdim * If the first name starts with this funny character, then we have 81321369Sdim * a first value with an unspellable name. You cannot specify it. 82321369Sdim * So, we don't list it either. 83321369Sdim */ 84321369Sdim if (**paz_names == 0x7F) { 85321369Sdim paz_names++; 86321369Sdim hidden = 1; 87321369Sdim ct_down = --name_ct; 88321369Sdim } 89321369Sdim 90321369Sdim /* 91226633Sdim * Figure out the maximum length of any name, plus the total length 92321369Sdim * of all the names. 93321369Sdim */ 94321369Sdim { 95226633Sdim char const * const * paz = paz_names; 96321369Sdim 97321369Sdim do { 98321369Sdim size_t len = strlen(*(paz++)) + 1; 99321369Sdim if (len > max_len) 100321369Sdim max_len = len; 101321369Sdim ttl_len += len; 102321369Sdim } while (--ct_down > 0); 103321369Sdim 104321369Sdim ct_down = name_ct; 105321369Sdim } 106321369Sdim 107321369Sdim /* 108321369Sdim * IF any one entry is about 1/2 line or longer, print one per line 109321369Sdim */ 110321369Sdim if (max_len > 35) { 111276479Sdim do { 112321369Sdim fprintf(option_usage_fp, ENUM_ERR_LINE, *(paz_names++)); 113321369Sdim } while (--ct_down > 0); 114321369Sdim } 115321369Sdim 116321369Sdim /* 117321369Sdim * ELSE IF they all fit on one line, then do so. 118321369Sdim */ 119321369Sdim else if (ttl_len < 76) { 120321369Sdim fputc(' ', option_usage_fp); 121321369Sdim do { 122226633Sdim fputc(' ', option_usage_fp); 123226633Sdim fputs(*(paz_names++), option_usage_fp); 124341825Sdim } while (--ct_down > 0); 125218885Sdim fputc(NL, option_usage_fp); 126321369Sdim } 127218885Sdim 128321369Sdim /* 129226633Sdim * Otherwise, columnize the output 130321369Sdim */ 131226633Sdim else { 132218885Sdim unsigned int ent_no = 0; 133218885Sdim char zFmt[16]; /* format for all-but-last entries on a line */ 134321369Sdim 135321369Sdim sprintf(zFmt, ENUM_ERR_WIDTH, (int)max_len); 136321369Sdim max_len = 78 / max_len; /* max_len is now max entries on a line */ 137321369Sdim fputs(TWO_SPACES_STR, option_usage_fp); 138218885Sdim 139321369Sdim /* 140321369Sdim * Loop through all but the last entry 141218885Sdim */ 142321369Sdim ct_down = name_ct; 143218885Sdim while (--ct_down > 0) { 144321369Sdim if (++ent_no == max_len) { 145261991Sdim /* 146321369Sdim * Last entry on a line. Start next line, too. 147321369Sdim */ 148226633Sdim fprintf(option_usage_fp, NLSTR_SPACE_FMT, *(paz_names++)); 149321369Sdim ent_no = 0; 150321369Sdim } 151321369Sdim 152321369Sdim else 153321369Sdim fprintf(option_usage_fp, zFmt, *(paz_names++) ); 154218885Sdim } 155321369Sdim fprintf(option_usage_fp, NLSTR_FMT, *paz_names); 156321369Sdim } 157321369Sdim 158321369Sdim if (pOpts > OPTPROC_EMIT_LIMIT) { 159218885Sdim fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden); 160218885Sdim 161321369Sdim (*(pOpts->pUsageProc))(pOpts, EXIT_FAILURE); 162321369Sdim /* NOTREACHED */ 163218885Sdim } 164321369Sdim 165321369Sdim if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_MEMBERSHIP) { 166321369Sdim fprintf(option_usage_fp, zLowerBits, name_ct); 167321369Sdim fputs(zSetMemberSettings, option_usage_fp); 168321369Sdim } else { 169321369Sdim fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden); 170218885Sdim } 171321369Sdim} 172321369Sdim 173218885Sdim/** 174321369Sdim * Convert a name or number into a binary number. 175321369Sdim * "~0" and "-1" will be converted to the largest value in the enumeration. 176321369Sdim * 177321369Sdim * @param name the keyword name (number) to convert 178321369Sdim * @param pOpts the program's option descriptor 179321369Sdim * @param pOD the option descriptor for this option 180321369Sdim * @param paz_names the list of keywords for this option 181218885Sdim * @param name_ct the count of keywords 182321369Sdim */ 183321369Sdimstatic uintptr_t 184321369Sdimfind_name(char const * name, tOptions * pOpts, tOptDesc * pOD, 185321369Sdim char const * const * paz_names, unsigned int name_ct) 186321369Sdim{ 187321369Sdim /* 188321369Sdim * Return the matching index as a pointer sized integer. 189321369Sdim * The result gets stashed in a char * pointer. 190321369Sdim */ 191321369Sdim uintptr_t res = name_ct; 192321369Sdim size_t len = strlen((char *)name); 193321369Sdim uintptr_t idx; 194321369Sdim 195321369Sdim if (IS_DEC_DIGIT_CHAR(*name)) { 196321369Sdim char * pz = VOIDP(name); 197218885Sdim unsigned long val = strtoul(pz, &pz, 0); 198218885Sdim if ((*pz == NUL) && (val < name_ct)) 199321369Sdim return (uintptr_t)val; 200218885Sdim pz_enum_err_fmt = znum_too_large; 201218885Sdim option_usage_fp = stderr; 202261991Sdim enum_err(pOpts, pOD, paz_names, (int)name_ct); 203261991Sdim return name_ct; 204261991Sdim } 205261991Sdim 206321369Sdim if (IS_INVERSION_CHAR(*name) && (name[2] == NUL)) { 207261991Sdim if ( ((name[0] == '~') && (name[1] == '0')) 208261991Sdim || ((name[0] == '-') && (name[1] == '1'))) 209288943Sdim return (uintptr_t)(name_ct - 1); 210288943Sdim goto oops; 211288943Sdim } 212288943Sdim 213288943Sdim /* 214288943Sdim * Look for an exact match, but remember any partial matches. 215288943Sdim * Multiple partial matches means we have an ambiguous match. 216288943Sdim */ 217 for (idx = 0; idx < name_ct; idx++) { 218 if (strncmp((char *)paz_names[idx], (char *)name, len) == 0) { 219 if (paz_names[idx][len] == NUL) 220 return idx; /* full match */ 221 222 if (res == name_ct) 223 res = idx; /* save partial match */ 224 else 225 res = (uintptr_t)~0; /* may yet find full match */ 226 } 227 } 228 229 if (res < name_ct) 230 return res; /* partial match */ 231 232 oops: 233 234 pz_enum_err_fmt = (res == name_ct) ? zNoKey : zambiguous_key; 235 option_usage_fp = stderr; 236 enum_err(pOpts, pOD, paz_names, (int)name_ct); 237 return name_ct; 238} 239 240 241/*=export_func optionKeywordName 242 * what: Convert between enumeration values and strings 243 * private: 244 * 245 * arg: tOptDesc *, pOD, enumeration option description 246 * arg: unsigned int, enum_val, the enumeration value to map 247 * 248 * ret_type: char const * 249 * ret_desc: the enumeration name from const memory 250 * 251 * doc: This converts an enumeration value into the matching string. 252=*/ 253char const * 254optionKeywordName(tOptDesc * pOD, unsigned int enum_val) 255{ 256 tOptDesc od = { 0 }; 257 od.optArg.argEnum = enum_val; 258 259 (*(pOD->pOptProc))(OPTPROC_RETURN_VALNAME, &od ); 260 return od.optArg.argString; 261} 262 263 264/*=export_func optionEnumerationVal 265 * what: Convert from a string to an enumeration value 266 * private: 267 * 268 * arg: tOptions *, pOpts, the program options descriptor 269 * arg: tOptDesc *, pOD, enumeration option description 270 * arg: char const * const *, paz_names, list of enumeration names 271 * arg: unsigned int, name_ct, number of names in list 272 * 273 * ret_type: uintptr_t 274 * ret_desc: the enumeration value 275 * 276 * doc: This converts the optArg.argString string from the option description 277 * into the index corresponding to an entry in the name list. 278 * This will match the generated enumeration value. 279 * Full matches are always accepted. Partial matches are accepted 280 * if there is only one partial match. 281=*/ 282uintptr_t 283optionEnumerationVal(tOptions * pOpts, tOptDesc * pOD, 284 char const * const * paz_names, unsigned int name_ct) 285{ 286 uintptr_t res = 0UL; 287 288 /* 289 * IF the program option descriptor pointer is invalid, 290 * then it is some sort of special request. 291 */ 292 switch ((uintptr_t)pOpts) { 293 case (uintptr_t)OPTPROC_EMIT_USAGE: 294 /* 295 * print the list of enumeration names. 296 */ 297 enum_err(pOpts, pOD, paz_names, (int)name_ct); 298 break; 299 300 case (uintptr_t)OPTPROC_EMIT_SHELL: 301 { 302 unsigned int ix = (unsigned int)pOD->optArg.argEnum; 303 /* 304 * print the name string. 305 */ 306 if (ix >= name_ct) 307 printf(INVALID_FMT, ix); 308 else 309 fputs(paz_names[ ix ], stdout); 310 311 break; 312 } 313 314 case (uintptr_t)OPTPROC_RETURN_VALNAME: 315 { 316 unsigned int ix = (unsigned int)pOD->optArg.argEnum; 317 /* 318 * Replace the enumeration value with the name string. 319 */ 320 if (ix >= name_ct) 321 return (uintptr_t)INVALID_STR; 322 323 pOD->optArg.argString = paz_names[ix]; 324 break; 325 } 326 327 default: 328 if ((pOD->fOptState & OPTST_RESET) != 0) 329 break; 330 331 res = find_name(pOD->optArg.argString, pOpts, pOD, paz_names, name_ct); 332 333 if (pOD->fOptState & OPTST_ALLOC_ARG) { 334 AGFREE(pOD->optArg.argString); 335 pOD->fOptState &= ~OPTST_ALLOC_ARG; 336 pOD->optArg.argString = NULL; 337 } 338 } 339 340 return res; 341} 342 343static void 344set_memb_shell(tOptions * pOpts, tOptDesc * pOD, char const * const * paz_names, 345 unsigned int name_ct) 346{ 347 /* 348 * print the name string. 349 */ 350 unsigned int ix = 0; 351 uintptr_t bits = (uintptr_t)pOD->optCookie; 352 size_t len = 0; 353 354 (void)pOpts; 355 bits &= ((uintptr_t)1 << (uintptr_t)name_ct) - (uintptr_t)1; 356 357 while (bits != 0) { 358 if (bits & 1) { 359 if (len++ > 0) fputs(OR_STR, stdout); 360 fputs(paz_names[ix], stdout); 361 } 362 if (++ix >= name_ct) break; 363 bits >>= 1; 364 } 365} 366 367static void 368set_memb_names(tOptions * opts, tOptDesc * od, char const * const * nm_list, 369 unsigned int nm_ct) 370{ 371 char * pz; 372 uintptr_t mask = (1UL << (uintptr_t)nm_ct) - 1UL; 373 uintptr_t bits = (uintptr_t)od->optCookie & mask; 374 unsigned int ix = 0; 375 size_t len = 1; 376 377 /* 378 * Replace the enumeration value with the name string. 379 * First, determine the needed length, then allocate and fill in. 380 */ 381 while (bits != 0) { 382 if (bits & 1) 383 len += strlen(nm_list[ix]) + PLUS_STR_LEN + 1; 384 if (++ix >= nm_ct) break; 385 bits >>= 1; 386 } 387 388 od->optArg.argString = pz = AGALOC(len, "enum"); 389 bits = (uintptr_t)od->optCookie & mask; 390 if (bits == 0) { 391 *pz = NUL; 392 return; 393 } 394 395 for (ix = 0; ; ix++) { 396 size_t nln; 397 int doit = bits & 1; 398 399 bits >>= 1; 400 if (doit == 0) 401 continue; 402 403 nln = strlen(nm_list[ix]); 404 memcpy(pz, nm_list[ix], nln); 405 pz += nln; 406 if (bits == 0) 407 break; 408 memcpy(pz, PLUS_STR, PLUS_STR_LEN); 409 pz += PLUS_STR_LEN; 410 } 411 *pz = NUL; 412 (void)opts; 413} 414 415/** 416 * Check membership start conditions. An equal character (@samp{=}) says to 417 * clear the result and not carry over any residual value. A carat 418 * (@samp{^}), which may follow the equal character, says to invert the 419 * result. The scanning pointer is advanced past these characters and any 420 * leading white space. Invalid sequences are indicated by setting the 421 * scanning pointer to NULL. 422 * 423 * @param od the set membership option description 424 * @param argp a pointer to the string scanning pointer 425 * @param invert a pointer to the boolean inversion indicator 426 * 427 * @returns either zero or the original value for the optCookie. 428 */ 429static uintptr_t 430check_membership_start(tOptDesc * od, char const ** argp, bool * invert) 431{ 432 uintptr_t res = (uintptr_t)od->optCookie; 433 char const * arg = SPN_WHITESPACE_CHARS(od->optArg.argString); 434 if ((arg == NULL) || (*arg == NUL)) 435 goto member_start_fail; 436 437 *invert = false; 438 439 switch (*arg) { 440 case '=': 441 res = 0UL; 442 arg = SPN_WHITESPACE_CHARS(arg + 1); 443 switch (*arg) { 444 case '=': case ',': 445 goto member_start_fail; 446 case '^': 447 goto inversion; 448 default: 449 break; 450 } 451 break; 452 453 case '^': 454 inversion: 455 *invert = true; 456 arg = SPN_WHITESPACE_CHARS(arg + 1); 457 if (*arg != ',') 458 break; 459 /* FALLTHROUGH */ 460 461 case ',': 462 goto member_start_fail; 463 464 default: 465 break; 466 } 467 468 *argp = arg; 469 return res; 470 471member_start_fail: 472 *argp = NULL; 473 return 0UL; 474} 475 476/** 477 * convert a name to a bit. Look up a name string to get a bit number 478 * and shift the value "1" left that number of bits. 479 * 480 * @param opts program options descriptor 481 * @param od the set membership option description 482 * @param pz address of the start of the bit name 483 * @param nm_list the list of names for this option 484 * @param nm_ct the number of entries in this list 485 * 486 * @returns 0UL on error, other an unsigned long with the correct bit set. 487 */ 488static uintptr_t 489find_member_bit(tOptions * opts, tOptDesc * od, char const * pz, int len, 490 char const * const * nm_list, unsigned int nm_ct) 491{ 492 char nm_buf[ AO_NAME_SIZE ]; 493 494 memcpy(nm_buf, pz, len); 495 nm_buf[len] = NUL; 496 497 { 498 unsigned int shift_ct = (unsigned int) 499 find_name(nm_buf, opts, od, nm_list, nm_ct); 500 if (shift_ct >= nm_ct) 501 return 0UL; 502 503 return 1UL << shift_ct; 504 } 505} 506 507/*=export_func optionMemberList 508 * what: Get the list of members of a bit mask set 509 * 510 * arg: tOptDesc *, od, the set membership option description 511 * 512 * ret_type: char * 513 * ret_desc: the names of the set bits 514 * 515 * doc: This converts the OPT_VALUE_name mask value to a allocated string. 516 * It is the caller's responsibility to free the string. 517=*/ 518char * 519optionMemberList(tOptDesc * od) 520{ 521 uintptr_t sv = od->optArg.argIntptr; 522 char * res; 523 (*(od->pOptProc))(OPTPROC_RETURN_VALNAME, od); 524 res = VOIDP(od->optArg.argString); 525 od->optArg.argIntptr = sv; 526 return res; 527} 528 529/*=export_func optionSetMembers 530 * what: Convert between bit flag values and strings 531 * private: 532 * 533 * arg: tOptions *, opts, the program options descriptor 534 * arg: tOptDesc *, od, the set membership option description 535 * arg: char const * const *, 536 * nm_list, list of enumeration names 537 * arg: unsigned int, nm_ct, number of names in list 538 * 539 * doc: This converts the optArg.argString string from the option description 540 * into the index corresponding to an entry in the name list. 541 * This will match the generated enumeration value. 542 * Full matches are always accepted. Partial matches are accepted 543 * if there is only one partial match. 544=*/ 545void 546optionSetMembers(tOptions * opts, tOptDesc * od, 547 char const * const * nm_list, unsigned int nm_ct) 548{ 549 /* 550 * IF the program option descriptor pointer is invalid, 551 * then it is some sort of special request. 552 */ 553 switch ((uintptr_t)opts) { 554 case (uintptr_t)OPTPROC_EMIT_USAGE: 555 enum_err(OPTPROC_EMIT_USAGE, od, nm_list, nm_ct); 556 return; 557 558 case (uintptr_t)OPTPROC_EMIT_SHELL: 559 set_memb_shell(opts, od, nm_list, nm_ct); 560 return; 561 562 case (uintptr_t)OPTPROC_RETURN_VALNAME: 563 set_memb_names(opts, od, nm_list, nm_ct); 564 return; 565 566 default: 567 break; 568 } 569 570 if ((od->fOptState & OPTST_RESET) != 0) 571 return; 572 573 { 574 char const * arg; 575 bool invert; 576 uintptr_t res = check_membership_start(od, &arg, &invert); 577 if (arg == NULL) 578 goto fail_return; 579 580 while (*arg != NUL) { 581 bool inv_val = false; 582 int len; 583 584 switch (*arg) { 585 case ',': 586 arg = SPN_WHITESPACE_CHARS(arg+1); 587 if ((*arg == ',') || (*arg == '|')) 588 goto fail_return; 589 continue; 590 591 case '-': 592 case '!': 593 inv_val = true; 594 /* FALLTHROUGH */ 595 596 case '+': 597 case '|': 598 arg = SPN_WHITESPACE_CHARS(arg+1); 599 } 600 601 len = (int)(BRK_SET_SEPARATOR_CHARS(arg) - arg); 602 if (len == 0) 603 break; 604 605 if ((len == 3) && (strncmp(arg, zAll, 3) == 0)) { 606 if (inv_val) 607 res = 0; 608 else res = ~0UL; 609 } 610 else if ((len == 4) && (strncmp(arg, zNone, 4) == 0)) { 611 if (! inv_val) 612 res = 0; 613 } 614 else do { 615 char * pz; 616 uintptr_t bit = strtoul(arg, &pz, 0); 617 618 if (pz != arg + len) { 619 bit = find_member_bit(opts, od, pz, len, nm_list, nm_ct); 620 if (bit == 0UL) 621 goto fail_return; 622 } 623 if (inv_val) 624 res &= ~bit; 625 else res |= bit; 626 } while (false); 627 628 arg = SPN_WHITESPACE_CHARS(arg + len); 629 } 630 631 if (invert) 632 res ^= ~0UL; 633 634 if (nm_ct < (8 * sizeof(uintptr_t))) 635 res &= (1UL << nm_ct) - 1UL; 636 637 od->optCookie = VOIDP(res); 638 } 639 return; 640 641fail_return: 642 od->optCookie = VOIDP(0); 643} 644 645/** @} 646 * 647 * Local Variables: 648 * mode: C 649 * c-file-style: "stroustrup" 650 * indent-tabs-mode: nil 651 * End: 652 * end of autoopts/enum.c */ 653