Home | History | Annotate | Line # | Download | only in libopts
      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