lib/: Pure C machinery for handling `keyword arguments' to functions.
[sod] / lib / keyword.h
diff --git a/lib/keyword.h b/lib/keyword.h
new file mode 100644 (file)
index 0000000..1fa3e56
--- /dev/null
@@ -0,0 +1,583 @@
+/* -*-c-*-
+ *
+ * Keyword argument handling
+ *
+ * (c) 2015 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the Sensible Object Design, an object system for C.
+ *
+ * SOD is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * SOD is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with SOD; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef KEYWORD_H
+#define KEYWORD_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+
+/*----- Function annotations ----------------------------------------------*/
+
+/* Some macros are defined for annotating functions.  They may improve
+ * compiler diagnostics when used properly.  They should be included as part
+ * of the function's declaration specifiers.
+ *
+ *   * @KWCALL@ marks a function as expecting keyword arguments.  The
+ *     compiler may check that there are an odd number of arguments, that the
+ *     even-numbered (starting from zero) arguments have pointer-to-character
+ *     type, and that the final argument is null.
+ *
+ *   * @KW__NORETURN@ marks a function as never returning.  Applied to
+ *     @kw_unknown@ and its various friends.  Users are not expected to use
+ *     this.
+ */
+
+#if defined(__GNUC__)
+#  define KW__GCC_VERSION_P(maj, min)                                  \
+       (__GNUC__ > (maj) || (__GNUC__ == (maj) && __GNUC_MINOR__ >= (min)))
+#  if KW__GCC_VERSION_P(2, 5)
+#    define KW__NORETURN __attribute__((__noreturn__))
+#  endif
+#  if KW__GCC_VERSION_P(4, 0)
+#    define KWCALL __attribute__((__sentinel__))
+#  endif
+#endif
+
+/* --- Trivial definitions, if we don't have compiler support --- */
+
+#ifndef KW__NORETURN
+#  define KW__NORETURN
+#endif
+
+#ifndef KWCALL
+#  define KWCALL
+#endif
+
+/*----- Type definitions --------------------------------------------------*/
+
+/* A keyword/value pair.  A vector of these can be passed as the value of the
+ * special keyword `kw.tab'.  This is a rather cumbersome way of constructing
+ * lists of keyword arguments for a function in a programmatic way.
+ */
+struct kwval {
+  const char *kw;                      /* The keyword name, as a string */
+  const void *val;                     /* A pointer to the keyword value */
+};
+
+/* A table of keyword/value pairs.  This is used as the value of a `kw.tab'
+ * argument which is itself in a @struct kwval@ table, since it's not
+ * possible to store both the vector and length directly.
+ */
+struct kwtab {
+  const struct kwval *v;               /* The address of the vector */
+  size_t n;                            /* The number of keywords */
+};
+
+/* The type of unknown-keyword handler functions. */
+typedef void kw_unkhookfn(const char */*set*/, const char */*kw*/);
+
+/*----- Global variables --------------------------------------------------*/
+
+/* A global hook function for handling unknown-keyword errors.  The default
+ * function prints a message to the standard error stream and aborts.
+ *
+ * The hook function must not return.  It's not possible to recover from an
+ * unknown-keyword error while parsing a variable-length argument tail, since
+ * it's impossible to find out what type the corresponding argument value is.
+ *
+ * Having a single global hook isn't really very satisfactory, but a fully
+ * adequate solution gets complicated quickly.  An external library will
+ * eventually be available to pick up the slack.
+ */
+extern kw_unkhookfn *kw_unkhook;
+
+/*----- Argument list macros ----------------------------------------------*/
+
+/* These macros is intended to be conveniences rather than a proper
+ * abstraction.  Functions with more complicated interfaces, and their
+ * callers, will have to make their own arrangements.
+ */
+
+/* --- @KWTAIL@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Use:                Marker to be included in a function prototype (at the end of
+ *             the argument list) to indicate that the function accepts
+ *             keyword arguments.  It is acceptable for the @KWTAIL@ marker
+ *             to be only thing in the argument list.
+ */
+
+#define KWTAIL const char *kwfirst_, ...
+
+/* --- @KWARGS@ --- *
+ *
+ * Arguments:  @body@ = a sequence of @K(kw, value)@ macro calls, without
+ *                     separators
+ *
+ * Use:                A package of actual keyword arguments.  In C89, the @body@
+ *             must not be empty: to pass no keywords, use @NO_KWARGS@
+ *             instead.
+ */
+
+#define KWARGS(body) body KW__END
+#define KW__END ((const char *)0)
+
+/*  --- @NO_KWARGS@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Use:                Special marker to include in an actual argument list to
+ *             indicate that no keyword arguments are to be passed.  See
+ *             @KWARGS@ above.
+ */
+
+#define NO_KWARGS KW__END, KW__END
+  /* Slight hack.  The @KWCALL@ macro sets GCC and similar compilers up to
+   * check for a sentinal null pointer at the end of the variable-length
+   * argument tail.  Alas, if there are no keywords at all, then the null
+   * terminator ends up in the @kwfirst_@ argument, and the tail is propetly
+   * empty, with the result that the compiler gives an annoying warning.
+   * Supplying an extra argument here is obviously harmless, and makes the
+   * otherwise useful warning go away in this case where it's not wanted.
+   */
+
+/*  --- @K@ --- *
+ *
+ * Arguments:  @kw@ = keyword name, as an unquoted token list
+ *             @val@ = keyword value, as an expression
+ *
+ * Use:                Bundles a keyword @kw@ and value @val@ together.
+ */
+
+#define K(kw, val) #kw, (val),
+
+/* --- @KW_VALIST@ --- *
+ *
+ * Arguments:  @va_list ap@ = argument-list extraction state
+ *
+ * Use:                Passes a reified variable-length argument tail into a keyword
+ *             function.
+ */
+
+#define K_VALIST(ap) "kw.valist", &(ap),
+
+/* --- @KW_TAB@ --- *
+ *
+ * Arguments:  @const struct kwval *v@ = base address of argument vector
+ *             @size_t n@ = length of argument vector
+ *
+ * Use:                Passes an vector of keyword arguments into a keyword
+ *             function.
+ */
+
+#define K_TAB(v, n) "kw.tab", (v), (size_t)(n),
+
+/*----- Keyword set definitions -------------------------------------------*
+ *
+ * A `keyword set' describes the collection of keyword arguments to be
+ * accepted by a function (or group of functions).  Keyword sets have names,
+ * which are C identifiers.  A keyword set must not be empty: use
+ * @kw_parseempty@ instead of this machinery when defining a function which
+ * may later accept keyword arguments but which currently doesn't define any.
+ *
+ * A keyword set definition is a macro of a single argument, conventionally
+ * named `@_@'.  The macro for a keyword set called @foo@ is named
+ * @foo_KWSET@.  It should consist of a triple @_(type, key, dflt)@ for each
+ * keyword argument, where @type@ is the C type of the argument, @key@ is the
+ * name of the argument (as a C identifier), and @dflt@ is an expression
+ * (valid to use in an aggregate initializer) to provide the default value
+ * for the argument.  The @type@ must be such that @type *@ is the type of a
+ * pointer to object of @type@.
+ */
+
+/* --- @KWSET_STRUCT@ --- *
+ *
+ * Arguments:  @set@ = the keyword set name
+ *
+ * Use:                Defines the keyword set argument structure @struct
+ *             set_kwargs@.
+ *
+ *             The structure is used to communicate argument values between
+ *             a function accepting keyword arguments and the argument
+ *             parsing function constructed by @KWSET_PARSEFN@.  It contains
+ *             two members for each keyword argument: one with the name of
+ *             the argument and the appropriate type to hold its value; the
+ *             other is a one-bit-wide bitfield named with a `_suppliedp'
+ *             suffix, and is set to indicate whether the caller provided a
+ *             a value for the corresponding keyword argument.
+ */
+
+#define KWSET_STRUCT(set)                                              \
+  struct set##_kwargs {                                                        \
+    set##_KWSET(KWSET__SUPPLIEDP)                                      \
+    set##_KWSET(KWSET__STRUCTMEM)                                      \
+  }
+#define KWSET__SUPPLIEDP(type, name, dflt) unsigned name##_suppliedp : 1;
+#define KWSET__STRUCTMEM(type, name, dflt) type name;
+
+/* --- @KWSET_PARSEFN@ --- *
+ *
+ * Arguments:  @set@ = the keyword set name
+ *
+ * Use:                Defines the keyword argument parsing function @set_kwparse@.
+ *             A call to this macro may be preceded by a storage-class
+ *             specifier, e.g., @static@, to specify the linkage for the
+ *             parsing function's name.
+ *
+ *             This function takes five arguments:
+ *
+ *             @struct set_kwargs *kw@ = pointer to keyword set argument
+ *                     structure to fill in
+ *             @const char *kwfirst@ = first keyword argument name from the
+ *                     variable-length argument tail, or null if the
+ *                     argument tail is empty
+ *             @va_list *ap@ = pointer to variable-length tail extraction
+ *                     state object
+ *             @const struct kwval *v@ = base address of argument vector
+ *             @size_t n@ = length of argument vector
+ *
+ *             The `kwparse' function extracts keyword arguments from the
+ *             argument tail (via @*ap@), and then the vector @v@; it
+ *             updates the structure @*kw@ with their values, and sets the
+ *             `_suppliedp' flags accordingly.  It's unusual to call the
+ *             `kwparse' function with both a nontrivial argument tail and
+ *             vector, but the effect is nonetheless well-defined.
+ *
+ *             The argument tail consists of alternating keyword argument
+ *             names (as pointers to null-terminated strings) and values,
+ *             terminated by a null pointer.  The argument values are simply
+ *             copied into the structure.  Passing the @kwfirst@ argument
+ *             separately allows functions to declare an explicit positional
+ *             argument for the first keyword name, which is useful if the
+ *             function has no other positional arguments.
+ *
+ *             The argument vector consists of @struct kwval@ items, each of
+ *             which contains a keyword name (as a pointer to a null-
+ *             terminated string) and the address of its value.  Argument
+ *             values are again copied into the structure.  Note that a
+ *             vector doesn't store the arguments directly.  This makes them
+ *             rather cumbersome to set up, but the benefit is a simple and
+ *             uniform approach for all keyword arguments.
+ *
+ *             The main application for argument vectors is for `front-end'
+ *             functions which want to pass on some subset of their keywords
+ *             to another function.  There isn't currently any macrology
+ *             provided for achieving this, but it's not especially
+ *             difficult.
+ *
+ *             There are (currently) two special keyword arguments, whose
+ *             names are not valid identifiers.  Future additions will also
+ *             have names beginning `kw.'.
+ *
+ *               * `kw.valist' -- the corresponding argument has type
+ *                 @va_list *@, and represents an entire variable-length
+ *                 argument tail to process, including the first keyword
+ *                 name.
+ *
+ *               * `kw.tab' -- the corresponding argument is a vector of
+ *                 @struct kwval@ items to process.  In a variable-length
+ *                 argument tail, this is passed as two arguments: the base
+ *                 address of the vector, and the length (as a @size_t@).
+ *                 In an argument vector, this is passed instead as a value
+ *                 of type @struct kwtab@.
+ *
+ *             If an unknown keyword is encountered while parsing, the
+ *             function @kw_unknown@ is called.
+ *
+ *             The keyword argument `kw.unknown' will never be defined.
+ */
+
+#define KWSET_PARSEFN(set)                                             \
+  void set##_kwparse(struct set##_kwargs *kw,                          \
+                    const char *kwfirst, va_list *ap,                  \
+                    const struct kwval *v, size_t n)                   \
+  {                                                                    \
+    const char *k, *kk;                                                        \
+    va_list *aap;                                                      \
+    const struct kwtab *t;                                             \
+    const struct kwval *vv;                                            \
+    size_t nn;                                                         \
+                                                                       \
+    for (k = kwfirst; k; k = va_arg(*ap, const char *)) {              \
+      if (!strcmp(k, "kw.valist")) {                                   \
+       aap = va_arg(*ap, va_list *);                                   \
+       kk = va_arg(*aap, const char *);                                \
+       set##_kwparse(kw, kk, aap, 0, 0);                               \
+      } else if (!strcmp(k, "kw.tab")) {                               \
+       vv = va_arg(*ap, const struct kwval *);                         \
+       nn = va_arg(*ap, size_t);                                       \
+       set##_kwparse(kw, 0, 0, vv, nn);                                \
+      }                                                                        \
+      set##_KWSET(KWSET__ARGVA)                                                \
+      else kw_unknown(#set, k);                                                \
+    }                                                                  \
+                                                                       \
+    while (n) {                                                                \
+      if (!strcmp(v->kw, "kw.valist")) {                               \
+       aap = *(va_list *const *)v->val;                                \
+       kk = va_arg(*aap, const char *);                                \
+       set##_kwparse(kw, kk, aap, 0, 0);                               \
+      } else if (!strcmp(v->kw, "kw.tab")) {                           \
+       t = (const struct kwtab *)v->val;                               \
+       set##_kwparse(kw, 0, 0, t->v, t->n);                            \
+      }                                                                        \
+      set##_KWSET(KWSET__ARGTAB)                                       \
+      else kw_unknown(#set, v->kw);                                    \
+      v++; n--;                                                                \
+    }                                                                  \
+  }
+#define KWSET__ARGVA(type, name, dflt)                                 \
+  else if (!strcmp(k, #name)) {                                                \
+    kw->name##_suppliedp = 1;                                          \
+    kw->name = va_arg(*ap, type);                                      \
+  }
+#define KWSET__ARGTAB(type, name, dflt)                                        \
+  else if (!strcmp(v->kw, #name)) {                                    \
+    kw->name##_suppliedp = 1;                                          \
+    kw->name = *(type const *)v->val;                                  \
+  }
+
+/*----- Defining keyword-accepting functions ------------------------------*/
+
+/* --- @KWDECL@ --- *
+ *
+ * Arguments:  @set@ = the keyword set name
+ *             @kw@ = the name for the keyword argument structure value
+ *
+ * Use:                Declares and initializes a keyword argument structure object
+ *             @kw@.  The `_suppliedp' members are initially all zero; the
+ *             argument value members are set to their default values as
+ *             specified in the keyword set definition macro.
+ */
+
+#define KWDECL(set, kw)                                                        \
+  struct set##_kwargs kw =                                             \
+    { set##_KWSET(KWSET__SPINIT) set##_KWSET(KWSET__DFLT) }
+#define KWSET__SPINIT(type, name, dflt) 0,
+#define KWSET__DFLT(type, name, dflt) dflt,
+
+/* --- @KW_PARSE@, @KW_PARSE_EMPTY@ --- *
+ *
+ * Arguments:  @set@ = the keyword set name
+ *             @kw@ = the name of the keyword argument structure
+ *             @kwfirst@ = the first keyword argument name from the
+ *                     variable-length argument tail (and, therefore, the
+ *                     final positional argument)
+ *
+ * Use:                Invokes the appropriate `kwparse' function to process the
+ *             function's variable-length argument tail as keyword
+ *             arguments.
+ *
+ *             It is recommended that functions avoid allocating resources
+ *             or making observable changes to program state until they have
+ *             successfully parsed their keyword arguments.
+ *
+ *             It is not possible to define an empty keyword argument set.
+ *             If a function currently accepts no keyword argumets, but
+ *             wants to reserve the ability to accept them later, then it
+ *             should use @KW_PARSE_EMPTY@ (or, better, @KWPARSE_EMPTY@
+ *             below).  The keyword argument set name here is used only for
+ *             diagnostic purposes, and need not (and probably should not)
+ *             correspond to a keyword-set definition macro.
+ */
+
+#define KW_PARSE(set, kw, kwfirst) do {                                        \
+  va_list ap_;                                                         \
+  va_start(ap_, kwfirst);                                              \
+  set##_kwparse(&(kw), kwfirst, &ap_, 0, 0);                           \
+  va_end(ap_);                                                         \
+} while (0)
+
+#define KW_PARSE_EMPTY(set, kwfirst) do {                              \
+  va_list ap_;                                                         \
+  va_start(ap_, kwfirst);                                              \
+  kw_parseempty(#set, kwfirst, &ap, 0, 0);                             \
+  va_end(ap_);                                                         \
+} while (0)
+
+/* --- @KWPARSE@, @KWPARSE_EMPTY@ --- *
+ *
+ * Arguments:  @set@ = the keyword set name
+ *
+ * Use:                All-in-one keyword parsing for simple cases.
+ *
+ *             This declares a keyword argument structure literally named
+ *             @kw@, and parses the function's variable-length argument tail
+ *             on the assumption that the function's argument list prototype
+ *             contains a @KWTAIL@ marker.
+ *
+ *             It is recommended that functions avoid allocating resources
+ *             or making observable changes to program state until they have
+ *             successfully parsed their keyword arguments.
+ *
+ *             In C89, this macro must be placed precisely between the
+ *             declarations at the start of the function body, and the
+ *             statements after them.
+ *
+ *             It is not possible to define an empty keyword argument set.
+ *             If a function currently accepts no keyword argumets, but
+ *             wants to reserve the ability to accept them later, then it
+ *             should use @KWPARSE_EMPTY@.  The keyword argument set name
+ *             here is used only for diagnostic purposes, and need not (and
+ *             probably should not) correspond to a keyword-set definition
+ *             macro.
+ */
+
+#define KWPARSE(set) KWDECL(set, kw); KW_PARSE(set, kw, kwfirst_)
+
+#define KWPARSE_EMPTY(set) KW_PARSE_EMPTY(set, kwfirst_)
+
+/* --- @KW_COUNT@ --- *
+ *
+ * Arguments:  @set@ = the keyword set name
+ *
+ * Use:                Expands to the number of keywords defined in the @set@.
+ */
+
+#define KW_COUNT(set) (0u set##_KWSET(KW__COUNT))
+#define KW__COUNT(type, name, dflt) + 1u
+
+/* --- @KW_COPY@ --- *
+ *
+ * Arguments:  @fromset@ = the source keyword set name
+ *             @toset@ = the destination keyword set name
+ *             @kw@ = the source keyword argument structure
+ *             @v@ = the destination vector
+ *             @n@ = next free index in vector
+ *
+ * Use:                Copies arguments from the source structure @kw@ into the
+ *             vector @v@.  The structure @kw@ must have type @struct
+ *             fromset_kwargs *@.  The argument @v@ must have type @struct
+ *             kwval *@ (after array-to- pointer decay), and there must be a
+ *             variable @v_n@ of sufficiently large integral type suitably
+ *             initialized.  Elements of the vector, starting with element
+ *             @n@, will be filled in with those keyword arguments defined
+ *             in @toset@ -- which must be a subset of @srcsrc@ from @kw@
+ *             for which the `_suppliedp' flags are set.  The @val@ members
+ *             will point directly into the @kw@ structure.  The @n@
+ *             counter will be updated, and on completion will contain the
+ *             index of the first unused entry in the vector.
+ */
+
+#define KW_COPY(fromset, toset, kw, v, n) do {                         \
+  const struct fromset##_kwargs *kw_ = &(kw);                          \
+  struct kwval *v_ = (v);                                              \
+  size_t n_ = (n);                                                     \
+  toset##_KWSET(KW__COPY)                                              \
+  (n) = n_;                                                            \
+} while (0)
+
+#define KW__COPY(type, name, dflt)                                     \
+  if (kw_->name##_suppliedp) {                                         \
+    v_[n_].kw = #name;                                                 \
+    v_[n_].val = &kw_->name;                                           \
+    n_++;                                                              \
+  }
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @kw_unknown@ --- *
+ *
+ * Arguments:  @const char *set@ = the keyword set name, as a string
+ *             @const char *kw@ = the unknown keyword argument, as a string
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                Called when an unrecognized keyword argument is encountered
+ *             during parsing.  This calls the @kw_unkhook@ with the same
+ *             arguments. Recovery via @longjmp@ or a similar machanism is
+ *             acceptable.
+ */
+
+extern KW__NORETURN void kw_unknown(const char */*set*/, const char */*kw*/);
+
+/* --- @kw_defunknown@ --- *
+ *
+ * Arguments:  @const char *set@ = keyword set name
+ *             @const char *kw@ = the offending keyword name
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                This is the default @kw_unkhook@ hook function.
+ *
+ *             In a hosted implementation, this function reports an internal
+ *             error to stderr about the unknown keyword and calls @abort@.
+ *             It is an implementation responsibility for freestanding
+ *             implementations wanting to use this keyword argument
+ *             mechanism.
+ */
+
+extern KW__NORETURN kw_unkhookfn kw_defunknown;
+
+/* --- @kw__hookfailed@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                Called by @kw_unknown@ if the @kw_unkhook@ hook function
+ *             returns.
+ *
+ *             User code is not expected to call this function.  It exists
+ *             as an implementation respensibility for freestanding
+ *             implementations wanting to use this keyword argument
+ *             mechanism.
+ */
+
+extern KW__NORETURN void kw__hookfailed(void);
+
+/* --- @kw_parseempty@ --- *
+ *
+ * Arguments:  @const char *set@ = the keyword set name, as a string
+ *             @const char *kwfirst@ = the first keyword argument name
+ *             @va_list *ap@ = pointer to argument-tail extraction state
+ *             @const struct kwval *v@ = base address of argument vector
+ *             @size_t n@ = size of argument vector
+ *
+ * Returns:    ---
+ *
+ * Use:                Goes through the motions of parsing keyword arguments, but
+ *             doesn't in fact handle any other than the standard ones
+ *             described above (see @KWSET_PARSEFN@).  This is useful when a
+ *             function doesn't currently define any keyword arguments but
+ *             wants to reserve the right to define some in the future.
+ *             (The usual machinery can't be used in this case, since the
+ *             argument structure would be empty.  Besides, it would be
+ *             pointless to include multiple copies of the same boilerplate
+ *             code in a program.)
+ */
+
+extern void kw_parseempty(const char */*set*/,
+                         const char */*kwfirst*/, va_list */*ap*/,
+                         const struct kwval */*v*/, size_t /*n*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif