From 9e91c8e7b5fcdeb6389ac7ccbcd9c77348c4493a Mon Sep 17 00:00:00 2001
From: Mark Wooding <mdw@distorted.org.uk>
Date: Sun, 10 Jan 2016 13:51:04 +0000
Subject: [PATCH] lib/: Pure C machinery for handling `keyword arguments' to
 functions.

The new `keyword.h' header file defines a bunch of (somewhat nasty)
macros which allow one to define, parse, pass, and access things which
look a bit like Common Lisp's keyword arguments.  There are a few
support functions, mostly for reporting errors.
---
 debian/libsod-dev.install |    1 +
 doc/concepts.tex          |   39 ++
 doc/runtime.tex           |  554 +++++++++++++++++++++
 lib/Makefile.am           |    4 +
 lib/keyword-hosted.c      |   79 +++
 lib/keyword.3             | 1171 +++++++++++++++++++++++++++++++++++++++++++++
 lib/keyword.c             |  110 +++++
 lib/keyword.h             |  583 ++++++++++++++++++++++
 lib/sod.h                 |    1 +
 test/Makefile.am          |    8 +
 test/kwtest.c             |  143 ++++++
 test/kwtest.ref           |   30 ++
 12 files changed, 2723 insertions(+)
 create mode 100644 lib/keyword-hosted.c
 create mode 100644 lib/keyword.3
 create mode 100644 lib/keyword.c
 create mode 100644 lib/keyword.h
 create mode 100644 test/kwtest.c
 create mode 100644 test/kwtest.ref

