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