enum.c revision 294569
138494Sobrien 2174294Sobrien/** 338494Sobrien * \file enumeration.c 438494Sobrien * 538494Sobrien * Handle options with enumeration names and bit mask bit names 638494Sobrien * for their arguments. 738494Sobrien * 838494Sobrien * @addtogroup autoopts 938494Sobrien * @{ 1038494Sobrien */ 1138494Sobrien/* 1238494Sobrien * This routine will run run-on options through a pager so the 1338494Sobrien * user may examine, print or edit them at their leisure. 1438494Sobrien * 1538494Sobrien * This file is part of AutoOpts, a companion to AutoGen. 1638494Sobrien * AutoOpts is free software. 1738494Sobrien * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 1838494Sobrien * 1938494Sobrien * AutoOpts is available under any one of two licenses. The license 2042629Sobrien * in use must be one of these two and the choice is under the control 2138494Sobrien * of the user of the license. 2238494Sobrien * 2338494Sobrien * The GNU Lesser General Public License, version 3 or later 2438494Sobrien * See the files "COPYING.lgplv3" and "COPYING.gplv3" 2538494Sobrien * 2638494Sobrien * The Modified Berkeley Software Distribution License 2738494Sobrien * See the file "COPYING.mbsd" 2838494Sobrien * 2938494Sobrien * These files have the following sha256 sums: 3038494Sobrien * 3138494Sobrien * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 3238494Sobrien * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 3338494Sobrien * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 3438494Sobrien */ 3538494Sobrien 3638494Sobrien/* = = = START-STATIC-FORWARD = = = */ 3738494Sobrienstatic void 3838494Sobrienenum_err(tOptions * pOpts, tOptDesc * pOD, 3938494Sobrien char const * const * paz_names, int name_ct); 40174294Sobrien 4138494Sobrienstatic uintptr_t 4238494Sobrienfind_name(char const * name, tOptions * pOpts, tOptDesc * pOD, 4338494Sobrien char const * const * paz_names, unsigned int name_ct); 4438494Sobrien 4538494Sobrienstatic void 4638494Sobrienset_memb_shell(tOptions * pOpts, tOptDesc * pOD, char const * const * paz_names, 4738494Sobrien unsigned int name_ct); 4838494Sobrien 4938494Sobrienstatic void 5038494Sobrienset_memb_names(tOptions * opts, tOptDesc * od, char const * const * nm_list, 5138494Sobrien unsigned int nm_ct); 5238494Sobrien 5338494Sobrienstatic uintptr_t 5438494Sobriencheck_membership_start(tOptDesc * od, char const ** argp, bool * invert); 5538494Sobrien 5638494Sobrienstatic uintptr_t 5738494Sobrienfind_member_bit(tOptions * opts, tOptDesc * od, char const * pz, int len, 5838494Sobrien char const * const * nm_list, unsigned int nm_ct); 5938494Sobrien/* = = = END-STATIC-FORWARD = = = */ 6038494Sobrien 6138494Sobrienstatic void 6238494Sobrienenum_err(tOptions * pOpts, tOptDesc * pOD, 6338494Sobrien char const * const * paz_names, int name_ct) 6438494Sobrien{ 6538494Sobrien size_t max_len = 0; 66119679Smbr size_t ttl_len = 0; 6738494Sobrien int ct_down = name_ct; 68119679Smbr int hidden = 0; 6938494Sobrien 7038494Sobrien /* 7138494Sobrien * A real "pOpts" pointer means someone messed up. Give a real error. 7238494Sobrien */ 7338494Sobrien if (pOpts > OPTPROC_EMIT_LIMIT) 7438494Sobrien fprintf(option_usage_fp, pz_enum_err_fmt, pOpts->pzProgName, 7538494Sobrien pOD->optArg.argString, pOD->pz_Name); 7638494Sobrien 7738494Sobrien fprintf(option_usage_fp, zValidKeys, pOD->pz_Name); 7838494Sobrien 7938494Sobrien /* 8038494Sobrien * If the first name starts with this funny character, then we have 8138494Sobrien * a first value with an unspellable name. You cannot specify it. 8238494Sobrien * So, we don't list it either. 8338494Sobrien */ 8438494Sobrien if (**paz_names == 0x7F) { 8538494Sobrien paz_names++; 8638494Sobrien hidden = 1; 8738494Sobrien ct_down = --name_ct; 8838494Sobrien } 8938494Sobrien 9038494Sobrien /* 9138494Sobrien * Figure out the maximum length of any name, plus the total length 9238494Sobrien * of all the names. 9338494Sobrien */ 94119679Smbr { 9538494Sobrien char const * const * paz = paz_names; 9638494Sobrien 9738494Sobrien do { 9838494Sobrien size_t len = strlen(*(paz++)) + 1; 9938494Sobrien if (len > max_len) 10038494Sobrien max_len = len; 10138494Sobrien ttl_len += len; 10238494Sobrien } while (--ct_down > 0); 10338494Sobrien 10438494Sobrien ct_down = name_ct; 10538494Sobrien } 10638494Sobrien 10738494Sobrien /* 10838494Sobrien * IF any one entry is about 1/2 line or longer, print one per line 10938494Sobrien */ 11038494Sobrien if (max_len > 35) { 11138494Sobrien do { 11238494Sobrien fprintf(option_usage_fp, ENUM_ERR_LINE, *(paz_names++)); 11338494Sobrien } while (--ct_down > 0); 11438494Sobrien } 11538494Sobrien 11638494Sobrien /* 11738494Sobrien * ELSE IF they all fit on one line, then do so. 11838494Sobrien */ 11938494Sobrien else if (ttl_len < 76) { 12038494Sobrien fputc(' ', option_usage_fp); 12138494Sobrien do { 12238494Sobrien fputc(' ', option_usage_fp); 12338494Sobrien fputs(*(paz_names++), option_usage_fp); 12438494Sobrien } while (--ct_down > 0); 12538494Sobrien fputc(NL, option_usage_fp); 12638494Sobrien } 12738494Sobrien 12838494Sobrien /* 12938494Sobrien * Otherwise, columnize the output 13038494Sobrien */ 13138494Sobrien else { 13238494Sobrien unsigned int ent_no = 0; 13338494Sobrien char zFmt[16]; /* format for all-but-last entries on a line */ 13438494Sobrien 13538494Sobrien sprintf(zFmt, ENUM_ERR_WIDTH, (int)max_len); 13638494Sobrien max_len = 78 / max_len; /* max_len is now max entries on a line */ 13738494Sobrien fputs(TWO_SPACES_STR, option_usage_fp); 13838494Sobrien 13938494Sobrien /* 14038494Sobrien * Loop through all but the last entry 14138494Sobrien */ 14238494Sobrien ct_down = name_ct; 14338494Sobrien while (--ct_down > 0) { 14438494Sobrien 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 */ 183static uintptr_t 184find_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=*/ 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 (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=*/ 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