diff --git a/debian/libsod-dev.install b/debian/libsod-dev.install
index 517bae1..e4ce4b1 100644
--- a/debian/libsod-dev.install
+++ b/debian/libsod-dev.install
@@ -3,5 +3,6 @@
 /usr/lib/*/libsod.la
 /usr/lib/*/libsod.so
 /usr/lib/*/pkgconfig
+/usr/share/man/man3/keyword.3
 /usr/share/man/man3/sod.3
 /usr/share/man/man3/sod-struct.3
diff --git a/doc/concepts.tex b/doc/concepts.tex
index 20402dc..8a8eb02 100644
--- a/doc/concepts.tex
+++ b/doc/concepts.tex
@@ -432,6 +432,45 @@ nickname @|super|, then the macro @|MYCLASS__CONV_SUPER| converts a
 \xref{sec:structures.layout.additional} for the formal description.
 
 %%%--------------------------------------------------------------------------
+\section{Keyword arguments} \label{sec:concepts.keywords}
+
+In standard C, the actual arguments provided to a function are matched up
+with the formal arguments given in the function definition according to their
+ordering in a list.  Unless the (rather cumbersome) machinery for dealing
+with variable-length argument tails (@|<stdarg.h>|) is used, exactly the
+correct number of arguments must be supplied, and in the correct order.
+
+A \emph{keyword argument} is matched by its distinctive \emph{name}, rather
+than by its position in a list.  Keyword arguments may be \emph{omitted},
+causing some default behaviour by the function.  A function can detect
+whether a particular keyword argument was supplied: so the default behaviour
+need not be the same as that caused by any specific value of the argument.
+
+Keyword arguments can be provided in three ways.
+\begin{enumerate}
+\item Directly, as a variable-length argument tail, consisting (for the most
+  part) of alternating keyword names, as pointers to null-terminated strings,
+  and argument values, and terminated by a null pointer.  This is somewhat
+  error-prone, and the support library defines some macros which help ensure
+  that keyword argument lists are well formed.
+\item Indirectly, through a @|va_list| object capturing a variable-length
+  argument tail passed to some other function.  Such indirect argument tails
+  have the same structure as the direct argument tails described above.
+  Because @|va_list| objects are hard to copy, the keyword-argument support
+  library consistently passes @|va_list| objects \emph{by reference}
+  throughout its programming interface.
+\item Indirectly, through a vector of @|struct kwval| objects, each of which
+  contains a keyword name, as a pointer to a null-terminated string, and the
+  \emph{address} of a corresponding argument value.  (This indirection is
+  necessary so that the items in the vector can be of uniform size.)
+  Argument vectors are rather inconvenient to use, but are the only practical
+  way in which a caller can decide at runtime which arguments to include in a
+  call, which is useful when writing wrapper functions.
+\end{enumerate}
+
+Keyword arguments are provided as a general feature for C functions.
+
+%%%--------------------------------------------------------------------------
 \section{Messages and methods} \label{sec:concepts.methods}
 
 Objects can be sent \emph{messages}.  A message has a \emph{name}, and
diff --git a/doc/runtime.tex b/doc/runtime.tex
index 8b8e080..f6f0846 100644
--- a/doc/runtime.tex
+++ b/doc/runtime.tex
@@ -30,6 +30,560 @@ the Sod library.  The common structure of object instances and classes is
 described in \xref{ch:structures}.
 
 %%%--------------------------------------------------------------------------
+\section{Keyword argument support} \label{sec:runtime.keywords}
+
+This section describes the types, macros, and functions exposed in the
+@|<sod/keyword.h>| header file which provides support for defining and
+calling functions which make use of keyword arguments; see \xref{sec:concepts.keywords}.
+
+
+\subsection{Type definitions} \label{sec:sec:runtime.keywords.types}
+
+The header file defines two simple structure types, and a function type which
+will be described later.
+
+\begin{describe}[struct kwval]{type}
+    {struct kwval \{ \\ \ind
+       const char *kw; \\
+       const void *val; \- \\
+     \};}
+
+  The @|kwval| structure describes a keyword argument name/value pair.  The
+  @|kw| member points to the name, as a null-terminated string.  The @|val|
+  member always contains the \emph{address} of the value.  (This somewhat
+  inconvenient arrangement makes the size of a @|kwval| object independent of
+  the actual argument type.)
+\end{describe}
+
+\begin{describe}[struct kwtab]{type}
+    {struct kwtab \{ \\ \ind
+       const struct kwval *v; \\
+       size_t n; \- \\
+     \};}
+
+  The @|kwtab| structure describes a list of keyword arguments, represented
+  as a vector of @|kwval| structures.  The @|v| member points to the start of
+  the vector; the @|n| member contains the number of elements in the vector.
+\end{describe}
+
+
+\subsection{Calling functions with keyword arguments}
+\label{sec:runtime.keywords.calling}
+
+Functions which accept keyword arguments are ordinary C functions with
+variable-length argument tails.  Hence, they can be called using ordinary C
+(of the right kind) and all will be well.  However, argument lists must
+follow certain rules (which will be described in full below); failure to do
+this will result in \emph{undefined behaviour}.
+
+The header file provides integration with some C compilers in the form of
+macros which can be used to help the compiler diagnose errors in calls to
+keyword-accepting functions; but such support is rather limited at the
+moment.  Some additional macros are provided for use in calls to such
+functions, and it is recommended that, where possible, these are used.  In
+particular, it's all too easy to forget the trailing null terminator which
+marks the end of a list of keyword arguments.
+
+That said, the underlying machinery is presented first, and the convenience
+macros are described later.
+
+\subsubsection{Keyword argument mechanism}
+The argument tail, following the mandatory arguments, consists of a sequence
+of zero or more alternating keyword names, as pointers to null-terminated
+strings (with type @|const char~*|), and their argument values.  This
+sequence is finally terminated by a null pointer (again with type @|const
+char~*|) in place of a keyword name.
+
+Each function may define for itself which keyword names it accepts,
+and what types the corresponding argument values should have.
+There are also (currently) three special keyword names.
+\begin{description} \let\makelabel\code
+
+\item[kw.valist] This special keyword is followed by a pointer to a
+  variable-length argument tail cursor object, of type @|va_list~*|.  This
+  cursor object will be modified as the function extracts successive
+  arguments from the tail.  The argument tail should consist of alternating
+  keyword names and argument values, as described above, including the first
+  keyword name.  (This is therefore different from the convention used when
+  calling keyword argument parser functions: see the description of the
+  \descref{KWSET_PARSEFN}[macro]{mac} for more details about these.)  The
+  argument tail may itself contain the special keywords.
+
+\item[kw.tab] This special keyword is followed by \emph{two} argument values:
+  a pointer to the base of a vector of @|kwval| structures, and the number of
+  elements in this vector (as a @|size_t|).  Each element of the vector
+  describes a single keyword argument: the @|kw| member points to the
+  keyword's name, and the @|val| member points to the value.
+
+  The vector may contain special keywords.  The @|val| pointer for a
+  @|kw.valist| argument should contain the address of an object of type
+  @|va_list~*| (and not point directly to the cursor object, since @|val| is
+  has type @|const void~*| but the cursor will be modified as its argument
+  tail is traversed).  The @|val| pointer for a @|kw.tab| argument should
+  contain the address of a @|kwtab| structure which itself contains the base
+  address and length of the argument vector to be processed.
+
+\item[kw.unknown] This keyword is never accepted by any function.  If it is
+  encountered, the @|kw_unknown| function is called to report the situation
+  as an error; see below.
+
+\end{description}
+It is possible to construct a circular structure of indirect argument lists
+(in a number of ways).  Don't try to pass such a structure to a function: the
+result will be unbounded recursion or some other bad outcome.
+
+\subsubsection{Argument list structuring macros}
+The following macros are intended to help with constructing keyword argument
+lists.  Their use is not essential, but may help prevent errors.
+
+\begin{describe}[KWARGS]{mac}{KWARGS(@<body>)}
+  The @<body> encloses a sequence of keyword arguments expressed as calls to
+  argument consists of a sequence of calls to the keyword-argument macros
+  described below, one after another without any separation.
+
+  In C89, macro actual arguments are not permitted to be empty; if there are
+  no keyword arguments to provide, and you're using a C89 compiler, then use
+  @|NO_KWARGS| (below) instead.  If your compiler supports C99 or later, it's
+  fine to just write @|KWARGS()| instead.
+\end{describe}
+
+\begin{describe}{mac}{NO_KWARGS}
+  A marker, to be written instead of a @|KWARGS| invocation, to indicate that
+  no keyword arguments are to be passed to a function.
+
+  This is unnecessary with compilers which support C99 or later, since once
+  can use @|KWARGS()| with an empty @<body> argument.
+\end{describe}
+
+The following keyword-argument macros can be used within the @|KWARGS|
+@<body> argument.
+
+\begin{describe}[K]{mac}{K(@<name>, @<value>)}
+  Passes a keyword @<name> and its corresponding @<value>, as a pair of
+  arguments.  The @<name> should be a single identifier (not a quoted
+  string).  The @<value> may be any C expression of the appropriate type.
+\end{describe}
+
+\begin{describe}[K_VALIST]{mac}{K_VALIST(@<ap>)}
+  Passes an indirect variable-length argument tail.  The argument @<ap>
+  should be an lvalue of type @|va_list|, which will be passed by reference.
+\end{describe}
+
+\begin{describe}[K_TAB]{mac}{K_TAB(@<v>, @<n>)}
+  Passes a vector of keyword arguments.  The argument @<v> should be the base
+  address of the vector, and @<n> should be the number of elements in the
+  vector.
+\end{describe}
+
+
+\subsection{Defining functions with keyword arguments}
+\label{sec:runtime.keywords.defining}
+
+\subsubsection{Keyword sets}
+A \emph{keyword set} defines the collection of keyword arguments accepted by
+a particular function.  The same keyword set may be used by several
+functions.  (If your function currently accepts no keyword arguments, but you
+plan to add some later, do not define a keyword set, and use the
+@|KWPARSE_EMPTY| macro described below.)
+
+Each keyword set has a name, which is a C identifier.  It's good to choose
+meaningful and distinctive names for keyword sets.  Keyword set names are
+meaningful at runtime: they are used as part of the @|kw_unknown| protocol
+(\xref{sec:runtime.keywords.unknown}), and may be examined by handler
+functions, or reported to a user in error messages.  For a keyword set which
+is used only by a single function, it is recommended that the set be given
+the same name as the function.
+
+The keyword arguments for a keyword set named @<set> are described by a `list
+macro' named @|@<set>{}_KWSET|.  This macro takes a single argument,
+conventionally named @`_'.
+
+It should expand to a sequence of one or more list items of the form
+\begin{prog}
+  _(@<type>, @<name>, @<default>)
+\end{prog}
+with no separation between them.
+
+For example:
+\begin{prog}
+  \#define example_KWSET(_) @\\ \\ \ind
+    _(int, x, 0) @\\ \\
+    _(const char *, y, NULL)
+\end{prog}
+
+Each @<name> should be a distinct C identifier; they will be used to name
+structure members.  An argument @<name> should not end with the suffix
+@`_suppliedp' (for reasons which will soon become apparent).
+
+Each @<type> should be a C @<type-name> such that
+\begin{prog}
+  @<type> @<name> ;
+\end{prog}
+is a valid declaration: so it may consist of declaration specifiers and
+(possibly qualified) pointer declarator markers, but not array or function
+markers (since they would have to be placed after the @<name>).  This is the
+same requirement made by the standard \man{va_arg}{3} macro.
+
+Each @<default> should be an initializer expression or brace-enclosed list,
+suitable for use in an aggregate initializer for a variable with automatic
+storage duration.  (In C89, aggregate initializers may contain only constant
+expressions; this restriction was lifted in C99.)
+
+\subsubsection{Function declaration markers}
+The following marker macros are intended to be used in both declarations and
+definitions of functions which accept keyword arguments.
+
+\begin{describe}{mac}{KWTAIL}
+  The @|KWTAIL| is expected to be used at the end of function parameter type
+  list to indicate that the function accepts keyword arguments; if there are
+  preceding mandatory arguments then the @|KWTAIL| marker should be separated
+  from them with a comma @`,'.  (It is permitted for a function parameter
+  type list to contain only a @|KWTAIL| marker.)
+
+  Specifically, the macro declares a mandatory argument @|const char
+  *kwfirst_| (to collect the first keyword name), and a variable-length
+  argument tail.
+
+  The \descref{KWPARSE}[macro]{mac} assumes that the enclosing function's
+  argument list ends with a @|KWTAIL| marker.
+\end{describe}
+
+\begin{describe}{mac}{KWCALL}
+  The @|KWCALL| macro acts as a declaration specifier for functions which
+  accept keyword arguments.  Its effect is to arrange for the compiler to
+  check, as far as is possible, that calls to the function are well-formed
+  according to the keyword-argument rules.  The exact checking performed
+  depends on the compiler's abilities (and how well supported the compiler
+  is): it may check that every other argument is a string; it may check that
+  the list is terminated with a null pointer; it may not do anything at all.
+  Again, this marker should be included in a function's definition and in any
+  declarations.
+\end{describe}
+
+\subsubsection{Auxiliary definitions}
+The following macros define data types and functions used for collecting
+keyword arguments.
+
+\begin{describe}[KWSET_STRUCT]{mac}{KWSET_STRUCT(@<set>);}
+  The @|KWSET_STRUCT| macro defines a \emph{keyword structure} named @|struct
+  @<set>{}_kwargs|.  For each argument defined in the keyword set, this
+  structure contains two members: one has exactly the @<name> and @<type>
+  listed in the keyword set definition; the other is a 1-bit-wide bitfield of
+  type @|unsigned int| named @|@<name>{}_suppliedp|.
+\end{describe}
+
+\begin{describe}[KWDECL]{mac}
+    {@<declaration-specifiers> KWDECL(@<set>, @<kw>);}
+  The macro declares and initializes a keyword argument structure variable
+  named @<kw> for the named keyword @<set>.  The optional
+  @<declaration-specifiers> may provide additional storage-class, qualifiers,
+  or other declaration specifiers.  The @`_suppliedp' flags are initialized
+  to zero; the other members are initialized with the corresponding defaults
+  from the keyword-set definition.
+\end{describe}
+
+\begin{describe}[KWSET_PARSEFN]{mac}
+    {@<declaration-specifiers> KWSET_PARSEFN(@<set>)}
+
+  The macro @|KWSET_PARSEFN| defines a keyword argument \emph{parser
+  function}
+  \begin{prog}
+    void @<set>{}_kwparse(\=struct @<set>{}_kwargs *@<kw>,
+                            const char *@<kwfirst>, va_list *@<ap>, \+ \\
+                            const struct kwval *@<v>, size_t @<n>);
+  \end{prog}
+  The macro call can (and usually will) be preceded by storage class
+  specifiers such as @|static|, for example to adjust the linkage of the
+  name.\footnote{%
+    I don't recommend declaring parser functions @|inline|: parser functions
+    are somewhat large, and modern compilers are pretty good at figuring out
+    whether to inline static functions.} %
+
+  The function's behaviour is as follows.  It parses keyword arguments from a
+  variable-length argument tail, and/or a vector of @|kwval| structures.
+  When a keyword argument is recognized, for some keyword @<name>, the
+  keyword argument structure pointed to by @<kw> is updated: the flag
+  @|@<name>{}_suppliedp| is set to 1; and the argument value is stored (by
+  simple assignment) in the @<name> member.
+
+  Hence, if the @`_suppliedp' members are initialized to zero, the caller can
+  determine which keyword arguments were supplied.  It is not possible to
+  discover whether two or more arguments have the same keyword: in this case,
+  the value from the last such argument is left in the keyword argument
+  structure, and any values from earlier arguments are lost.  (For this
+  purpose, the argument vector @<v> is scanned \emph{after} the
+  variable-length argument tail captured in @<ap>.)
+
+  The variable-argument tail is read from the list described by @|* @<ap>|.
+  The argument tail is expected to consist of alternating keyword strings (as
+  ordinary null-terminated strings) and the corresponding values, terminated
+  by a null pointer of type @|const char~*| in place of a keyword; except
+  that the first keyword (or terminating null pointer, if no arguments are
+  provided) is expected to have been extracted already and provided as the
+  @<kwfirst> argument; the first argument retrieved using the @|va_list|
+  cursor object should then be the value corresponding to the keyword named
+  by @<kwfirst>.\footnote{%
+    This slightly unusual convention makes it possible for a function to
+    collect the first keyword as a separate mandatory argument, which is
+    essential if there are no other mandatory arguments.  It also means that
+    the compiler will emit a diagnostic if you attempt to call a function
+    which expects keyword arguments, but don't supply any and forget the null
+    pointer which terminates the (empty) list.} %
+  If @<kwfirst> is a null pointer, then @<ap> need not be a valid pointer;
+  otherwise, the cursor object @|* @<ap>| will be modified as the function
+  extracts successive arguments from the tail.
+
+  The keyword vector is read from the vector of @|kwval| structures starting
+  at address @<v> and containing the following @<n> items.  If @<n> is zero
+  then @<v> need not be a valid pointer.
+
+  The function also handles the special @|kw.valist| and @|kw.tab| arguments
+  described above (\xref{sec:runtime.keywords.calling}).  If an unrecognized
+  keyword argument is encountered, then \descref{kw_unknown}{fun} is called.
+\end{describe}
+
+\subsubsection{Parsing keywords}
+The following macros make use of the definitions described above to actually
+make a function's keyword arguments available to it.
+
+\begin{describe}[KW_PARSE]{mac}{KW_PARSE(@<set>, @<kw>, @<kwfirst>);}
+  The @|KW_PARSE| macro invokes a keyword argument parsing function.  The
+  @<set> argument should name a keyword set; @<kw> should be an lvalue of
+  type @|struct @<set>{}_kwargs|; and @<kwfirst> should be the name of the
+  enclosing function's last mandatory argument, which must have type @|const
+  char~*|.
+
+  It calls the function @|@<set>{}_kwparse| with five arguments: the address
+  of the keyword argument structure @<kw>; the string pointer @<kwfirst>; the
+  address of a temporary argument-tail cursor object of type @|va_list|,
+  constructed on the assumption that @<kwfirst> is the enclosing function's
+  final keyword argument; a null pointer; and the value zero (signifying an
+  empty keyword-argument vector).
+
+  If the variable @<kw> was declared using \descref{KWDECL}{mac} and the
+  function @|@<set>{}_kwparse| has been defined using
+  \descref{KWSET_PARSEFN}{mac} then the effect is to parse the keyword
+  arguments passed to the function and set the members of @<kw>
+  appropriately.
+\end{describe}
+
+\begin{describe}[KWPARSE]{mac}{KWPARSE(@<set>);}
+  The macro @|KWPARSE| (note the lack of underscore) combines
+  \descref{KWDECL}{mac} and \descref{KW_PARSE}{mac}.  It declares and
+  initializes a keyword argument structure variable with the fixed name
+  @|kw|, and parses the keyword arguments provided to the enclosing function,
+  storing the results in @|kw|.  It assumes that the first keyword name is in
+  an argument named @|kwfirst_|, as set up by the
+  \descref{KWTAIL}[marker]{mac}.
+
+  The macro expands both to a variable declaration and a statement: in C89,
+  declarations must precede statements, so under C89 rules this macro must
+  appear exactly between the declarations at the head of a brace-enclosed
+  block (typically the function body) and the statements at the end.  This
+  restriction was lifted in C99, so the macro may appear anywhere in the
+  function body.  However, it is recommended that callers avoid taking
+  actions which might require cleanup before attempting to parse their
+  keyword arguments, since keyword argument parsing functions invoke the
+  @|kw_unknown| handler (\xref{sec:runtime.keywords.unknown}) if they
+  encounter an unknown keyword, and the calling function will not get a
+  chance to tidy up after itself if this happens.
+\end{describe}
+
+As mentioned above, it is not permitted to define an empty keyword set.
+(Specifically, invoking \descref{KWSET_STRUCT}{mac} for an empty keyword set
+would result in attempting to define a structure with no members, which C
+doesn't allow.)  On the other hand, keyword arguments are a useful extension
+mechanism, and it's useful to be able to define a function which doesn't
+currently accept any keywords, but which might in the future be extended to
+allow keyword arguments.
+
+\begin{describe}[KW_PARSE_EMPTY]{mac}{KW_PARSE_EMPTY(@<set>, @<kwfirst>);}
+  This is an analogue to \descref{KW_PARSE}{mac} which checks the keyword
+  argument list for a function which accepts no keyword arguments.
+
+  It calls the \descref{kw_parseempty}[function]{fun} with five arguments:
+  the @<set> name, as a string; the string pointer @<kwfirst>; the address of
+  a temporary argument-tail cursor object of type @|va_list|, constructed on
+  the assumption that @<kwfirst> is the enclosing function's final keyword
+  argument; a null pointer; and the value zero (signifying an empty
+  keyword-argument vector).
+
+  The effect is to check that the argument tail contains no keyword arguments
+  other than the special predefined ones.
+\end{describe}
+
+\begin{describe}[KWPARSE_EMPTY]{mac}{KWPARSE_EMPTY(@<set>);}
+  This is an analogue to \descref{KWPARSE}{mac} which checks that the
+  enclosing function has been passed no keyword arguments other than the
+  special predefined ones.  It assumes that the first keyword name is in an
+  argument named @|kwfirst_|, as set up by the \descref{KWTAIL}[marker]{mac}.
+\end{describe}
+
+\begin{describe}[kw_parseempty]{fun}
+    {void kw_parseempty(\=const char *@<set>,
+                          const char *@<kwfirst>, va_list *@<ap>, \+ \\
+                          const struct kwval *@<v>, size_t @<n>);}
+  This function checks an keyword argument list to make sure that contains no
+  keyword arguments (other than the special ones described in
+  \xref{sec:runtime.keywords.calling}).
+
+  The @<set> argument should point to a null-terminated string: this will be
+  reported as the keyword set name to \descref{kw_unknown}{fun}, though it
+  need not (and likely will not) refer to any defined keyword set.  The
+  remaining arguments are as for the keyword parsing functions defined by the
+  \descref{KWSET_PARSEFN}[macro]{mac}.
+\end{describe}
+
+\subsection{Function wrappers} \label{sec:runtime.keywords.wrappers}
+
+Most users will not need the hairy machinery involving argument vectors.
+Their main use is in defining \emph{wrapper functions}.  Suppose there is a
+function @<f> which accepts some keyword arguments, and we want to write a
+function @<g> which accepts the same keywords recognized by @<f> and some
+additional ones.  Unfortunately @<f> may behave differently depending on
+whether or not a particular keyword argument is supplied at all, but it's not
+possible to synthesize a valid @|va_list| other than by simply capturing a
+live argument tail, and it's not possible to decide at runtime whether or not
+to include some arguments in a function call.  It's still possible to write
+@<g>, by building a vector of keyword arguments, collected one-by-one
+depending on the corresponding @`_suppliedp' flags.
+
+A few macros are provided to make this task easier.
+
+\begin{describe}[KW_COUNT]{mac}{KW_COUNT(@<set>)}
+  Returns the number of keywords defined in a keyword set named @<set>.
+\end{describe}
+
+\begin{describe}[KW_COPY]{mac}
+    {KW_COPY(@<fromset>, @<toset>, @<kw>, @<v>, @<n>);}
+
+  The macro @|KW_COPY| populates a vector of @|kwval| structures from a
+  keyword-argument structure.
+
+  The @<fromset> and @<toset> arguments should be the names of keyword sets;
+  @<kw> should be an lvalue of type @|@<fromset>{}_kwargs|; @<v> should be
+  the base address of a sufficiently large vector of @|struct kwval| objects;
+  and @<n> should be an lvalue of some appropriate integer type.  The
+  @<toset> must be a subset of @<fromset>: i.e., for every keyword defined in
+  @<toset> there is a keyword defined in @<fromset> with the same name and
+  type.
+
+  Successive elements of @<v>, starting at index @<n>, are filled in to refer
+  to the keyword arguments defined in @<toset> whose @`_suppliedp' flag is
+  set in the argument structure pointed to by @<kw>; for each such argument,
+  a pointer to the keyword name is stored in the corresponding vector
+  element's @|kw| member, and a pointer to the argument value, held in the
+  keyword argument structure, is stored in the vector element's @|val|
+  member.
+
+  At the end of this, the index @<n> is advanced so as to contain the index
+  of the first unused element of @<v>.  Hence, at most @|KW_COUNT(@<toset>)|
+  elements of @<v> will be used.
+\end{describe}
+
+
+\subsection{Handling unknown-keyword errors}
+\label{sec:runtime.keywords.unknown}
+
+When parsing a variable-length argument tail, it is not possible to continue
+after encountering an unknown keyword name.  This is because it is necessary
+to know the (promoted) type of the following argument value in order to skip
+past it; but the only clue provided as to the type is the keyword name, which
+in this case is meaningless.
+
+In this situation, the parser functions generated by
+\descref{KWSET_PARSEFN}{mac} (and the \descref{kw_parseempty}[function]{fun})
+call @|kw_unknown|.
+
+\begin{describe}[kw_unknown]{fun}
+    {void kw_unknown(const char *@<set>, const char *@<kw>);}
+
+  This is a function of two arguments: @<set> points to the name of the
+  keyword set expected by the caller, as a null-terminated string; and @<kw>
+  is the unknown keyword which was encountered.  All that @|kw_unknown| does
+  is invoke the function whose address is stored in the global variable
+  \descref{kw_unkhook}{var} with the same arguments.
+
+  This function never returns to its caller: if the @|kw_unkhook| function
+  returns (which it shouldn't) then @|kw_unknown| writes a fatal error
+  message to the standard error stream and calls \man{abort}{3}.
+\end{describe}
+
+\begin{describe}[kw_unkhookfn]{type}
+    {typedef void kw_unkhookfn(const char *@<set>, const char *@<kw>);}
+
+  The @|kw_unkhookfn| type is the type of unknown-keyword handler functions.
+  A handler function is given two arguments, both of which are pointers to
+  null-terminated strings: @<set> is the name of the keyword set expected;
+  and @<kw> is the name of the offending unknown keyword.
+\end{describe}
+
+\begin{describe}[kw_unkhook]{var}{kw_unkhookfn *kw_unkhook}
+  This variable\footnote{%
+    Having a single global hook variable is obviously inadequate for a modern
+    library, but dealing with multiple threads isn't currently possible
+    without writing (moderately complex) system-specific code which would be
+    out of place in this library.  The author's intention is that the hook
+    variable @|kw_unkhook| be `owned' by some external library which can make
+    its functionality available to client programs in a safer and more
+    convenient way.  On Unix-like platforms (including Cygwin) that library
+    will be (a later version of) \textbf{mLib}; other platforms will likely
+    need different arrangements.  The author is willing to coordinate any
+    such efforts.} %
+  holds the current unknown-keyword handler function.  It will be invoked by
+  \descref{kw_unknown}{fun}.  The function may take whatever action seems
+  appropriate, but should not return to its caller.
+
+  Initially, this variable points to the
+  \descref{kw_defunknown}[function]{fun}.
+\end{describe}
+
+\begin{describe}[kw_defunknown]{fun}
+    {void kw_defunknown(const char *@<set>, const char *@<kw>);}
+  This function simply writes a message to standard error, to the effect that
+  the keyword named by @<kw> is not known in the keyword set @<set>, and
+  calls \man{abort}{3}.
+
+  This function is the default value of the \descref{kw_unkhook}[hook
+  variable]{var}.
+\end{describe}
+
+As an example of the kind of special effect which can be achieved using this
+hook, the following hacking answers whether a function recognizes a
+particular keyword argument.
+
+\begin{prog}
+  \#define KWARGS_TEST(k, val) KWARGS(K(k, val) K(kw.unknown, 0))
+  \\+
+  static jmp_buf kw_test_jmp;
+  \\+
+  static void kw_test_unknown(const char *set, const char *kw) \\
+  \{ \\ \ind
+    if (strcmp(kw, "kw.unknown")) longjmp(kw_test_jmp, 1); \\
+    else longjmp(kw_test_jmp, 2); \- \\
+  \}
+  \\+
+  \#define KW_TEST(flag, set, call) do \{ @\\ \\ \ind
+    kw_unkhookfn *oldunk = kw_unkhook; @\\ \\
+    kw_unkhook = kw_test_unknown; @\\ \\
+    switch (setjmp(kw_test_jmp)) \{ @\\ \\ \ind
+      case 0: call; abort(); @\\ \\
+      case 1: flag = 1; break; @\\ \\
+      case 2: flag = 0; break; @\\ \\
+      default: abort(); \- @\\ \\
+    \} @\\ \\
+    kw_unkhook = oldunk; \- @\\ \\
+  \} while (0)
+  \\+
+  /* Example of use */ \\
+  int f; \\
+  KW_TEST(f, somefunc(1, "two", 3, KWARGS_TEST("shiny", 68.7))); \\
+  /\=* now f is nonzero if `somefunc' accepts the `shiny' keyword \+ \\
+   {}* (which we hope wants a double argument) \\
+   {}*/
+\end{prog}
+
+%%%--------------------------------------------------------------------------
 \section{Object system support} \label{sec:runtime.object}
 
 This section describes the macros and functions exposed in the @|<sod/sod.h>|
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 7cb564a..1a4bb72 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -40,6 +40,10 @@ nodist_pkginclude_HEADERS=
 ###--------------------------------------------------------------------------
 ### The source files.
 
