About Docs Source
LCOV - code coverage report
Current view: top level - src/lib/crater - opts.c (source / functions) Hit Total Coverage
Test: unnamed Lines: 326 406 80.3 %
Date: 2024-02-13 04:57:17 Functions: 30 32 93.8 %

          Line data    Source code
       1             : #include <stdio.h>
       2             : #include <stdlib.h>
       3             : #include <string.h>
       4             : #include <strings.h>
       5             : #include <ctype.h>
       6             : #include <limits.h>
       7             : #include <errno.h>
       8             : 
       9             : #include <crater/opts.h>
      10             : #include <crater/hash.h>
      11             : 
      12         113 : bool cr8r_opt_missing_optional(cr8r_opt *self){
      13         113 :     return 1;
      14             : }
      15             : 
      16           2 : bool cr8r_opt_print_help(cr8r_opt *self, char *opt){
      17           2 :     cr8r_opt *opts = self->dest;
      18           2 :     fprintf(stderr, "%s", self->description);
      19          29 :     for(uint64_t i = 0; opts[i].short_name || opts[i].long_name; ++i){
      20          27 :         if(opts + i != self){
      21          25 :             if(opts[i].short_name){
      22          15 :                 if(opts[i].long_name){
      23          14 :                     fprintf(stderr, "\t-%s/--%s: \t%s\n", opts[i].short_name, opts[i].long_name, opts[i].description ?: "<no description>");
      24             :                 }else{
      25           1 :                     fprintf(stderr, "\t-%s: \t%s\n", opts[i].short_name, opts[i].description ?: "<no description>");
      26             :                 }
      27             :             }else{
      28          10 :                 fprintf(stderr, "\t--%s: \t%s\n", opts[i].long_name, opts[i].description ?: "<no description>");
      29             :             }
      30             :         }else{
      31           2 :             fprintf(stderr, "\t-h/--help: display this help information and exit\n");
      32             :         }
      33             :     }
      34           2 :     exit(0);
      35             : }
      36             : 
      37           1 : bool cr8r_opt_ignore_arg(void *data, int argc, char **argv, int i){
      38           1 :     return 1;
      39             : }
      40             : 
      41           0 : bool cr8r_opt_set_enum_0(cr8r_opt *self, char *opt){
      42           0 :     *(int*)self->dest = 0;
      43           0 :     return 1;
      44             : }
      45             : 
      46             : /// Implementation of on_opt that sets *(int*)self->dest to 1
      47           0 : bool cr8r_opt_set_enum_1(cr8r_opt *self, char *opt){
      48           0 :     *(int*)self->dest = 1;
      49           0 :     return 1;
      50             : }
      51             : 
      52           1 : bool cr8r_opt_parse_ull(cr8r_opt *self, char *opt){
      53           1 :     char *end = opt;
      54           1 :     errno = 0;
      55           1 :     *(unsigned long long*)self->dest = strtoull(opt, &end, 0);
      56           1 :     return errno != ERANGE && end != opt && !*end;
      57             : }
      58             : 
      59           1 : bool cr8r_opt_parse_ll(cr8r_opt *self, char *opt){
      60           1 :     char *end = opt;
      61           1 :     errno = 0;
      62           1 :     *(long long*)self->dest = strtoll(opt, &end, 0);
      63           1 :     return errno != ERANGE && end != opt && !*end;
      64             : }
      65             : 
      66           1 : bool cr8r_opt_parse_ul(cr8r_opt *self, char *opt){
      67           1 :     char *end = opt;
      68           1 :     errno = 0;
      69           1 :     *(unsigned long*)self->dest = strtoul(opt, &end, 0);
      70           1 :     return errno != ERANGE && end != opt && !*end;
      71             : }
      72             : 
      73           1 : bool cr8r_opt_parse_l(cr8r_opt *self, char *opt){
      74           1 :     char *end = opt;
      75           1 :     errno = 0;
      76           1 :     *(unsigned long*)self->dest = strtol(opt, &end, 0);
      77           1 :     return errno != ERANGE && end != opt && !*end;
      78             : }
      79             : 
      80           1 : bool cr8r_opt_parse_u(cr8r_opt *self, char *opt){
      81           1 :     char *end = opt;
      82           1 :     errno = 0;
      83           1 :     unsigned long n = strtoul(opt, &end, 0);
      84           1 :     *(unsigned*)self->dest = n;
      85           1 :     return errno != ERANGE && end != opt && !*end &&
      86             :         n <= UINT_MAX;
      87             : }
      88             : 
      89           1 : bool cr8r_opt_parse_i(cr8r_opt *self, char *opt){
      90           1 :     char *end = opt;
      91           1 :     errno = 0;
      92           1 :     long n = strtol(opt, &end, 0);
      93           1 :     *(int*)self->dest = n;
      94           2 :     return errno != ERANGE && end != opt && !*end &&
      95           2 :         INT_MIN <= n && n <= INT_MAX;
      96             : }
      97             : 
      98           1 : bool cr8r_opt_parse_us(cr8r_opt *self, char *opt){
      99           1 :     char *end = opt;
     100           1 :     errno = 0;
     101           1 :     unsigned long n = strtoul(opt, &end, 0);
     102           1 :     *(unsigned short*)self->dest = n;
     103           1 :     return errno != ERANGE && end != opt && !*end &&
     104             :         n <= USHRT_MAX;
     105             : }
     106             : 
     107           1 : bool cr8r_opt_parse_s(cr8r_opt *self, char *opt){
     108           1 :     char *end = opt;
     109           1 :     errno = 0;
     110           1 :     long n  = strtol(opt, &end, 0);
     111           1 :     *(short*)self->dest = n;
     112           2 :     return errno != ERANGE && end != opt && !*end &&
     113           2 :         SHRT_MIN <= n && n <= SHRT_MAX;
     114             : }
     115             : 
     116           3 : bool cr8r_opt_parse_uc(cr8r_opt *self, char *opt){
     117           3 :     char *end = opt;
     118           3 :     if(!*end){
     119             :         return 0;
     120             :     }
     121           2 :     if(!end[1]){
     122           1 :         *(unsigned char*)self->dest = end[0];
     123           1 :         return 1;
     124             :     }
     125           1 :     errno = 0;
     126           1 :     unsigned long n = strtoul(opt, &end, 0);
     127           1 :     *(unsigned char*)self->dest = n;
     128           1 :     return errno != ERANGE && end != opt && !*end &&
     129             :         n <= UCHAR_MAX;
     130             : }
     131             : 
     132           3 : bool cr8r_opt_parse_sc(cr8r_opt *self, char *opt){
     133           3 :     char *end = opt;
     134           3 :     if(!*end){
     135             :         return 0;
     136             :     }
     137           2 :     if(!end[1]){
     138           1 :         *(signed char*)self->dest = end[0];
     139           1 :         return 1;
     140             :     }
     141           1 :     errno = 0;
     142           1 :     long n = strtol(opt, &end, 0);
     143           1 :     *(signed char*)self->dest = n;
     144           2 :     return errno != ERANGE && end != opt && !*end &&
     145           2 :         SCHAR_MIN <= n && n <= SCHAR_MAX;
     146             : }
     147             : 
     148           3 : bool cr8r_opt_parse_c(cr8r_opt *self, char *opt){
     149           3 :     char *end = opt;
     150           3 :     if(!*end){
     151             :         return 0;
     152             :     }
     153           2 :     if(!end[1]){
     154           1 :         *(char*)self->dest = end[0];
     155           1 :         return 1;
     156             :     }
     157           1 :     errno = 0;
     158             : #if CHAR_MIN
     159           1 :     long n = strtol(opt, &end, 0);
     160           1 :     *(char*)self->dest = n;
     161             : #else
     162             :     unsigned long n = strtoul(opt, &end, 0);
     163             :     *(char*)self->dest = n;
     164             : #endif
     165           2 :     return errno != ERANGE && end != opt && !*end &&
     166           2 :         CHAR_MIN <= n && n <= CHAR_MAX;
     167             : }
     168             : 
     169           1 : bool cr8r_opt_parse_f(cr8r_opt *self, char *opt){
     170           1 :     char *end = opt;
     171           1 :     errno = 0;
     172           1 :     *(float*)self->dest = strtof(opt, &end);
     173           1 :     return errno != ERANGE && end != opt && !*end;
     174             : }
     175             : 
     176           1 : bool cr8r_opt_parse_d(cr8r_opt *self, char *opt){
     177           1 :     char *end = opt;
     178           1 :     errno = 0;
     179           1 :     *(double*)self->dest = strtod(opt, &end);
     180           1 :     return errno != ERANGE && end != opt && !*end;
     181             : }
     182             : 
     183           1 : bool cr8r_opt_parse_ld(cr8r_opt *self, char *opt){
     184           1 :     char *end = opt;
     185           1 :     errno = 0;
     186           1 :     *(long double*)self->dest = strtold(opt, &end);
     187           1 :     return errno != ERANGE && end != opt && !*end;
     188             : }
     189             : 
     190           1 : bool cr8r_opt_parse_cstr(cr8r_opt *self, char *opt){
     191           1 :     *(const char**)self->dest = opt;
     192           1 :     return !!*opt;
     193             : }
     194             : 
     195           4 : bool cr8r_opt_parse_b(cr8r_opt *self, char *opt){
     196           4 :     char *end = opt;
     197           4 :     errno = 0;
     198           4 :     unsigned long n = strtoul(opt, &end, 0);
     199           4 :     *(bool*)self->dest = !!n;
     200           4 :     if(errno == ERANGE || n > 1){
     201             :         return 0;
     202             :     }
     203           3 :     if(end == opt){
     204           3 :         if(!strcasecmp(opt, "true") || !strcasecmp(opt, "yes") || !strcasecmp(opt, "t") || !strcasecmp(opt, "y")){
     205           1 :             *(bool*)self->dest = true;
     206           2 :         }else if(!strcasecmp(opt, "false") || !strcasecmp(opt, "no") || !strcasecmp(opt, "f") || !strcasecmp(opt, "n")){
     207           1 :             *(bool*)self->dest = false;
     208             :         }else{
     209             :             return 0;
     210             :         }
     211             :     }
     212             :     return 1;
     213             : }
     214             : 
     215         245 : static inline int get_digit_base(int c, int base){
     216         245 :     if(c < '0'){
     217             :         return -1;
     218         239 :     }else if(base <= 10){
     219         235 :         return c < '0' + base ? c - '0' : -1;
     220           4 :     }else if(c <= '9'){
     221           1 :         return c - '0';
     222           3 :     }else if(c > 'Z'){
     223           2 :         if(c < 'a'){
     224             :             return -1;
     225             :         }
     226           1 :         c -= (int)'a' - 'A';
     227             :     }
     228           2 :     return c < 'A' + base - 10 ? c - 'A' + 10 : -1;
     229             : }
     230             : 
     231             : static const __int128 i128_max = ~(((__int128)1) << 127);
     232             : static const __int128 i128_min = ((__int128)1) << 127;
     233             : static const unsigned __int128 u128_max = ~(unsigned __int128)0;
     234             : 
     235             : // this function tries to emulate strtoul/strtol and friends, including most of the bugs in the api
     236             : // in particular, str is a pointer to a string to parse, base is the base to use, and is_signed
     237             : // indicates whether or not the string is signed.
     238             : // base can be 2-36, or 0 to infer the base.  if any other value is supplied, errno is set to EINVAL,
     239             : // *end is set to str if end is not NULL, and 0 is returned.
     240             : // the string should have the format `\s*[+-](?:0[xX]?)?\d+`.
     241             : // "\s" is a whitespace character according to isspace().
     242             : // "\d" is a digit in the given base, which are the first `base` digits starting from 0 if `base <= 10`,
     243             : // with letters from a-z, case insensitive, if `base > 10`.
     244             : // the allowed prefix `(?:0[xX]?)?` depends on the base: if base is 16, `0[xX]` is allowed, but optional,
     245             : // and if base is 0, the base is inferred to be 16 if the prefix is `0[xX]`, 8 if the prefix is `0`, and 10
     246             : // otherwise.  notice that `0` is not considered to be part of the prefix if base is explicitly set to 8.
     247             : // numbers are always allowed to start with 0's.
     248             : // if matching the string to the format fails before any digit in the number is read, *end is set to str if
     249             : // applicable (NOT the first position which does not match the format, since having it set to str is
     250             : // faster to check and more convenient).  this includes if the string is empty or NULL.
     251             : // this is why it is important that leading 0's are considered to be part of the number rather than the prefix
     252             : // (besides the first 0 if base is 0).  if a non-digit character is encountered after at least one
     253             : // digit in the number has been read, the number read up until that point is converted to a 128-bit integer
     254             : // and then *end is set to point to the first non-digit character if applicable and the number read so far is
     255             : // returned.
     256             : // if the number read so far overflows, *end is set to pointto the first character AFTER the digit that caused
     257             : // overflow, errno is set to ERANGE, and a clamped limit is returned (ie, i128_min if is_signed and the number
     258             : // has a leading '-', i128_max if is_signed and the number does not have a leading '-', and u128_max otherwise).
     259             : // 
     260             : // bugs carried over from strtoul and friends:
     261             : // if !is_signed, a leading '-' is ignored rather than treated as an error.
     262             : // errno is not set to 0 in the absence of any error
     263             : // **end is not const
     264             : CR8R_ATTR_NO_SAN("integer")
     265          11 : static inline unsigned __int128 strtou128(const char *str, char **end, int base, bool is_signed){
     266          11 :     if(end){
     267          11 :         *end = (char*)str;
     268             :     }
     269          11 :     if(base < 0 || base > 36 || base == 1){
     270           0 :         errno = EINVAL;
     271           0 :         return 0;
     272             :     }
     273          11 :     if(!str){
     274             :         return 0;
     275             :     }
     276          12 :     while(isspace(*str)){
     277           1 :         ++str;
     278             :     }
     279          11 :     bool negative = *str == '-';
     280          11 :     if(negative || *str == '+'){
     281           3 :         ++str;
     282             :     }
     283          11 :     if(!base){
     284          11 :         if(*str == '0'){
     285           5 :             if(!strncasecmp("x", str + 1, 1)){
     286           2 :                 str += 2;
     287           2 :                 base = 16;
     288             :             }else{
     289           3 :                 base = 8;
     290           3 :                 if(!*++str){
     291           2 :                     if(end){
     292           2 :                         *end = (char*)str;
     293             :                     }
     294           2 :                     return 0;
     295             :                 }
     296             :             }
     297             :         }else{
     298             :             base = 10;
     299             :         }
     300           0 :     }else if(base == 16 && !strncasecmp("0x", str, 2)){
     301           0 :         str += 2;
     302             :     }
     303           9 :     int cur_digit = get_digit_base(*str, base);
     304           9 :     if(cur_digit == -1){
     305             :         return 0;
     306             :     }
     307           8 :     unsigned __int128 res = cur_digit;
     308             :     // res*base <= -i128_min <-- res <= -i128_min/base
     309             :     // res*base > -i128_min <-- res >= (-i128_min + base)/base <-> res > -i128_min/base
     310           8 :     unsigned __int128 MAX = (is_signed ? (unsigned __int128)i128_min : u128_max);
     311           8 :     unsigned __int128 safe_ceil = MAX/(unsigned __int128)base;
     312         464 :     while(1){
     313         236 :         cur_digit = get_digit_base(*++str, base);
     314         236 :         if(cur_digit == -1){
     315             :             break;
     316             :         }
     317             :         // res*base + cur_digit <= MAX
     318             :         // cur_digit <= MAX - res*base
     319         230 :         if(res > safe_ceil || (unsigned __int128)cur_digit > MAX - res*(unsigned __int128)base){
     320           2 :             if(end){
     321           2 :                 *end = (char*)str;
     322             :             }
     323           2 :             errno = ERANGE;
     324           2 :             return is_signed ? negative ? (unsigned __int128)i128_min : (unsigned __int128)i128_max : u128_max;
     325             :         }
     326         228 :         res = res*(unsigned __int128)base + (unsigned __int128)cur_digit;
     327             :     }
     328           6 :     if(is_signed && !negative && res == (unsigned __int128)i128_min){
     329           1 :         if(end){
     330           1 :             *end = (char*)str;
     331             :         }
     332           1 :         errno = ERANGE;
     333           1 :         return i128_max;
     334           5 :     }else if(end){
     335           5 :         *end = (char*)str;
     336             :     }
     337           5 :     return is_signed && negative ? -res : res;
     338             : }
     339             : 
     340           8 : bool cr8r_opt_parse_i128(cr8r_opt *self, char *opt){
     341           8 :     char *end = opt;
     342           8 :     errno = 0;
     343           8 :     *(__int128*)self->dest = (__int128)strtou128(opt, &end, 0, true);
     344           8 :     return errno != ERANGE && end != opt && !*end;
     345             : }
     346             : 
     347           3 : bool cr8r_opt_parse_u128(cr8r_opt *self, char *opt){
     348           3 :     char *end = opt;
     349           3 :     errno = 0;
     350           3 :     *(unsigned __int128*)self->dest = strtou128(opt, &end, 0, false);
     351           3 :     return errno != ERANGE && end != opt && !*end;
     352             : }
     353             : 
     354           3 : char *cr8r_sprint_i128(char buf[static 41], __int128 i){
     355           3 :     char *it = buf + 41;
     356           3 :     *--it = '\0';
     357           3 :     if(!i){
     358           1 :         *--it = '0';
     359           2 :     }else if(i < 0){
     360          44 :         for(;i; i /= 10){
     361          42 :             *--it = '0' - i%10;
     362             :         }
     363           2 :         *--it = '-';
     364             :     }else{
     365           0 :         for(;i; i /= 10){
     366           0 :             *--it = '0' + i%10;
     367             :         }
     368             :     }
     369           3 :     return it;
     370             : }
     371             : 
     372           2 : char *cr8r_sprint_u128(char buf[static 40], unsigned __int128 i){
     373           2 :     char *it = buf + 40;
     374           2 :     *--it = '\0';
     375           2 :     if(!i){
     376           1 :         *--it = '0';
     377          40 :     }else for(;i; i /= 10){
     378          39 :         *--it = '0' + i%10;
     379             :     }
     380           2 :     return it;
     381             : }
     382             : 
     383          13 : static bool init_opt_tbls(cr8r_opt *opts, cr8r_opt_cfg *cfg, cr8r_hashtbl_t *long_opts, cr8r_hashtbl_t *short_opts){
     384          13 :     uint64_t num_opts = 0, num_long = 0, num_short = 0;
     385         194 :     for(cr8r_opt *opt; opt = opts + num_opts, opt->short_name || opt->long_name; ++num_opts){
     386         181 :         int char_len;
     387         181 :         if(opt->arg_mode && !opt->on_opt){
     388           0 :             fprintf(stderr, "Crater opt error: invalid opt spec (argument to opt allowed but on_opt missing)\n");
     389           0 :             return 0;
     390         181 :         }else if(opt->long_name && !*opt->long_name){
     391           0 :             fprintf(stderr, "Crater opt error: invalid opt spec (long_name is empty string (use NULL instead))\n");
     392           0 :             return 0;
     393         181 :         }else if(opt->short_name && !*opt->short_name){
     394           0 :             fprintf(stderr, "Crater opt error: invalid opt spec (short_name is empty string (use NULL instead))\n");
     395           0 :             return 0;
     396         181 :         }else if(opt->short_name && ((char_len = mblen(opt->short_name, MB_CUR_MAX)) == -1 || opt->short_name[char_len])){
     397           0 :             fprintf(stderr, "Crater opt error: invalid opt spec (short_name contains invalid multibyte character or multiple locale characters)\n");
     398           0 :             return 0;
     399         181 :         }else if(opt->short_name && strchr("-= ", *opt->short_name)){
     400           0 :             fprintf(stderr, "Crater opt error: invalid opt spec (short_name is '-', '=', or ' ', which are not permitted)\n");
     401           0 :             return 0;
     402         181 :         }else if(opt->long_name){
     403         782 :             for(uint64_t i = 0; (char_len = mblen(opt->long_name + i, MB_CUR_MAX)) > 0; i += char_len){
     404         608 :                 if(char_len == 1 && strchr("= ", opt->long_name[i])){
     405           0 :                     fprintf(stderr, "Crater opt error: invalid opt spec (long_name contains '=' or ' ', which are not permitted)\n");
     406           0 :                     return 0;
     407             :                 }
     408             :             }
     409           7 :         }else if(opt->arg_mode < 0 || opt->arg_mode > 2){
     410           0 :             fprintf(stderr, "Crater opt error: unknown value for arg_mode\n");
     411           0 :             return 0;
     412             :         }
     413         181 :         opt->found = false;
     414         181 :         num_long += !!opt->long_name;
     415         181 :         num_short += !!opt->short_name;
     416             :     }
     417          13 :     if(!cr8r_hash_init(long_opts, &cr8r_htft_cstr_u64, num_long*3)){
     418           0 :         fprintf(stderr, "Crater opt error: could not allocate long_opts table!\n");
     419           0 :         return 0;
     420             :     }
     421          13 :     if(!cr8r_hash_init(short_opts, &cr8r_htft_cstr_u64, num_short*3)){
     422           0 :         cr8r_hash_destroy(long_opts, &cr8r_htft_cstr_u64);
     423           0 :         fprintf(stderr, "Crater opt error: could not allocate short_opts table!\n");
     424           0 :         return 0;
     425             :     }
     426         194 :     bool success = true;
     427         194 :     for(uint64_t i = 0; i < num_opts; ++i){
     428         181 :         int status = 0;
     429         181 :         if(opts[i].short_name){
     430         111 :             cr8r_hash_insert(short_opts, &cr8r_htft_cstr_u64, &(cr8r_htent_cstr_u64){opts[i].short_name, i}, &status);
     431         111 :             if(status != 1){
     432           0 :                 fprintf(stderr, "Crater opt error: %s\n", status ? "duplicate short name" : "hash_insert failed!");
     433           0 :                 success = false;
     434           0 :                 break;
     435             :             }
     436             :         }
     437         181 :         if(opts[i].long_name){
     438         174 :             cr8r_hash_insert(long_opts, &cr8r_htft_cstr_u64, &(cr8r_htent_cstr_u64){opts[i].long_name, i}, &status);
     439         174 :             if(status != 1){
     440           0 :                 fprintf(stderr, "Crater opt error: %s\n", status ? "duplicate long name" : "hash_insert failed!");
     441           0 :                 success = false;
     442           0 :                 break;
     443             :             }
     444             :         }
     445             :     }
     446           0 :     if(!success){
     447           0 :         cr8r_hash_destroy(long_opts, &cr8r_htft_cstr_u64);
     448           0 :         cr8r_hash_destroy(short_opts, &cr8r_htft_cstr_u64);
     449             :     }
     450             :     return success;
     451             : }
     452             : 
     453          29 : static const char *strmbchr(const char *str, const char *mbchr){
     454         159 :     for(int i = 0, char_len; (char_len = mblen(str + i, MB_CUR_MAX)) > 0; i += char_len){
     455         153 :         if(!memcmp(str + i, mbchr, char_len)){
     456             :             return str + i;
     457             :         }
     458             :     }
     459             :     return NULL;
     460             : }
     461             : 
     462          29 : static inline bool parse_long_opt(cr8r_hashtbl_t *long_opts, cr8r_opt *opts, cr8r_opt_cfg *cfg, int argc, char **argv, int *argi){
     463          29 :     char *opt = NULL;
     464          29 :     cr8r_htent_cstr_u64 *ent = NULL;
     465          29 :     char *eq_chr = (char*)strmbchr(argv[*argi] + 2, "=");
     466          29 :     if(eq_chr){
     467          23 :         *eq_chr = '\0';
     468             :     }
     469          29 :     ent = cr8r_hash_get(long_opts, &cr8r_htft_cstr_u64, &(cr8r_htent_cstr_u64){.str=argv[*argi] + 2});
     470          29 :     if(!ent){
     471           2 :         fprintf(stderr, "Unrecognized option --%s\n", argv[*argi] + 2);
     472           2 :         if(eq_chr){
     473           1 :             *eq_chr = '=';
     474             :         }
     475           2 :         ++*argi;
     476           2 :         return 0;
     477             :     }
     478          27 :     if(eq_chr){
     479          22 :         *eq_chr = '=';
     480             :     }
     481          27 :     bool success = true;
     482          27 :     if(eq_chr){//option argument can be passed after an '=' in the same argv entry
     483          22 :         opt = eq_chr + 1;
     484          22 :         ++*argi;
     485          22 :         if(opts[ent->n].arg_mode == 0){
     486           0 :             fprintf(stderr, "Option --%s does not take an argument\n", opts[ent->n].long_name);
     487           0 :             success = false;
     488             :         }
     489           5 :     }else if(opts[ent->n].arg_mode == 0){
     490           2 :         ++*argi;
     491           3 :     }else if(*argi + 1 != argc && *argv[*argi + 1] != '-'){//or in the next argv entry, if it does not start with '-'
     492           3 :         opt = argv[*argi + 1];
     493           3 :         *argi += 2;
     494           0 :     }else if(opts[ent->n].arg_mode == 1){//or there is no option argument
     495           0 :         fprintf(stderr, "Option --%s missing required argument\n", opts[ent->n].long_name);
     496           0 :         ++*argi;
     497           0 :         success = false;
     498             :     }
     499             :     // if arg_mode is 0 or 2 and on_opt is not NULL, on_opt must be able to tolerate NULL opt
     500             :     // if arg_mode == 1, an error will be reported and on_opt will never be called if opt is NULL
     501          27 :     if(success && opts[ent->n].on_opt){
     502          27 :         if(!opts[ent->n].on_opt(opts + ent->n, opt)){
     503           6 :             if(opt){
     504           6 :                 fprintf(stderr, "Invalid argument to option --%s\n", opts[ent->n].long_name);
     505             :             }else{
     506           0 :                 fprintf(stderr, "Crater opt error: option --%s has arg_mode 2 but on_opt failed on NULL.  arg_mode or on_opt should be changed\n", opts[ent->n].long_name);
     507             :             }
     508             :             success = false;
     509             :         }
     510             :     }
     511          26 :     opts[ent->n].found = true;
     512          26 :     return success;
     513             : }
     514             : 
     515          20 : static inline bool parse_short_optgrp(cr8r_hashtbl_t *short_opts, cr8r_opt *opts, cr8r_opt_cfg *cfg, int argc, char **argv, int *argi){
     516          20 :     char *opt = NULL;
     517          20 :     cr8r_htent_cstr_u64 *ent = NULL;
     518          20 :     bool success = true, reading_group = true;
     519          42 :     for(int j = 1, char_len; reading_group && argv[*argi][j]; j += char_len){
     520          23 :         if(!success && (cfg->flags & CR8R_OPTS_FATAL_ERRS)){
     521           0 :             return 0;
     522             :         }
     523          23 :         char_len = mblen(argv[*argi] + j, MB_CUR_MAX);
     524          23 :         if(strchr("-= ", argv[*argi][j]) || char_len <= 0){
     525           1 :             fprintf(stderr, "Invalid short option name ('-', '=', ' ', '\\n', or a bad multibyte character)\n");
     526           1 :             success = false;
     527           2 :             continue;
     528             :         }
     529          22 :         char tmp_chr = argv[*argi][j + char_len];
     530          22 :         argv[*argi][j + char_len] = '\0';
     531          22 :         ent = cr8r_hash_get(short_opts, &cr8r_htft_cstr_u64, &(cr8r_htent_cstr_u64){.str=argv[*argi] + j});
     532          22 :         argv[*argi][j + char_len] = tmp_chr;
     533          22 :         if(!ent){
     534           1 :             fprintf(stderr, "Unrecognized option -%.*s\n", char_len, argv[*argi] + j);
     535           1 :             success = false;
     536           1 :             continue;
     537             :         }
     538          21 :         bool curr_success = true;
     539          21 :         if(tmp_chr == '='){
     540          13 :             opt = argv[*argi] + j + char_len + 1;
     541          13 :             ++*argi;
     542          13 :             reading_group = false;
     543          13 :             if(opts[ent->n].arg_mode == 0){
     544           0 :                 fprintf(stderr, "Option -%s does not take an argument\n", opts[ent->n].short_name);
     545           0 :                 success = curr_success = false;
     546             :             }
     547             :         // if the current option accepts an argument (arg_mode is 1 or 2),
     548             :         // the current option is the last one in the group, and the next command line argument does
     549             :         // not begin with a dash
     550           8 :         }else if(opts[ent->n].arg_mode && !tmp_chr && *argi + 1 != argc && *argv[*argi + 1] != '-'){
     551           2 :             opt = argv[*argi + 1];
     552           2 :             *argi += 2;
     553           2 :             reading_group = false;
     554           6 :         }else if(opts[ent->n].arg_mode == 1){
     555           1 :             fprintf(stderr, "Option -%s missing required argument (are you missing a ' '/'='?)\n", opts[ent->n].short_name);
     556           1 :             success = curr_success = false;
     557             :         }
     558          21 :         if(curr_success && opts[ent->n].on_opt){
     559          20 :             if(!opts[ent->n].on_opt(opts + ent->n, opt)){
     560           3 :                 if(opts[ent->n].arg_mode == 2 && !opt){
     561           0 :                     fprintf(stderr, "Crater opt error: option -%s has arg_mode 2 but on_opt failed on NULL.  arg_mode or on_opt should be changed\n", opts[ent->n].short_name);
     562             :                 }else{
     563           3 :                     fprintf(stderr, "Invalid argument to option -%s\n", opts[ent->n].short_name);
     564             :                 }
     565             :                 success = false;
     566             :             }
     567             :         }
     568          20 :         opts[ent->n].found = true;
     569             :     }
     570          19 :     if(reading_group){
     571           4 :         ++*argi;
     572             :     }
     573             :     return success;
     574             : }
     575             : 
     576          13 : bool cr8r_opt_parse(cr8r_opt *opts, cr8r_opt_cfg *cfg, int argc, char **argv){
     577          13 :     cr8r_hashtbl_t long_opts, short_opts;
     578          13 :     if(!init_opt_tbls(opts, cfg, &long_opts, &short_opts)){
     579             :         return 0;
     580             :     }
     581          13 :     bool success = true;
     582          13 :     int i = 1;
     583          61 :     while(i < argc){
     584          50 :         if(*argv[i] != '-'){
     585           1 :             if(!cfg->on_arg || !cfg->on_arg(cfg->data, argc, argv, i)){
     586           0 :                 fprintf(stderr, "Failed to parse positional argument %i \"%s\"\n", i, argv[i]);
     587           0 :                 success = false;
     588             :             }
     589           1 :             ++i;
     590          49 :         }else if(argv[i][1] == '-'){
     591          29 :             if(!argv[i][2]){//encountered "--"; done
     592           0 :                 if(cfg->flags & CR8R_OPTS_CB_ON_DD){
     593           0 :                     if(!cfg->on_arg || !cfg->on_arg(cfg->data, argc, argv, i)){
     594           0 :                         fprintf(stderr, "Failed to parse positional argument %i \"--\"\n", i);
     595           0 :                         success = false;
     596             :                     }
     597             :                 }
     598             :                 break;
     599             :             }//otherwise we have a "--long_name" option
     600          29 :             success &= parse_long_opt(&long_opts, opts, cfg, argc, argv, &i);
     601          20 :         }else if(!argv[i][1]){//encountered "-"
     602           0 :             if(!(cfg->flags & CR8R_OPTS_ALLOW_STRAY_DASH)){
     603           0 :                 fprintf(stderr, "Stray \"-\" in argv\n");
     604           0 :                 success = false;
     605           0 :             }else if(!cfg->on_arg || !cfg->on_arg(cfg->data, argc, argv, i)){
     606           0 :                 fprintf(stderr, "Failed to parse positional argument %i \"%s\"\n", i, argv[i]);
     607           0 :                 success = false;
     608             :             }
     609           0 :             ++i;
     610             :         }else{
     611          20 :             success &= parse_short_optgrp(&short_opts, opts, cfg, argc, argv, &i);
     612             :         }
     613          48 :         if(!success && (cfg->flags & CR8R_OPTS_FATAL_ERRS)){
     614           0 :             cr8r_hash_destroy(&long_opts, &cr8r_htft_cstr_u64);
     615           0 :             cr8r_hash_destroy(&short_opts, &cr8r_htft_cstr_u64);
     616           0 :             return 0;
     617             :         }
     618             :     }
     619          11 :     cr8r_hash_destroy(&long_opts, &cr8r_htft_cstr_u64);
     620          11 :     cr8r_hash_destroy(&short_opts, &cr8r_htft_cstr_u64);
     621         165 :     for(cr8r_opt *opt = opts; opt->short_name || opt->long_name; ++opt){
     622         154 :         if(!opt->found && opt->on_missing){
     623         113 :             if(!opt->on_missing(opt)){
     624           0 :                 if(opt->short_name){
     625           0 :                     if(opt->long_name){
     626           0 :                         fprintf(stderr, "Crater opt error: Option -%s/--%s was not provided but its on_missing callback failed\n", opt->short_name, opt->long_name);
     627             :                     }else{
     628           0 :                         fprintf(stderr, "Crater opt error: Option -%s was not provided but its on_missing callback failed\n", opt->short_name);
     629             :                     }
     630             :                 }else{
     631           0 :                     fprintf(stderr, "Crater opt error: Option --%s was not provided but its on_missing callback failed\n", opt->long_name);
     632             :                 }
     633             :                 success = false;
     634             :             }
     635             :         }
     636         154 :         if(!opt->found && !opt->on_missing){
     637           0 :             if(opt->short_name){
     638           0 :                 if(opt->long_name){
     639           0 :                     fprintf(stderr, "Missing required option -%s/--%s\n", opt->short_name, opt->long_name);
     640             :                 }else{
     641           0 :                     fprintf(stderr, "Missing required option -%s\n", opt->short_name);
     642             :                 }
     643             :             }else{
     644           0 :                 fprintf(stderr, "Missing required option --%s\n", opt->long_name);
     645             :             }
     646             :             success = false;
     647             :         }
     648         154 :         if(!success && (cfg->flags & CR8R_OPTS_FATAL_ERRS)){
     649             :             return 0;
     650             :         }
     651             :     }
     652          11 :     for(; i < argc; ++i){
     653           0 :         if(!cfg->on_arg || !cfg->on_arg(cfg->data, argc, argv, i)){
     654           0 :             fprintf(stderr, "Failed to parse positional argument %i \"%s\"\n", i, argv[i]);
     655           0 :             success = false;
     656             :         }
     657             :     }
     658             :     return success;
     659             : }
     660             : 

Generated by: LCOV version 1.14