add getsubopt, improve override macros
authorgit <redacted>
Sun, 19 Apr 2026 10:51:26 +0000 (06:51 -0400)
committergit <redacted>
Sun, 19 Apr 2026 10:51:26 +0000 (06:51 -0400)
README.md
asprintf.h
getopt_long.h

index 46ea40c2336230ebc9219165f636fda5a8a6470e..a506381c9a06eb3e0c9e0e73d1bd014082a641ff 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-A (WIP) collection of portable, (mostly) strict C99, single-header implementations of common libc extensions.
+A (WIP) collection of portable, strict C99, single-header implementations of common libc extensions.
 
 ### How to
 
@@ -21,21 +21,34 @@ By default, the header won't have any stdlib includes, change that with:
 #define <EXT-FUNC-NAME>_INCLUDE_LIBC
 ```
 
-Or disable function override with:
+Or disable function, type and variable override with:
 
 ```
 #define HAVE_<EXT-FUNC-NAME>
 ```
 
-## Specifics
+### Specifics
+
+---
+
+### asprintf
+
+includes `vasprintf()` and `asprintf()`
+
+---
 
 ### getopt\_long
 
+includes `getopt()`, `getsubopt()`, `getopt_long()` and `getopt_long_only`
+
 `getopt_long.h` has additional macros related to argument
-permuting, defining those enables it (GNU behaviour):
+permuting, defining those enables it (GNU behaviour, will still be disabled with `$POSIXLY_CORRECT` set):
 
 ```
 #define GETOPT_PERMUTE_ARGS
 #define GETOPT_LONG_PERMUTE_ARGS
 #define GETOPT_LONG_ONLY_PERMUTE_ARGS
 ```
+
+---
+
index a22a6fbbade4ec97be6a3212f14f0bf605bc8983..6d3341ae7c27f85aaa87bca5a839b294eff21e6e 100644 (file)
   #include <stdarg.h>
   #include <stdio.h>
   #include <stdlib.h>
- #endif
+ #endif /* ASPRINTF_INCLUDE_LIBC */
 
  #ifndef HAVE_ASPRINTF
   #define HAVE_ASPRINTF 1
+  #define asprintf  i_asprintf_
+  #define vasprintf i_vasprintf_
   int i_vasprintf_(char **restrict strp, const char *restrict fmt, va_list ap);
   int i_asprintf_(char **restrict strp, const char *restrict fmt, ...);
-  /* 
-   * Credit for a big part of the macro madness:
-   * Jens Gustedt
-   * https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/
-   */
-
-  /*
-   * This isn't strict C99, because C99 requires at least one argument in "..."
-   * for macros. Still, I haven't encountered a compiler that won't preprocess 
-   * this right (out of the popular ones, MSVC wasn't tested)
-   */
-  #define vasprintf(s, f, a)   i_vasprintf_(s, f, a)
-  #define I_COND_COMMA_(...) ,
-  #define I_PICK_ARG_(_1, _2, ARG, ...) ARG
-  #define I_HAS_COMMA_(...) I_PICK_ARG_(__VA_ARGS__, 1, 0)
-  #define I_ISEMPTY_(...)                                                      \
-  II_ISEMPTY_(                                                                 \
-         /* test if there is just one argument, eventually an empty           \
-         one */                                                               \
-         I_HAS_COMMA_(__VA_ARGS__),                                           \
-         /* test if I_COND_COMMA_ together with the argument                  \
-         adds a comma */                                                      \
-         I_HAS_COMMA_(I_COND_COMMA_ __VA_ARGS__),                             \
-         /* test if the argument together with a parenthesis                  \
-         adds a comma */                                                      \
-         I_HAS_COMMA_(__VA_ARGS__ (/*empty*/)),                               \
-         /* test if placing it between I_COND_COMMA_ and the                  \
-         parenthesis adds a comma */                                          \
-         I_HAS_COMMA_(I_COND_COMMA_ __VA_ARGS__ (/*empty*/))                  \
-         )
-  #define I_PASTE5_(_0, _1, _2, _3, _4) _0 ## _1 ## _2 ## _3 ## _4
-  #define II_ISEMPTY_(_0, _1, _2, _3)\
-  I_HAS_COMMA_(I_PASTE5_(I_IS_EMPTY_CASE_, _0, _1, _2, _3))
-  #define I_IS_EMPTY_CASE_0001 ,
-
-  #define II_PASTE_(a) I_EMPTY_##a
-  #define I_PASTE_(a) II_PASTE_(a)
-
-  #define I_GET_ARG_(...) I_PICK_ARG_(__VA_ARGS__)
-  #define asprintf(s, f, ...)\
-  i_asprintf_(s, f I_PASTE_(I_ISEMPTY_(I_GET_ARG_(, , __VA_ARGS__))) __VA_ARGS__)
-  #define I_EMPTY_1
-  #define I_EMPTY_0 I_COND_COMMA_()
  #endif /* !HAVE_ASPRINTF */
 
  #ifdef ASPRINTF_IMPLEMENTATION
index c2e1a3628bc0a3941a3b0773fe3d0fd2a27f7331..41f59de5e50651a3e76a7f247ee07a15eb172ca9 100644 (file)
  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
  */
 
+/*
+ * Copyright (c) 1990, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
 /*
  * Copyright (c) 2000 The NetBSD Foundation, Inc.
  * All rights reserved.
   #include <stdio.h>
   #include <stdlib.h>
   #include <string.h>
- #endif
+ #endif /* GETOPT_LONG_INCLUDE_LIBC */
 
  #ifndef HAVE_GETOPT_LONG
   #define HAVE_GETOPT_LONG 1
+
+  #define no_argument        0
+  #define required_argument  1
+  #define optional_argument  2
+
   /* 
    * structs are in their own namespace, so this should be OK
    * the worst it can do is make compiler messages worse
    */
   #define option i_option_
 
+  #define optarg    i_optarg_
+  #define suboptarg i_suboptarg_
+  #define optind    i_optind_
+  #define opterr    i_opterr_
+  #define optopt    i_optopt_
+  #define optreset  i_optreset_
+
+  #define getopt                      i_getopt_
+  #define getsubopt                   i_getsubopt_
+  #define getopt_long                 i_getopt_long_
+  #define getopt_long_only            i_getopt_long_only_
+
+  #ifndef GETOPT_LONG_IMPLEMENTATION
+   extern char *i_optarg_;
+   extern char *i_suboptarg_;
+   extern int  i_optind_, i_opterr_, i_optopt_, i_optreset_;
+  #endif /* !GETOPT_LONG_IMPLEMENTATION */
+
   struct i_option_ {
        const char *name;
        /*
        int val;
   };
 
-  int  i_getopt_(int argc, char *argv[], const char *optstring);
-  int  i_getsubopt_(char *restrict optionp[], const char *restrict tokens[],
-                     char *restrict valuep[]);
-  int  i_getopt_long_(int argc, char *argv[], const char *optstring,
+  int  i_getopt_(int argc, char * const *argv, const char *optstring);
+  int  i_getsubopt_(char **optionp, char * const *tokens, char **valuep);
+  int  i_getopt_long_(int argc, char * const *argv, const char *optstring,
                       const struct option *longopts, int *longindex);
-  int  i_getopt_long_only_(int argc, char *argv[], const char *optstring,
+  int  i_getopt_long_only_(int argc, char * const *argv, const char *optstring,
                            const struct option *longopts, int *longindex);
 
-  #define no_argument        0
-  #define required_argument  1
-  #define optional_argument  2
-
-  #define getopt(c,v,o)               i_getopt_(c,v,o)
-  #define getsubopt(o,t,v)            i_getsubopt_(o,t,v)
-  #define getopt_long(c,v,o,l,i)      i_getopt_long_(c,v,o,l,i)
-  #define getopt_long_only(c,v,o,l,i) i_getopt_long_only_(c,v,o,l,i)
-
  #endif /* !HAVE_GETOPT_LONG */
  
  #ifdef GETOPT_LONG_IMPLEMENTATION
 
   #define I_FLAG_PERMUTE_      0x01    /* permute non-options to the end of argv */
   #define I_FLAG_ALLARGS_      0x02    /* treat non-options as args to option "-1" */
-  #define I_FLAG_LONGONLY_     0x04    /* operate as i_getopt_long_only */
+  #define I_FLAG_LONGONLY_     0x04    /* operate as getopt_long_only */
 
   /* return values */
   #define      I_BADCH_                (int)'?'
 
   #define      I_EMSG_                 ""
 
-  static void i_warnx_(const char *, ...);
-  static int i_getopt_internal_(int, char **, const char *,
-                            const struct option *, int *, int);
-  static int i_parse_long_options__(char * const *, const char *,
-                              const struct option *, int *, int);
-  static int i_gcd_(int, int);
-  static void i_permute_args_(int, int, int, char * const *);
-
+  int  i_opterr_ = 1;          /* if error message should be printed */
+  int  i_optind_ = 1;          /* index into parent argv vector */
+  int  i_optopt_ = '?';        /* character checked for validity */
+  int   i_optreset_;            /* reset getopt */
+  char  *i_optarg_;            /* argument associated with option */
+  char  *i_suboptarg_;          /* argument associated with suboption */
 
-  /* XXX: set i_optreeset_ to 1 rather than these two */
+  /* XXX: set optreset to 1 rather than these two */
   static int   i_nonopt_start_ = -1;   /* first non option argument (for permute) */
   static int   i_nonopt_end_ = -1;     /* first option after non options (for permute) */
-  static int   i_opterr_ = 1;          /* if error message should be printed */
-  static int   i_optind_ = 1;          /* index into parent argv vector */
-  static int   i_optopt_ = '?';        /* character checked for validity */
-  static int   i_optreeset_;           /* reset getopt */
-  static char   *i_optarg_;            /* argument associated with option */
   static char   *i_place_ = I_EMSG_;    /* option letter processing */
   
   /* Error messages */
   static const char i_illoptchar_[] = "unknown option -- %c";
   static const char i_illoptstring_[] = "unknown option -- %s";
 
+  static void i_warnx_(const char *, ...);
+  static int  i_getopt_internal_(int, char * const *, const char *,
+                             const struct option *, int *, int);
+  static int  i_parse_long_options__(char * const *, const char *,
+                             const struct option *, int *, int);
+  static int  i_gcd_(int, int);
+  static void i_permute_args_(int, int, int, char * const *);
+
   /*
    * Own warnx() for portability
    */
   }
 
   /*
-   * Exchange the block from i_nonopt_start_ to i_nonopt_end_ with the block
-   * from i_nonopt_end_ to opt_end (keeping the same order of arguments
+   * Exchange the block from nonopt_start to nonopt_end with the block
+   * from nonopt_end to opt_end (keeping the same order of arguments
    * in each block).
    */
   static void
-  i_permute_args_(int pai_nonopt_start_, int pai_nonopt_end_, int opt_end,
+  i_permute_args_(int panonopt_start, int panonopt_end, int opt_end,
                   char * const *nargv)
   {
          int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
          /*
           * compute lengths of blocks and number and size of cycles
           */
-         nnonopts = pai_nonopt_end_ - pai_nonopt_start_;
-         nopts = opt_end - pai_nonopt_end_;
+         nnonopts = panonopt_end - panonopt_start;
+         nopts = opt_end - panonopt_end;
          ncycle = i_gcd_(nnonopts, nopts);
-         cyclelen = (opt_end - pai_nonopt_start_) / ncycle;
+         cyclelen = (opt_end - panonopt_start) / ncycle;
 
          for (i = 0; i < ncycle; i++) {
-               cstart = pai_nonopt_end_+i;
+               cstart = panonopt_end+i;
                pos = cstart;
                for (j = 0; j < cyclelen; j++) {
-                     if (pos >= pai_nonopt_end_)
+                     if (pos >= panonopt_end)
                          pos -= nnonopts;
                      else
                          pos += nopts;
                  if (match == -1) {    /* partial match */
                      match = i;
                  } else {
-                     /* i_ambig_uous abbreviation */
+                     /* ambiguous abbreviation */
                      if (I_PRINT_ERROR_)
                          i_warnx_(i_ambig_, (int)current_argv_len,
                                     current_argv);
                          i_warnx_(i_noarg_, (int)current_argv_len,
                                   current_argv);
                      /*
-                      * XXX: GNU sets i_optopt_ to val regardless of flag
+                      * XXX: GNU sets optopt to val regardless of flag
                       */
                      if (long_options[match].flag == NULL)
                          i_optopt_ = long_options[match].val;
                                i_warnx_(i_recargstring_,
                                    current_argv);
                        /*
-                        * XXX: GNU sets i_optopt_ to val regardless of flag
+                        * XXX: GNU sets optopt to val regardless of flag
                         */
                        if (long_options[match].flag == NULL)
                                i_optopt_ = long_options[match].val;
    *   Parse argc/argv argument vector.  Called by user level routines.
    */
   static int
-  i_getopt_internal_(int nargc, char **nargv, const char *options,
+  i_getopt_internal_(int nargc, char *const *nargv, const char *options,
                 const struct option *long_options, int *idx, int flags)
   {
          char *oli;                            /* option letter list index */
 
          /*
           * XXX Some GNU programs (like cvs) set optind to 0 instead of
-          * XXX using optreeset. Work around this braindamage.
+          * XXX using optreset. Work around this braindamage.
           */
          if (i_optind_ == 0)
-                 i_optind_ = i_optreeset_ = 1;
+                 i_optind_ = i_optreset_ = 1;
 
          /*
           * Disable GNU extensions if POSIXLY_CORRECT is set or options
           * string begins with a '+'.
           */
-         if (posix_me_harder == -1 || i_optreeset_)
+         if (posix_me_harder == -1 || i_optreset_)
                  posix_me_harder = (getenv("POSIXLY_CORRECT") != NULL);
          if (*options == '-')
                  flags |= I_FLAG_ALLARGS_;
                  options++;
 
          i_optarg_ = NULL;
-         if (i_optreeset_)
+         if (i_optreset_)
                  i_nonopt_start_ = i_nonopt_end_ = -1;
          start:
-         if (i_optreeset_ || !*i_place_) {             /* update scanning pointer */
-                 i_optreeset_ = 0;
+         if (i_optreset_ || !*i_place_) {              /* update scanning pointer */
+                 i_optreset_ = 0;
                  if (i_optind_ >= nargc) {          /* end of argument vector */
                      i_place_ = I_EMSG_;
                      if (i_nonopt_end_ != -1) {
                          i_optind_ -= i_nonopt_end_ - i_nonopt_start_;
                      } else if (i_nonopt_start_ != -1) {
                          /*
-                          * If we skipped non-options, set i_optind_
+                          * If we skipped non-options, set optind
                           * to the first of them.
                           */
                          i_optind_ = i_nonopt_start_;
   }
 
   int
-  i_getopt_(int argc, char *argv[], const char *optstring)
+  i_getsubopt_(char **optionp, char * const *tokens, char **valuep)
+  {
+         int cnt;
+         char *p;
+
+         i_suboptarg_ = *valuep = NULL;
+
+         if (!optionp || !*optionp)
+                 return(-1);
+
+         /* skip leading white-space, commas */
+         for (p = *optionp; *p && (*p == ',' || *p == ' ' || *p == '\t'); ++p);
+
+         if (!*p) {
+                 *optionp = p;
+                 return(-1);
+         }
+
+         /* save the start of the token, and skip the rest of the token. */
+         for (i_suboptarg_ = p;
+              *++p && *p != ',' && *p != '=' && *p != ' ' && *p != '\t';);
+
+         if (*p) {
+                 /*
+                  * If there's an equals sign, set the value pointer, and
+                  * skip over the value part of the token.  Terminate the
+                  * token.
+                  */
+                 if (*p == '=') {
+                     *p = '\0';
+                     for (*valuep = ++p;
+                         *p && *p != ',' && *p != ' ' && *p != '\t'; ++p);
+                     if (*p) 
+                         *p++ = '\0';
+                 } else {
+                     *p++ = '\0';
+                 }
+                 /* Skip any whitespace or commas after this token. */
+                 for (; *p && (*p == ',' || *p == ' ' || *p == '\t'); ++p);
+         }
+
+         /* set optionp for next round. */
+         *optionp = p;
+
+         for (cnt = 0; *tokens; ++tokens, ++cnt)
+                 if (!strcmp(i_suboptarg_, *tokens))
+                     return(cnt);
+         return(-1);
+  }
+
+  int
+  i_getopt_(int argc, char * const *argv, const char *optstring)
   {
          #ifdef GETOPT_PERMUTE_ARGS
           return (i_getopt_internal_(argc, argv, optstring, NULL, NULL,
   }
 
   int
-  i_getopt_long_(int argc, char *argv[], const char *optstring,
+  i_getopt_long_(int argc, char * const *argv, const char *optstring,
             const struct option *longopts, int *longindex)
   {  
          #ifdef GETOPT_LONG_PERMUTE_ARGS
   }
 
   int
-  i_getopt_long_only_(int argc, char *argv[], const char *optstring,
+  i_getopt_long_only_(int argc, char * const *argv, const char *optstring,
                    const struct option *longopts, int *longindex)
   {
          #ifdef GETOPT_LONG_ONLY_PERMUTE_ARGS