From 9e91c8e7b5fcdeb6389ac7ccbcd9c77348c4493a Mon Sep 17 00:00:00 2001 Message-Id: <9e91c8e7b5fcdeb6389ac7ccbcd9c77348c4493a.1716275596.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sun, 10 Jan 2016 13:51:04 +0000 Subject: [PATCH] lib/: Pure C machinery for handling `keyword arguments' to functions. Organization: Straylight/Edgeware From: Mark Wooding 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 @@ -431,6 +431,45 @@ nickname @|super|, then the macro @|MYCLASS__CONV_SUPER| converts a @|MyClass~*| to a @|SuperClass~*|. See \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 (@||) 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} diff --git a/doc/runtime.tex b/doc/runtime.tex index 8b8e080..f6f0846 100644 --- a/doc/runtime.tex +++ b/doc/runtime.tex @@ -29,6 +29,560 @@ This chapter describes the runtime support macros and functions provided by 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 +@|| 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(@)} + The @ 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 @ argument. +\end{describe} + +The following keyword-argument macros can be used within the @|KWARGS| +@ argument. + +\begin{describe}[K]{mac}{K(@, @)} + Passes a keyword @ and its corresponding @, as a pair of + arguments. The @ should be a single identifier (not a quoted + string). The @ may be any C expression of the appropriate type. +\end{describe} + +\begin{describe}[K_VALIST]{mac}{K_VALIST(@)} + Passes an indirect variable-length argument tail. The argument @ + should be an lvalue of type @|va_list|, which will be passed by reference. +\end{describe} + +\begin{describe}[K_TAB]{mac}{K_TAB(@, @)} + Passes a vector of keyword arguments. The argument @ should be the base + address of the vector, and @ 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 @ are described by a `list +macro' named @|@{}_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} + _(@, @, @) +\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 @ should be a distinct C identifier; they will be used to name +structure members. An argument @ should not end with the suffix +@`_suppliedp' (for reasons which will soon become apparent). + +Each @ should be a C @ such that +\begin{prog} + @ @ ; +\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 @). This is the +same requirement made by the standard \man{va_arg}{3} macro. + +Each @ 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(@);} + The @|KWSET_STRUCT| macro defines a \emph{keyword structure} named @|struct + @{}_kwargs|. For each argument defined in the keyword set, this + structure contains two members: one has exactly the @ and @ + listed in the keyword set definition; the other is a 1-bit-wide bitfield of + type @|unsigned int| named @|@{}_suppliedp|. +\end{describe} + +\begin{describe}[KWDECL]{mac} + {@ KWDECL(@, @);} + The macro declares and initializes a keyword argument structure variable + named @ for the named keyword @. The optional + @ 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} + {@ KWSET_PARSEFN(@)} + + The macro @|KWSET_PARSEFN| defines a keyword argument \emph{parser + function} + \begin{prog} + void @{}_kwparse(\=struct @{}_kwargs *@, + const char *@, va_list *@, \+ \\ + const struct kwval *@, size_t @); + \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 @, the + keyword argument structure pointed to by @ is updated: the flag + @|@{}_suppliedp| is set to 1; and the argument value is stored (by + simple assignment) in the @ 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 @ is scanned \emph{after} the + variable-length argument tail captured in @.) + + The variable-argument tail is read from the list described by @|* @|. + 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 + @ argument; the first argument retrieved using the @|va_list| + cursor object should then be the value corresponding to the keyword named + by @.\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 @ is a null pointer, then @ need not be a valid pointer; + otherwise, the cursor object @|* @| 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 @ and containing the following @ items. If @ is zero + then @ 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(@, @, @);} + The @|KW_PARSE| macro invokes a keyword argument parsing function. The + @ argument should name a keyword set; @ should be an lvalue of + type @|struct @{}_kwargs|; and @ should be the name of the + enclosing function's last mandatory argument, which must have type @|const + char~*|. + + It calls the function @|@{}_kwparse| with five arguments: the address + of the keyword argument structure @; the string pointer @; the + address of a temporary argument-tail cursor object of type @|va_list|, + constructed on the assumption that @ is the enclosing function's + final keyword argument; a null pointer; and the value zero (signifying an + empty keyword-argument vector). + + If the variable @ was declared using \descref{KWDECL}{mac} and the + function @|@{}_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 @ + appropriately. +\end{describe} + +\begin{describe}[KWPARSE]{mac}{KWPARSE(@);} + 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(@, @);} + 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 @ name, as a string; the string pointer @; the address of + a temporary argument-tail cursor object of type @|va_list|, constructed on + the assumption that @ 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(@);} + 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 *@, + const char *@, va_list *@, \+ \\ + const struct kwval *@, size_t @);} + 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 @ 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 @ which accepts some keyword arguments, and we want to write a +function @ which accepts the same keywords recognized by @ and some +additional ones. Unfortunately @ 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 +@, 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(@)} + Returns the number of keywords defined in a keyword set named @. +\end{describe} + +\begin{describe}[KW_COPY]{mac} + {KW_COPY(@, @, @, @, @);} + + The macro @|KW_COPY| populates a vector of @|kwval| structures from a + keyword-argument structure. + + The @ and @ arguments should be the names of keyword sets; + @ should be an lvalue of type @|@{}_kwargs|; @ should be + the base address of a sufficiently large vector of @|struct kwval| objects; + and @ should be an lvalue of some appropriate integer type. The + @ must be a subset of @: i.e., for every keyword defined in + @ there is a keyword defined in @ with the same name and + type. + + Successive elements of @, starting at index @, are filled in to refer + to the keyword arguments defined in @ whose @`_suppliedp' flag is + set in the argument structure pointed to by @; 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 @ is advanced so as to contain the index + of the first unused element of @. Hence, at most @|KW_COUNT(@)| + elements of @ 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 *@, const char *@);} + + This is a function of two arguments: @ points to the name of the + keyword set expected by the caller, as a null-terminated string; and @ + 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 *@, const char *@);} + + 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: @ is the name of the keyword set expected; + and @ 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 *@, const char *@);} + This function simply writes a message to standard error, to the effect that + the keyword named by @ is not known in the keyword 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} 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 +#include + +/*----- 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 +.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 ( ) +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, + +. +.\"----- 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 +#include +#include + +/*----- 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 #include +#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 +#include +#include +#include + +#include "keyword.h" + +static void show_kwval(const char *what, int f, const char *fmt, ...) +{ + va_list ap; + + printf(" %s = ", what); + if (!f) + puts(""); + 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 = + y = + z = +t1: some args + x = 42 + y = + z = `set' +t2: indirection +t1: via t2 + x = + y = 6.283 + z = +t3: no args + q = + z = +t1: via t3 + x = 42 + y = + z = +t3: x initially 19 + q = t + z = `boing' +t1: via t3 + x = 20 + y = + z = `boing' +t3 arg `q' present +t3 arg `z' present +t3 arg `nope' absent -- [mdw]