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