+pkginclude_HEADERS	+= keyword.h
+libsod_la_SOURCES	+= keyword.c keyword-hosted.c
+dist_man_MANS		+= keyword.3
+
 pkginclude_HEADERS	+= sod.h
 libsod_la_SOURCES	+= sod.c
 dist_man_MANS		+= sod.3
diff --git a/lib/keyword-hosted.c b/lib/keyword-hosted.c
new file mode 100644
index 0000000..ee1b186
--- /dev/null
+++ b/lib/keyword-hosted.c
@@ -0,0 +1,79 @@
+/* -*-c-*-
+ *
+ * Keyword-argument support functions requiring a hosted implementation
+ *
+ * (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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @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.
+ */
+
+void kw_defunknown(const char *set, const char *kw)
+{
+  fprintf(stderr, "INTERNAL ERROR: unknown `%s' keyword argument `%s'\n",
+	  set, kw);
+  abort();
+}
+
+/* --- @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.
+ */
+
+void kw__hookfailed(void)
+{
+  fprintf(stderr, "INTERNAL ERROR: `kw_unkhook' hook function returned\n");
+  abort();
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/lib/keyword.3 b/lib/keyword.3
new file mode 100644
index 0000000..88da56e
--- /dev/null
+++ b/lib/keyword.3
@@ -0,0 +1,1171 @@
+.\" -*-nroff-*-
+.\"
+.\" Keyword argument support
+.\"
+.\" (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.
+.
+.\" Highlight using terminal escapes, rather than overstriking.
+.\"\X'tty: sgr 1'
+.
+.\" String definitions and font selection.
+.ie t \{\
+.  ds o \(bu
+.  if \n(.g .fam P
+.\}
+.el \{\
+.  ds o o
+.\}
+.
+.\" .hP TEXT -- start an indented paragraph with TEXT hanging off to the left
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.
+.de t(
+'in +\\n(.ku
+..
+.de t)
+.in
+..
+.
+.\"--------------------------------------------------------------------------
+.TH keyword 3 "16 December 2015" "Straylight/Edgeware" "Sensible Object Design"
+.
+.SH NAME
+keyword \- keyword argument support library
+.
+.\"--------------------------------------------------------------------------
+.SH SYNOPSIS
+.B #include <sod/keyword.h>
+.PP
+.B "struct kwval { const char *kw; const void *val; };"
+.br
+.B "struct kwtab { const struct kwval *v; size_t n; };"
+.br
+.BI "typedef void kw_unkhookfn(const char *" set ", const char *" kw ");"
+.PP
+.BI "#define " set "_KWSET(_) \e"
+.in +4m
+.BI   "_(" name ", " type ", " default ") \e"
+.br
+\&...
+.in
+.IB declaration-specifiers " KWSET_STRUCT(" set ");"
+.br
+.IB declaration-specifiers " KWSET_PARSEFN(" set ")"
+.PP
+.B KWCALL
+.IB type0 " " func "(" type1 " " arg1 ,
+.RB ... ,
+.IB typen " " argn ,
+.B "KWTAIL);"
+.br
+.BI "KWDECL(" set ", " kw ");"
+.br
+.BI "KW_PARSE(" set ", " kw ", " kwfirst ");"
+.br
+.BI "KW_PARSE_EMPTY(" set ", " kwfirst ");"
+.br
+.BI "KWPARSE(" set ");"
+.br
+.BI "KWPARSE_EMPTY(" set ");"
+.PP
+.I val
+.B =
+.IB func "(" arg1 ,
+.RB ... ,
+.IB argn ,
+.BI "KWARGS(" \c
+.t(
+.BI "K(" name ", " value ")"
+.br
+.BI "K_VALIST(" ap ")"
+.br
+.BI "K_TAB(" v ", " n ")"
+.br
+.RB ... );
+.t)
+.br
+.I val
+.B =
+.IB func "(" arg1 ,
+.RB ... ,
+.IB argn ,
+.B "NO_KWARGS);"
+.PP
+.B unsigned
+.BI "KW_COUNT(" set ");"
+.br
+.B void
+.BI "KW_COPY(" \c
+.t(
+.IB fromset ", " toset ","
+.br
+.BI "const struct " fromset "_kwset *" kw ","
+.br
+.BI "struct kwval *" v ", size_t " n ");"
+.t)
+.PP
+.BI "void kw_unknown(const char *" set ", const char *" kw );
+.br
+.BI "void kw_parseempty(\fP" \c
+.t(
+.BI   "const char *" set ,
+.BI   "const char *" kwfirst ,
+.BI   "va_list *" ap ,
+.br
+.BI   "const struct kwval *" v ,
+.BI   "size_t " n );
+.t)
+.PP
+.B "kw_unkhookfn *kw_unkhook;"
+.br
+.B "kw_unkhookfn kw_defunknown;"
+.
+.\"--------------------------------------------------------------------------
+.SH DESCRIPTION
+.
+.SS Theory
+In standard C,
+the actual arguments provided to a function
+are matched up with the formal arguments
+given in the function definition
+according to their ordering in a list.
+Unless the (rather cumbersome) machinery for dealing with
+variable-length argument tails
+.RB ( <stdarg.h> )
+is used,
+exactly the correct number of arguments must be supplied,
+and in the correct order.
+.PP
+A
+.I keyword argument
+is matched by its distinctive
+.IR name ,
+rather than by its position in a list.
+Keyword arguments may be
+.IR omitted ,
+causing some default behaviour by the function.
+A function can detect whether
+a particular keyword argument was supplied:
+so the default behaviour need not be the same as
+that caused by any specific value of the argument.
+.PP
+Keyword arguments can be provided in three ways.
+.hP 1.
+Directly, as a variable-length argument tail,
+consisting (for the most part \(en see below) of alternating
+keyword names, as pointers to null-terminated strings, and
+argument values, and
+terminated by a null pointer.
+This is somewhat error-prone,
+and the support library defines some macros
+which help ensure that keyword argument lists are well formed.
+.hP 2.
+Indirectly, through a
+.B va_list
+object capturing a variable-length argument tail
+passed to some other function.
+Such indirect argument tails have the same structure as
+the direct argument tails described above.
+Because
+.B va_list
+objects are hard to copy,
+the keyword-argument support library consistently passes
+.B va_list
+objects
+.I by reference
+throughout its programming interface.
+.hP 3.
+Indirectly, through a vector of
+.B struct kwval
+objects,
+each of which contains
+a keyword name, as a pointer to a null-terminated string, and
+the
+.I address
+of a corresponding argument value.
+(This indirection is necessary so that
+the items in the vector can be of uniform size.)
+Argument vectors are rather inconvenient to use,
+but are the only practical way in which a caller can decide at runtime
+which arguments to include in a call,
+which is useful when writing wrapper functions.
+.
+.SS Type definitions
+The header file defines two simple structure types.
+.PP
+.IP
+.nf
+.ft B
+struct kwval {
+  const char *kw;
+  const void *val;
+};
+.fi
+.PP
+The
+.B kwval
+structure describes a keyword argument name/value pair.
+The
+.B kw
+member points to the name,
+as a null-terminated string.
+The
+.B val
+member always contains the
+.I address
+of the value.
+(This somewhat inconvenient arrangement
+makes the size of a
+.B kwval
+object independent of the actual argument type.)
+.PP
+.IP
+.nf
+.ft B
+struct kwtab {
+  const struct kwval *v;
+  size_t n;
+};
+.fi
+.PP
+The
+.B kwtab
+structure describes a list of keyword arguments,
+represented as a vector of
+.B kwval
+structures.
+The
+.B v
+member points to the start of the vector;
+the
+.B n
+member contains the number of elements in the vector.
+.PP
+The
+.B kw_unkhookfn
+type is the type of
+unknown-keyword handler functions.
+See the descriptions of
+.B kw_unknown
+and
+.B kw_unkhook
+below.
+.
+.SS Calling functions with keyword arguments
+Functions which accept keyword arguments are ordinary C functions
+with variable-length argument tails.
+Hence, they can be called using ordinary C (of the right kind)
+and all will be well.
+However, argument lists must follow certain rules
+(which will be described in full below);
+failure to do this will result in
+.IR "undefined behaviour" .
+The header file provides integration with some C compilers
+in the form of macros which can be used to help the compiler diagnose
+errors in calls to keyword-accepting functions;
+but such support is rather limited at the moment.
+Some additional macros are provided for use in calls to such functions,
+and it is recommended that, where possible, these are used.
+In particular, it's all too easy to forget the trailing null terminator
+which marks the end of a list of keyword arguments.
+.PP
+That said, the underlying machinery is presented first,
+and the convenience macros are described later.
+.PP
+The argument tail,
+following the mandatory arguments,
+consists of a sequence of zero or more alternating
+keyword names,
+as pointers to null-terminated strings
+(with type
+.BR "const char *" ),
+and their argument values.
+This sequence is finally terminated by a null pointer
+(again with type
+.BR "const char *" )
+in place of a keyword name.
+.PP
+Each function may define for itself which keyword names it accepts,
+and what types the corresponding argument values should have.
+There are also (currently) three special keyword names.
+.TP
+.B kw.valist
+This special keyword is followed by a pointer to
+a variable-length argument tail cursor object, of type
+.BR "va_list *" .
+This cursor object will be modified as the function extracts
+successive arguments from the tail.
+The argument tail should consist of alternating
+keyword names and argument values, as described above,
+including the first keyword name.
+(This is therefore different from the convention used when calling
+keyword argument parser functions:
+see the description of the
+.B KW_PARSEFN
+macro below for more details about these.)
+The argument tail may itself contain the special keywords.
+.TP
+.B kw.tab
+This special keyword is followed by
+.I two
+argument values:
+a pointer to the base of a vector of
+.B kwval
+structures,
+and the number of elements in this vector
+(as a
+.BR size_t ).
+Each element of the vector describes a single keyword argument:
+the
+.B kw
+member points to the keyword's name, and
+the
+.B val
+member points to the value.
+The vector may contain special keywords.
+The
+.B val
+pointer for a
+.B kw.valist
+argument should contain the address of an object of type
+.B "va_list *"
+(and not point directly to the cursor object,
+since
+.B val
+is has type
+.B "const void *"
+but the cursor will be modified as its argument tail is traversed).
+The
+.B val
+pointer for a
+.B kw.tab
+argument should contain the address of a
+.B kwtab
+structure which itself contains the base address and length of
+the argument vector to be processed.
+.TP
+.B kw.unknown
+This keyword is never accepted by any function.
+If it is encountered,
+the
+.B kw_unknown
+function is called to report the situation as an error;
+see below.
+.PP
+It is possible to construct a circular structure
+of indirect argument lists
+(in a number of ways).
+Don't try to pass such a structure to a function:
+the result will be unbounded recursion
+or some other bad outcome.
+.PP
+The macro
+.BI "KWARGS(" body ")"
+wraps up a sequence of keyword arguments.
+The single
+.I body
+argument consists of a sequence of calls to
+the keyword-argument macros described below,
+one after another without any separation.
+.PP
+In C89, macro actual arguments are not permitted to be empty;
+if there are no keyword arguments to provide,
+then the argument-less macro
+.B NO_KWARGS
+should be used instead.
+If you're using C99 or later,
+it's fine to just write
+.B KWARGS()
+instead.
+.PP
+The following keyword-argument macros can be used
+within
+.BR KWARGS 's
+.I body
+argument.
+.TP
+.BI "K(" name ", " value ")"
+Passes a keyword name and its corresponding value,
+as a pair of arguments.
+The
+.I name
+should be a single identifier
+(not a quoted string).
+The
+.I value
+may be any C expression
+of the appropriate type.
+.TP
+.BI "K_VALIST(" ap ")"
+Passes an indirect variable-length argument tail.
+The argument
+.I ap
+should be an lvalue of type
+.B va_list
+which will be passed by reference.
+.TP
+.BI "K_TAB(" v ", " n ")"
+Passes a vector of keyword arguments.
+The argument
+.I v
+should be the base address of the vector, and
+.I n
+should be the number of elements in the vector.
+.
+.SS Defining functions with keyword arguments
+A
+.I "keyword set"
+defines the collection of keyword arguments
+accepted by a particular function.
+The same keyword set may be used by several functions.
+(If your function currently accepts no keyword arguments,
+but you plan to add some later,
+do not define a keyword set,
+and use the
+.B KWPARSE_EMPTY
+macro described below.)
+.PP
+Each keyword set has a name,
+which is a C identifier.
+It's good to choose meaningful and distinctive names for keyword sets.
+Keyword set names are meaningful at runtime:
+they are used as part of the
+.B kw_unknown
+protocol (described below),
+and may be examined by handler functions,
+or reported to a user in error messages.
+For a keyword set which is used only by a single function,
+it is recommended that the set be given the same name as the function.
+.PP
+The keyword arguments for a keyword set named
+.I set
+are described by a `list macro' named
+.IB set _KWSET \fR.
+This macro takes a single argument,
+conventionally named
+.RB ` _ '.
+It should expand to a sequence of one or more list items of the form
+.IP
+.BI "_(" type ", " name ", " default ")"
+.PP
+with no separation between them.
+.PP
+For example:
+.IP
+.nf
+.ft B
+#define example_KWSET(_) \e
+.in +4m
+_(int, x, 0) \e
+_(const char *, y, NULL)
+.fi
+.ft P
+.PP
+Each
+.I name
+should be a distinct C identifier;
+they will be used to name structure members.
+An argument
+.I name
+should not end with the suffix
+.RB ` _suppliedp '
+(for reasons which will soon become apparent).
+.PP
+Each
+.I type
+should be a C
+.I type-name
+such that
+.IP
+.IB type " " name ;
+.PP
+is a valid declaration:
+so it may consist of declaration specifiers and
+(possibly qualified) pointer declarator markers,
+but not array or function markers
+(since they must be placed after the
+.IR name ).
+This is the same requirement made by the standard
+.BR va_arg (3)
+macro.
+.PP
+Each
+.I default
+should be an initializer expression
+or brace-enclosed list,
+suitable for use in an aggregate initializer
+for a variable with automatic storage duration.
+(In C89, aggregate initializers may contain only constant expressions;
+this restriction was lifted in C99.)
+.PP
+The macro
+.B KWTAIL
+is expected to be used at the end of function parameter type list
+to indicate that the function accepts keyword arguments;
+if there are preceding mandatory arguments
+then the
+.B KWTAIL
+marker should be separated from them with a comma
+.RB ` , '.
+(It is permitted for a function parameter type list to contain
+only a
+.B KWTAIL
+marker.)
+.PP
+Specifically,
+the macro declares a mandatory argument
+.B const char *kwfirst_
+(to collect the first keyword name),
+and a variable-length argument tail.
+.PP
+The macro
+.B KWPARSE
+(described below)
+assumes that the enclosing function's argument list ends with a
+.B KWTAIL
+marker.
+The marker should be included both in the function's definition and
+in any declarations, e.g., in the corresponding header file.
+.PP
+The
+.B KWCALL
+macro acts as a declaration specifier for
+functions which accept keyword arguments.
+Its effect is to arrange for the compiler to check,
+as far as is possible,
+that calls to the function are well-formed
+according to the keyword-argument rules.
+The exact checking performed depends on the compiler's abilities
+(and how well supported the compiler is):
+it may check that every other argument is a string;
+it may check that the list is terminated with a null pointer;
+it may not do anything at all.
+Again, this marker should be included in a function's definition and
+in any declarations.
+.PP
+The
+.B KWSET_STRUCT
+macro defines a
+.IR "keyword structure" .
+If
+.I set
+is a keyword-set name then
+.IP
+.BI "KWSET_STRUCT(" set ");"
+.PP
+declares a structure
+.B struct
+.IB set _kwargs \fR.
+For each argument defined in the keyword set,
+this structure contains two members:
+one has exactly the
+.I name
+and
+.I type
+listed in the keyword set definition;
+the other is a 1-bit-wide bitfield of type
+.B "unsigned int"
+named
+.IB name _suppliedp \fR.
+.PP
+The macro
+.B KWDECL
+declares and initializes a keyword argument structure variable.
+If
+.I set
+is a keyword-set name then
+.IP
+.I declaration-specifiers
+.BI "KWDECL(" set ", " kw ");"
+.PP
+declares a variable of type
+.B struct
+.IB set _kwargs
+named
+.IR kw .
+The optional
+.I declaration-specifiers
+may provide additional storage-class,
+qualifiers,
+or other declaration specifiers.
+The
+.RB ` _suppliedp '
+flags are initialized to zero;
+the other members are initialized with the corresponding defaults
+from the keyword-set definition.
+.PP
+The macro
+.B KWSET_PARSEFN
+defines a keyword argument
+.IR "parser function" .
+If
+.I set
+is a keyword-set name then
+.IP
+.I declaration-specifiers
+.BI "KWSET_PARSEFN(" set ")"
+.PP
+(no trailing semicolon!)
+defines a function
+.IP
+.B void
+.IB set _kwparse( \c
+.t(
+.BI "struct " set "_kwargs *" kw ","
+.br
+.BI "const char *" kwfirst ", va_list *" ap ","
+.br
+.BI "const struct kwval *" v ", size_t " n ");"
+.t)
+.PP
+The macro call can
+(and usually will)
+be preceded by storage class specifiers such as
+.BR static ,
+for example to adjust the linkage of the name.
+(I don't recommend declaring parser functions
+.BR inline :
+parser functions are somewhat large, and
+modern compilers are pretty good at
+figuring out whether to inline static functions.)
+.PP
+The function's behaviour is as follows.
+It parses keyword arguments from
+a variable-length argument tail, and/or
+a vector of
+.B kwval
+structures.
+When a keyword argument is recognized,
+for some keyword
+.IR name ,
+the keyword argument structure pointed to by
+.I kw
+is updated:
+the flag
+.IB name _suppliedp
+is set to 1;
+and the argument value is stored (by simple assignment) in the
+.I name
+member.
+Hence, if the
+.RB ` _suppliedp '
+members are initialized to zero,
+the caller can determine which keyword arguments were supplied.
+It is not possible to discover whether two or more arguments
+have the same keyword:
+in this case,
+the value from the last such argument is left
+in the keyword argument structure,
+and any values from earlier arguments are lost.
+(For this purpose,
+the argument vector
+.I v
+is scanned
+.I after
+the variable-length argument tail captured in
+.IR ap .)
+.PP
+The variable-argument tail is read from the list described by
+.BI * ap \fR.
+The argument tail is expected to consist of alternating
+keyword strings (as ordinary null-terminated strings)
+and the corresponding values,
+terminated by a null pointer of type
+.B "const char *"
+in place of a keyword;
+except that the first keyword
+(or terminating null pointer, if no arguments are provided)
+is expected to have been extracted already
+and provided as the
+.I kwfirst
+argument;
+the first argument retrieved using the
+.B va_list
+cursor object should then be the value
+corresponding to the keyword named by
+.IR kwfirst .
+(This slightly unusual convention makes it possible for a function to
+collect the first keyword as a separate mandatory argument,
+which is essential if there are no other mandatory arguments.
+It also means that the compiler will emit a diagnostic
+if you attempt to call a function which expects keyword arguments,
+but don't supply any and
+forget the null pointer which terminates the (empty) list.)
+If
+.I kwfirst
+is a null pointer,
+then
+.I ap
+need not be a valid pointer;
+otherwise, the cursor object
+.BI * ap
+will be modified as the function extracts
+successive arguments from the tail.
+.PP
+The keyword vector is read from the vector of
+.B kwval
+structures starting at address
+.I v
+and containing the following
+.I n
+items.
+If
+.I n
+is zero then
+.I v
+need not be a valid pointer.
+.PP
+The function also handles the special
+.B kw.valist
+and
+.B kw.tab
+arguments described above.
+If an unrecognized keyword argument is encountered,
+then
+.B kw_unknown
+is called:
+see below for details.
+.PP
+The
+.B KW_PARSE
+macro invokes a keyword argument parsing function.
+If
+.I set
+is a keyword-set name,
+.I kw
+names a keyword argument structure variable of type
+.B struct
+.IB set _kwargs \fR,
+and
+.I kwfirst
+is the name of the enclosing function's last mandatory argument,
+which must have type
+.BR "const char *" ,
+then
+.IP
+.BI "KW_PARSE(" set ", " kw ", " kwfirst ");"
+.PP
+calls the function
+.IB set _kwparse
+with five arguments:
+the address of the keyword argument structure
+.IR kw ;
+the string pointer
+.IR kwfirst ;
+the address of a temporary argument-tail cursor object of type
+.BR va_list ,
+constructed on the assumption that
+.I kwfirst
+is the enclosing function's final keyword argument;
+a null pointer; and
+the value zero (signifying an empty keyword-argument vector).
+If the variable
+.I kw
+was declared using
+.B KWDECL
+and the function
+.IB set _kwparse
+has been defined using
+.B KWSET_PARSEFN
+then the effect is to parse the keyword arguments passed to the function
+and set the members of
+.I kw
+appropriately.
+.PP
+The macro
+.B KWPARSE
+(note the lack of underscore)
+combines
+.B KWDECL
+and
+.BR KW_PARSE .
+If
+.I set
+is a keyword-set name then
+.IP
+.BI "KWPARSE(" set ");"
+.PP
+declares and initializes a keyword argument structure variable
+with the fixed name
+.BR kw ,
+and parses the keyword arguments provided to the enclosing function,
+storing the results in
+.BR kw .
+It assumes that the first keyword name
+is in an argument named
+.BR kwfirst_ ,
+as set up by
+.B KWTAIL marker described above.
+.PP
+The macro expands both to a variable declaration and a statement:
+in C89, declarations must precede statements,
+so under C89 rules this macro must appear exactly between
+the declarations at the head of a brace-enclosed block
+(typically the function body)
+and the statements at the end.
+This restriction was lifted in C99,
+so the macro may appear anywhere in the function body.
+However, it is recommended that callers avoid taking actions
+which might require cleanup
+before attempting to parse their keyword arguments,
+since keyword argument parsing functions invoke the
+.B kw_unknown
+handler if they encounter an unknown keyword,
+and the calling function will not get a chance
+to tidy up after itself if this happens.
+.PP
+As mentioned above,
+it is not permitted to define an empty keyword set.
+(Specifically, invoking
+.B KWSET_STRUCT
+for an empty keyword set would result in attempting to define
+a structure with no members, which C doesn't allow.)
+On the other hand, keyword arguments are a useful extension mechanism,
+and it's useful to be able to define a function which doesn't
+currently accept any keywords,
+but which might in the future be extended to allow keyword arguments.
+The macros
+.B KW_PARSE_EMPTY
+and
+.B KWPARSE_EMPTY
+are analogues of
+.B KW_PARSE
+and
+.B KWPARSE
+respectively,
+and handle this case.
+These macros take a keyword-set name as an argument,
+but this name is used only in diagnostic messages
+(e.g., if an unknown keyword name is encountered)
+and need not
+(and probably should not)
+correspond to a defined keyword set.
+.PP
+If
+.I set
+is an identifier then
+.IP
+.BI "KW_PARSE_EMPTY(" set ", " kwfirst ");"
+.PP
+calls the function
+.B kw_parseempty
+with five arguments:
+the
+.I set
+name, as a string;
+the string pointer
+.IR kwfirst ;
+the address of a temporary argument-tail cursor object of type
+.BR va_list ,
+constructed on the assumption that
+.I kwfirst
+is the enclosing function's final keyword argument;
+a null pointer; and
+the value zero (signifying an empty keyword-argument vector).
+The effect is to check that the argument tail contains
+no keyword arguments other than the special predefined ones.
+.PP
+If
+.I set
+is an identifier then
+.IP
+.B "KWPARSE_EMPTY(" set ");"
+.PP
+(note the lack of underscore)
+checks that the enclosing function has been passed
+no keyword arguments other than the special predefined ones.
+It assumes that the function's parameter type list ends with the
+.B KWTAIL
+marker described above.
+.PP
+The
+.B kw_parseempty
+function checks an keyword argument list
+to make sure that contains no keyword arguments
+(other than the special ones described above).
+.PP
+The
+.I set
+argument should point to a null-terminated string:
+this will be reported as the keyword set name to
+.BR kw_unknown ,
+though it need not
+(and likely will not)
+refer to any defined keyword set.
+The remaining arguments are as for
+the keyword parsing functions
+defined by the
+.B KWSET_PARSEFN
+macro.
+.
+.SS "Wrapper functions"
+Most users will not need the hairy machinery involving argument vectors.
+Their main use is in defining
+.IR "wrapper functions" .
+Suppose there is a function
+.I f
+which accepts some keyword arguments,
+and we want to write a function
+.I g
+which accepts the same keywords recognized by
+.I f
+and some additional ones.
+Unfortunately
+.I f
+may behave differently depending on whether or not
+a particular keyword argument is supplied at all, but
+it's not possible to synthesize a valid
+.B va_list
+other than by simply capturing a live argument tail,
+and it's not possible to decide at runtime
+whether or not to include some arguments in a function call.
+It's still possible to write
+.IR g ,
+by building a vector of keyword arguments,
+collected one-by-one depending on the corresponding
+.RB ` _suppliedp '
+flags (see below).
+A few macros are provided to make this task easier.
+.PP
+The macro
+.B KW_COUNT
+returns the number of keywords defined in a keyword set.
+If
+.I set
+is a keyword-set name, then
+.IP
+.BI "KW_COUNT(" set ")"
+.PP
+returns the number of keywords defined by
+.IR set ,
+as a constant expression of type
+.BR "unsigned int" .
+.PP
+The macro
+.B KW_COPY
+populates a vector of
+.B kwval
+structures from a keyword-argument structure.
+If
+.I fromset
+and
+.I toset
+are two keyword-set names then
+.IP
+.BI "KW_COPY(" fromset ", " toset ", " kw ", " v ", " n ");"
+.PP
+will populate the vector
+.IR v ,
+taking argument values from
+.IR kw .
+The
+.I toset
+must be a subset of
+.IR fromset :
+i.e., for every keyword defined in
+.I toset
+there is a keyword defined in
+.I fromset
+with the same name and type.
+The remaining arguments are as follows:
+.I kw
+is a pointer to a
+.BI "struct " fromset "_kwset"
+keyword-argument structure which has been filled in,
+e.g., by the keyword-argument parsing function
+.IB fromset _kwparse \fR;
+.I v
+is a pointer to a sufficiently large vector of
+.B "struct kwval"
+objects;
+and
+.I n
+is an lvalue designating an object of integer type.
+Successive elements of
+.IR v ,
+starting at index
+.IR n ,
+are filled in to refer to
+the keyword arguments defined in
+.I toset
+whose
+.RB ` _suppliedp '
+flag is set in the argument structure pointed to by
+.IR kw ;
+for each such argument,
+a pointer to the keyword name is stored in
+the corresponding vector element's
+.B kw
+member, and
+a pointer to the argument value,
+held in the keyword argument structure,
+is stored in the vector element's
+.B val
+member.
+At the end of this,
+the index
+.I n
+is advanced so as to contain the index of the first unused element of
+.IR v .
+Hence, at most
+.BI KW_COUNT( toset )
+elements of
+.I v
+will be used.
+.
+.SS Handling unknown-keyword errors
+When parsing a variable-length argument tail,
+it is not possible to continue after
+encountering an unknown keyword name.
+This is because it is necessary
+to know the (promoted) type of the following argument value
+in order to skip past it;
+but the only clue provided as to the type is the keyword name,
+which in this case is meaningless.
+.PP
+In this situation,
+the parser functions generated by
+.B KW_PARSEFN
+(and the
+.B kw_parseempty
+function)
+call
+.BR kw_unknown .
+This is a function of two arguments:
+.I set
+points to the name of the keyword set expected by the caller,
+as a null-terminated string; and
+.I kw
+is the unknown keyword which was encountered.
+All that
+.B kw_unknown
+does is invoke the function whose address is stored in
+the global variable
+.B kw_unkhook
+with the same arguments.
+The
+.B kw_unknown
+function never returns to its caller:
+if the
+.B kw_unkhook
+function returns (which it shouldn't)
+then
+.B kw_unknown
+writes a fatal error message to standard error
+and calls
+.BR abort (3).
+.PP
+By default
+.B kw_unkhook
+points to the function
+.BR kw_defunknown ,
+which just writes an error message
+quoting the keyword set name
+and offending keyword
+to standard error
+and calls
+.BR abort (3).
+.PP
+(In freestanding environments,
+the behaviour may be somewhat different:
+porting the library to such environments involves
+choosing appropriate behaviour for the target platform.)
+.PP
+As an example of the kind of special effect
+which can be achieved using this hook,
+the following hacking answers whether
+a function recognizes a particular keyword argument.
+.IP
+.nf
+.ft B
+#define KWARGS_TEST(k, val) KWARGS(K(k, val) K(kw.unknown, 0))
+
+static jmp_buf kw_test_jmp;
+
+static void kw_test_unknown(const char *set, const char *kw)
+{
+  if (strcmp(kw, "kw.unknown")) longjmp(kw_test_jmp, 1);
+  else longjmp(kw_test_jmp, 2);
+}
+
+#define KW_TEST(flag, set, call) do { \e
+  kw_unkhookfn *oldunk = kw_unkhook; \e
+  kw_unkhook = kw_test_unknown; \e
+  switch (setjmp(kw_test_jmp)) { \e
+    case 0: call; abort(); \e
+    case 1: flag = 1; break; \e
+    case 2: flag = 0; break; \e
+    default: abort(); \e
+  } \e
+  kw_unkhook = oldunk; \e
+} while (0)
+
+/* Example of use */
+int f;
+KW_TEST(f, somefunc(1, "two", 3, KWARGS_TEST("shiny", 68.7)));
+/* now f is nonzero if `somefunc' accepts the `shiny' keyword
+ * (which we hope wants a double argument)
+ */
+.ft P
+.fi
+.
+.\"--------------------------------------------------------------------------
+.SH BUGS
+.
+The unknown-keyword hook is inadequate for a modern library,
+but dealing with multiple threads isn't currently possible
+without writing (moderately complex) system-specific code.
+The author's intention is that the hook variable
+.B kw_unkhook
+be `owned' by some external library
+which can make its functionality available to client programs
+in a safer and more convenient way.
+On Unix-like platforms
+(including Cygwin)
+that library will be (a later version) of
+.BR mLib ;
+other platforms will likely need different arrangements.
+The author is willing to coordinate any such efforts.
+.PP
+The whole interface is rather clunky.
+Working with keyword-argument vectors is especially unpleasant.
+The remarkable thing is not that it's done well,
+but that it can be done at all.
+.
+.\"--------------------------------------------------------------------------
+.SH SEE ALSO
+.
+.BR va_start (3),
+.BR va_arg (3),
+.BR va_end (3).
+.
+.\"--------------------------------------------------------------------------
+.SH AUTHOR
+.
+Mark Wooding,
+<mdw@distorted.org.uk>
+.
+.\"----- That's all, folks --------------------------------------------------
diff --git a/lib/keyword.c b/lib/keyword.c
new file mode 100644
index 0000000..8ebf655
--- /dev/null
+++ b/lib/keyword.c
@@ -0,0 +1,110 @@
+/* -*-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.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "keyword.h"
+
+/*----- Global variables --------------------------------------------------*/
+
+kw_unkhookfn *kw_unkhook = kw_defunknown;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @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.
+ */
+
+void kw_unknown(const char *set, const char *kw)
+  { kw_unkhook(set, kw); kw__hookfailed(); }
+
+/* --- @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.)
+ */
+
+void kw_parseempty(const char *set, 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.va_list")) {
+      aap = va_arg(*ap, va_list *);
+      kk = va_arg(*aap, const char *);
+      kw_parseempty(set, kk, aap, 0, 0);
+    } else if (!strcmp(k, "kw.tab")) {
+      vv = va_arg(*ap, const struct kwval *);
+      nn = va_arg(*ap, size_t);
+      kw_parseempty(set, 0, 0, vv, nn);
+    } else
+      kw_unknown(set, k);
+  }
+  while (n) {
+    if (!strcmp(v->kw, "kw.va_list")) {
+      aap = *(va_list *const *)v->val;
+      kk = va_arg(*aap, const char *);
+      kw_parseempty(set, kk, aap, 0, 0);
+    } else if (!strcmp(k, "kw.tab")) {
+      t = (const struct kwtab *)v->val;
+      kw_parseempty(set, 0, 0, t->v, t->n);
+    } else
+      kw_unknown(set, k);
+    v++; n--;
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/lib/keyword.h b/lib/keyword.h
new file mode 100644
index 0000000..1fa3e56
--- /dev/null
+++ b/lib/keyword.h
@@ -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
diff --git a/lib/sod.h b/lib/sod.h
index 360afa8..2705dcc 100644
--- a/lib/sod.h
+++ b/lib/sod.h
@@ -94,6 +94,7 @@ SOD__VARARGS_MACROS_PREAMBLE
 #include <stdarg.h>
 #include <stddef.h>
 
+#include "keyword.h"
 #include "sod-base.h"
 
 /*----- Data structures ---------------------------------------------------*/
diff --git a/test/Makefile.am b/test/Makefile.am
index 08d6625..231d203 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -48,4 +48,12 @@ check_PROGRAMS		+= test
 nodist_test_SOURCES	 = test.c test.h
 BUILT_SOURCES		+= $(nodist_test_SOURCES)
 
+check_PROGRAMS		+= kwtest
+
+EXTRA_DIST		+= kwtest.ref
+CLEANFILES		+= kwtest.out
+check-local:: kwtest kwtest.ref
+	./kwtest >kwtest.out
+	diff -u $(srcdir)/kwtest.ref kwtest.out
+
 ###----- That's all, folks --------------------------------------------------
diff --git a/test/kwtest.c b/test/kwtest.c
new file mode 100644
index 0000000..b760ed4
--- /dev/null
+++ b/test/kwtest.c
@@ -0,0 +1,143 @@
+/* -*-c-*-
+ *
+ * Test program for the keyword-argument machinery
+ *
+ * (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 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "keyword.h"
+
+static void show_kwval(const char *what, int f, const char *fmt, ...)
+{
+  va_list ap;
+
+  printf("  %s = ", what);
+  if (!f)
+    puts("<unset>");
+  else {
+    va_start(ap, fmt);
+    vprintf(fmt, ap);
+    va_end(ap);
+    putchar('\n');
+  }
+}
+
+#define SHOW(mem, fmt) show_kwval(#mem, kw.mem##_suppliedp, fmt, kw.mem)
+
+#define t1_KWSET(_)							\
+  _(int, x, 0)								\
+  _(double, y, 69.0)							\
+  _(const char *, z, "default")
+KWSET_STRUCT(t1);
+static KWSET_PARSEFN(t1)
+static KWCALL void t1(const char *what, KWTAIL)
+{
+  KWPARSE(t1);
+
+  printf("t1: %s\n", what);
+  SHOW(x, "%d");
+  SHOW(y, "%g");
+  SHOW(z, "`%s'");
+}
+
+static KWCALL void t2(const char *what, ...)
+{
+  va_list ap;
+
+  printf("t2: %s\n", what);
+  va_start(ap, what);
+  t1("via t2", KWARGS(K_VALIST(ap)));
+  va_end(ap);
+}
+
+#define t3_KWSET(_)							\
+  _(int, q, 0)								\
+  t1_KWSET(_)
+KWSET_STRUCT(t3);
+static KWSET_PARSEFN(t3)
+static KWCALL void t3(const char *what, KWTAIL)
+{
+  struct kwval v[KW_COUNT(t1)];
+  size_t n = 0;
+  KWPARSE(t3);
+
+  printf("t3: %s\n", what);
+  SHOW(q, "%c");
+  SHOW(z, "`%s'");
+
+  KW_COPY(t3, t1, kw, v, n);
+  t1("via t3", KWARGS(K_TAB(v, n)
+		      K(x, kw.x_suppliedp ? kw.x + 1 : 42)));
+}
+
+/* The @KW_TEST@ machinery from the manpage... */
+#define KWARGS_TEST(k, val) KWARGS(K(k, val) K(kw.unknown, 0))
+
+static jmp_buf kw_test_jmp;
+
+static void kw_test_unknown(const char *set, const char *kw)
+{
+  if (strcmp(kw, "kw.unknown") == 0) longjmp(kw_test_jmp, 1);
+  else longjmp(kw_test_jmp, 2);
+}
+
+#define KW_TEST(flag, set, call) do {					\
+  kw_unkhookfn *oldunk = kw_unkhook;					\
+  kw_unkhook = kw_test_unknown;						\
+  switch (setjmp(kw_test_jmp)) {					\
+    case 0: call; abort();						\
+    case 1: flag = 1; break;						\
+    case 2: flag = 0; break;						\
+    default: abort();							\
+  }									\
+  kw_unkhook = oldunk;							\
+} while (0)
+/* END */
+
+int main(void)
+{
+  t1("no args", NO_KWARGS);
+  t1("some args", KWARGS(K(z, "set") K(x, 42)));
+
+  t2("indirection", KWARGS(K(y, 6.283)));
+
+  t3("no args", NO_KWARGS);
+  t3("x initially 19", KWARGS(K(q, 't') K(x, 19) K(z, "boing")));
+
+#define TEST_KWTEST(kw, val) do {					\
+  int f_;								\
+  KW_TEST(f_, t3, t3(0, KWARGS_TEST(kw, val)));				\
+  printf("t3 arg `%s' %s\n", #kw, f_ ? "present" : "absent");		\
+} while (0)
+  TEST_KWTEST(q, '!');
+  TEST_KWTEST(z, "splat");
+  TEST_KWTEST(nope, 0);
+
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/test/kwtest.ref b/test/kwtest.ref
new file mode 100644
index 0000000..8d55c85
--- /dev/null
+++ b/test/kwtest.ref
@@ -0,0 +1,30 @@
+t1: no args
+  x = <unset>
+  y = <unset>
+  z = <unset>
+t1: some args
+  x = 42
+  y = <unset>
+  z = `set'
+t2: indirection
+t1: via t2
+  x = <unset>
+  y = 6.283
+  z = <unset>
+t3: no args
+  q = <unset>
+  z = <unset>
+t1: via t3
+  x = 42
+  y = <unset>
+  z = <unset>
+t3: x initially 19
+  q = t
+  z = `boing'
+t1: via t3
+  x = 20
+  y = <unset>
+  z = `boing'
+t3 arg `q' present
+t3 arg `z' present
+t3 arg `nope' absent
-- 
2.11.0

