1 2 /** 3 * \file enumeration.c 4 * 5 * Handle options with enumeration names and bit mask bit names 6 * for their arguments. 7 * 8 * @addtogroup autoopts 9 * @{ 10 */ 11 /* 12 * This routine will run run-on options through a pager so the 13 * user may examine, print or edit them at their leisure. 14 * 15 * This file is part of AutoOpts, a companion to AutoGen. 16 * AutoOpts is free software. 17 * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 18 * 19 * AutoOpts is available under any one of two licenses. The license 20 * in use must be one of these two and the choice is under the control 21 * of the user of the license. 22 * 23 * The GNU Lesser General Public License, version 3 or later 24 * See the files "COPYING.lgplv3" and "COPYING.gplv3" 25 * 26 * The Modified Berkeley Software Distribution License 27 * See the file "COPYING.mbsd" 28 * 29 * These files have the following sha256 sums: 30 * 31 * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 32 * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 33 * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 34 */ 35 36 /* = = = START-STATIC-FORWARD = = = */ 37 static void 38 enum_err(tOptions * pOpts, tOptDesc * pOD, 39 char const * const * paz_names, int name_ct); 40 41 static uintptr_t 42 find_name(char const * name, tOptions * pOpts, tOptDesc * pOD, 43 char const * const * paz_names, unsigned int name_ct); 44 45 static void 46 set_memb_shell(tOptions * pOpts, tOptDesc * pOD, char const * const * paz_names, 47 unsigned int name_ct); 48 49 static void 50 set_memb_names(tOptions * opts, tOptDesc * od, char const * const * nm_list, 51 unsigned int nm_ct); 52 53 static uintptr_t 54 check_membership_start(tOptDesc * od, char const ** argp, bool * invert); 55 56 static uintptr_t 57 find_member_bit(tOptions * opts, tOptDesc * od, char const * pz, int len, 58 char const * const * nm_list, unsigned int nm_ct); 59 /* = = = END-STATIC-FORWARD = = = */ 60 61 static void 62 enum_err(tOptions * pOpts, tOptDesc * pOD, 63 char const * const * paz_names, int name_ct) 64 { 65 size_t max_len = 0; 66 size_t ttl_len = 0; 67 int ct_down = name_ct; 68 int hidden = 0; 69 70 /* 71 * A real "pOpts" pointer means someone messed up. Give a real error. 72 */ 73 if (pOpts > OPTPROC_EMIT_LIMIT) 74 fprintf(option_usage_fp, pz_enum_err_fmt, pOpts->pzProgName, 75 pOD->optArg.argString, pOD->pz_Name); 76 77 fprintf(option_usage_fp, zValidKeys, pOD->pz_Name); 78 79 /* 80 * If the first name starts with this funny character, then we have 81 * a first value with an unspellable name. You cannot specify it. 82 * So, we don't list it either. 83 */ 84 if (**paz_names == 0x7F) { 85 paz_names++; 86 hidden = 1; 87 ct_down = --name_ct; 88 } 89 90 /* 91 * Figure out the maximum length of any name, plus the total length 92 * of all the names. 93 */ 94 { 95 char const * const * paz = paz_names; 96 97 do { 98 size_t len = strlen(*(paz++)) + 1; 99 if (len > max_len) 100 max_len = len; 101 ttl_len += len; 102 } while (--ct_down > 0); 103 104 ct_down = name_ct; 105 } 106 107 /* 108 * IF any one entry is about 1/2 line or longer, print one per line 109 */ 110 if (max_len > 35) { 111 do { 112 fprintf(option_usage_fp, ENUM_ERR_LINE, *(paz_names++)); 113 } while (--ct_down > 0); 114 } 115 116 /* 117 * ELSE IF they all fit on one line, then do so. 118 */ 119 else if (ttl_len < 76) { 120 fputc(' ', option_usage_fp); 121 do { 122 fputc(' ', option_usage_fp); 123 fputs(*(paz_names++), option_usage_fp); 124 } while (--ct_down > 0); 125 fputc(NL, option_usage_fp); 126 } 127 128 /* 129 * Otherwise, columnize the output 130 */ 131 else { 132 unsigned int ent_no = 0; 133 char zFmt[16]; /* format for all-but-last entries on a line */ 134 135 sprintf(zFmt, ENUM_ERR_WIDTH, (int)max_len); 136 max_len = 78 / max_len; /* max_len is now max entries on a line */ 137 fputs(TWO_SPACES_STR, option_usage_fp); 138 139 /* 140 * Loop through all but the last entry 141 */ 142 ct_down = name_ct; 143 while (--ct_down > 0) { 144 if (++ent_no == max_len) { 145 /* 146 * Last entry on a line. Start next line, too. 147 */ 148 fprintf(option_usage_fp, NLSTR_SPACE_FMT, *(paz_names++)); 149 ent_no = 0; 150 } 151 152 else 153 fprintf(option_usage_fp, zFmt, *(paz_names++) ); 154 } 155 fprintf(option_usage_fp, NLSTR_FMT, *paz_names); 156 } 157 158 if (pOpts > OPTPROC_EMIT_LIMIT) { 159 fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden); 160 161 (*(pOpts->pUsageProc))(pOpts, EXIT_FAILURE); 162 /* NOTREACHED */ 163 } 164 165 if (OPTST_GET_ARGTYPE(pOD->fOptState) == OPARG_TYPE_MEMBERSHIP) { 166 fprintf(option_usage_fp, zLowerBits, name_ct); 167 fputs(zSetMemberSettings, option_usage_fp); 168 } else { 169 fprintf(option_usage_fp, zIntRange, hidden, name_ct - 1 + hidden); 170 } 171 } 172 173 /** 174 * Convert a name or number into a binary number. 175 * "~0" and "-1" will be converted to the largest value in the enumeration. 176 * 177 * @param name the keyword name (number) to convert 178 * @param pOpts the program's option descriptor 179 * @param pOD the option descriptor for this option 180 * @param paz_names the list of keywords for this option 181 * @param name_ct the count of keywords 182 */ 183 static uintptr_t 184 find_name(char const * name, tOptions * pOpts, tOptDesc * pOD, 185 char const * const * paz_names, unsigned int name_ct) 186 { 187 /* 188 * Return the matching index as a pointer sized integer. 189 * The result gets stashed in a char * pointer. 190 */ 191 uintptr_t res = name_ct; 192 size_t len = strlen(name); 193 uintptr_t idx; 194 195 if (IS_DEC_DIGIT_CHAR(*name)) { 196 char * pz; 197 unsigned long val = strtoul(name, &pz, 0); 198 if ((*pz == NUL) && (val < name_ct)) 199 return (uintptr_t)val; 200 pz_enum_err_fmt = znum_too_large; 201 option_usage_fp = stderr; 202 enum_err(pOpts, pOD, paz_names, (int)name_ct); 203 return name_ct; 204 } 205 206 if (IS_INVERSION_CHAR(*name) && (name[2] == NUL)) { 207 if ( ((name[0] == '~') && (name[1] == '0')) 208 || ((name[0] == '-') && (name[1] == '1'))) 209 return (uintptr_t)(name_ct - 1); 210 goto oops; 211 } 212 213 /* 214 * Look for an exact match, but remember any partial matches. 215 * Multiple partial matches means we have an ambiguous match. 216 */ 217 for (idx = 0; idx < name_ct; idx++) { 218 if (strncmp(paz_names[idx], 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 =*/ 253 char const * 254 optionKeywordName(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 =*/ 282 uintptr_t 283 optionEnumerationVal(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 343 static void 344 set_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 367 static void 368 set_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 */ 429 static uintptr_t 430 check_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 471 member_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 */ 488 static uintptr_t 489 find_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 (uintptr_t)1U << 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 =*/ 518 char * 519 optionMemberList(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 =*/ 545 void 546 optionSetMembers(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 641 fail_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