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
36static void
37enum_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 */
159static uintptr_t
160find_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=*/
229char const *
230optionKeywordName(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=*/
258uintptr_t
259optionEnumerationVal(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
319static void
320set_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
343static void
344set_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 */
405static uintptr_t
406check_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
447member_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 */
464static uintptr_t
465find_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=*/
494char *
495optionMemberList(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=*/
521void
522optionSetMembers(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
617fail_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