%%% ====================================================================
%%% @LaTeX-doc-source-file{
%%%   filename  = "rkeyval.dtx",
%%%   version   = "2.00",
%%%   date      = "2004/06/28",
%%%   time      = "15:19:29 EDT",
%%%   author    = "Michael J Downes and David M. Jones",
%%%   address   = "American Mathematical Society,
%%%                Publications Technical Group,
%%%                PO Box 6248,
%%%                Providence, RI 02940,
%%%                USA",
%%%   email     = "tech-support@ams.org",
%%%   URL       = "http://www.ams.org/",
%%%   abstract  = "Key-value scanning functions with more restrictive
%%%                syntax than the standard keyval package. This permits
%%%                some improvements in the error-checking.",
%%%   checksum  = "44721 1116 3178 30537",
%%%   docstring = "The checksum field, produced by Robert Solovay's
%%%                checksum utility, gives CRC-16 checksum, lines,
%%%                words, and characters.",
%%%  }
%%% ====================================================================
% \iffalse
%<*driver>
\documentclass[draft,oneside]{amsdtx}
\pagestyle{myheadings}

\makeatletter
\DeclareRobustCommand{\fld}{\@category@index{field}}
\makeatother

\DoNotIndex{\@emptytoks,\@temptokenb}

\CodelineIndex

\begin{document}
\title{The \pkg{rkeyval} package:\protect\linebreak[1]
  Syntactically restricted key-value scanning}
\author{Michael Downes and David M. Jones\\American Mathematical Society}
\date{Version \fileversion, \filedate}
\hDocInput{rkeyval.dtx}
\PrintIndex
\end{document}
%</driver>
% \fi
%
% \MakeShortVerb{\|}
%
% \maketitle
% \markboth{The \protect\pkg{rkeyval} package}
%          {Version \protect\fileversion, \protect\filedate}
%
%   \tableofcontents
%
% \section{Introduction}
%
%    The \pkg{rkeyval} package provides functions for scanning key-value
%    notation similar to the kind of scanning supported by the standard
%    \pkg{keyval} package. However, the syntax is more restrictive in
%    order to make some improved error-checking possible. In particular,
%    if a comma is omitted between two instances of |key={value}|
%    form, the \cn{RestrictedSetKeys} command will spot the missing
%    comma and issue a suitable error message (and it will be given at
%    the point where the missing comma is detected, before reading any
%    further in the \tex/ file). The standard \cn{setkeys} command, by
%    contrast, will append the second key name to the value of the first
%    key and discard the second value, without any notification to the
%    user that anything has gone wrong. But that is partly because the
%    standard \cn{setkeys} command allows implied values and does not
%    require braces around explicit values (except when necessary to
%    hide material that has a syntactic resemblance to a key-value
%    pair). With \cn{RestrictedSetKeys} the value must always be present
%    and it must be enclosed in braces.
%
%^^A Maybe commas should be optional.
%
%    Further restrictions of the \cn{RestrictedSetKeys} command and its
%    companion commands reduce memory consumption in certain ways.
%    Defining a key creates only one control sequence, a container for
%    holding the value. Processing of key values is normally limited to
%    storing a value given by the user; any additional processing must
%    be supplied separately by the programmer.
%
%    Generally speaking, the error-checking done by
%    \cn{RestrictedSetKeys} is better for applications where all the
%    keys are expected to have textual values, while \cn{setkeys} is
%    better when one wants to silently recover as far as possible from
%    syntactic errors, instead of notifying the user of the errors; or
%    when keys have nontrivial default values (i.e., not empty) or other
%    kinds of special processing.
%
%    \begin{verbatim}
%    \RestrictedSetKeys{setup-code}{group}{code}{key={val}, ...}
%\end{verbatim}
%    Normally \cn{RestrictedSetKeys} simply carries out the following
%    assignment for each key-value pair:
%    \begin{verbatim}
%    \def\group'key{val}
%\end{verbatim}
%    The first argument is normally empty, but the exact nature of the
%    warnings given and other aspects of the processing can be affected
%    by putting extra setup code there. The \pkg{amsrefs} package uses
%    this to implement a copying operation where field name and value
%    are written out immediately to another file instead of being stored
%    in the usual way.
%
%    Some examples for defining the key names associated with a given
%    group. This defines ``title'' as a recognized key for the \fn{bib}
%    group:
%    \begin{verbatim}
%    \DefineSimpleKey{bib}{title}
%\end{verbatim}
%    If a key is defined with \cs{DefineSimpleKey}, the result of using
%    the same key more than once in a single entry will be an error
%    message.
%
%    This defines ``title'' to be a repeatable key:
%    \begin{verbatim}
%    \DefineSupersedingKey{bib}{title}
%\end{verbatim}
%    If it occurs more than once, the last value supersedes the earlier
%    ones, instead of getting an error. This variation is not needed for
%    simple usage, but in more complicated situations where key values
%    are combined from multiple sources, it may be useful.
%
%    This defines ``author'' to be a repeatable key, with each value
%    being appended to a list of values:
%    \begin{verbatim}
%    \DefineAdditiveKey{bib}{author}{\name}
%\end{verbatim}
%    The third argument specifies a wrapper function that should be
%    applied to each item in the list. I.e., suppose that two author
%    names are given:
%    \begin{verbatim}
%    author={Smith, J. Q.},
%    author={Taylor, X. Y.},
%\end{verbatim}
%    Then they will be stored in the form
%    \begin{verbatim}
%    \name{Smith, J. Q.}\name{Taylor, X. Y.}
%\end{verbatim}
%
%    This defines ``transition'' to be a dummy key with a value that is
%    superficially nonempty but effectly empty:
%    \begin{verbatim}
%    \DefineDummyKey{bib}{transition}
%\end{verbatim}
%    Defining a dummy key like this can be useful in dealing with
%    certain boundary situations that sometimes arise.
%
% \StopEventually{}
%
% \section{Implementation}
%    Standard declaration of package name and date.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{rkeyval}[2004/05/05 v1.08]
%    \end{macrocode}
%
%    \begin{macro}{\@xp}
%    \begin{macro}{\@nx}
%    \begin{macrocode}
\let\@xp\expandafter
\let\@nx\noexpand
%    \end{macrocode}
%    \end{macro}
%    \end{macro}
%
%    \begin{macro}{\@gobblethree}
%    \begin{macro}{\@nilgobble}
%    Not in the \latex/ kernel yet.
%    \begin{macrocode}
\long\def\@gobblethree#1#2#3{}
\long\def\@nilgobble#1\@nil{}
%    \end{macrocode}
%    \end{macro}
%    \end{macro}
%
%    \begin{macro}{\@emptytoks}
%    Using \cs{@ifundefined} here avoids problems with really old
%    versions of \latex/ that choke on \cs{newtoks} if it is written
%    directly in the false branch of a conditional.
%    \begin{macrocode}
\@ifundefined{@emptytoks}{\csname newtoks\endcsname\@emptytoks}{}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\@temptokenb}
%    \begin{macrocode}
\@ifundefined{@temptokenb}{\csname newtoks\endcsname\@temptokenb}{}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\@append}
%    \begin{macrocode}
\def\@append#1#2#3{\@xp\def\@xp#2\@xp{#2#1{#3}}}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\star@}
%    Test for a trailing option marked by a star. Usage:
%    \begin{verbatim}
%    \newcommand{\blub}[1]{\star@{\blubaux{#1}}{default}}
%\end{verbatim}
%    Arg 1 of \cs{star@} is the code to
%    be run, arg 2 is the default value of the option (could be empty).
%    If arg 1 is \ncn{moo}, this test discards a star and expands to
%    \ncn{moo} if a star is found, or expands to |\moo{#2}| if
%    not. As the example shows, arg 1 need not be a single token.
%    \begin{macrocode}
\def\star@#1#2{%
    \def\star@a##1{#1}%
    \def\star@b{#1{#2}}%
    \futurelet\@let@token\star@test
}

\def\star@test{\ifx*\@let@token \let\star@b\star@a\fi \star@b}
%    \end{macrocode}
%
%    Please note: If there is a space before the star, then the star is
%    not treated as an option char.
%
%    Why use a star?  Since it's already part of standard \latex/
%    command syntax, it's unlikely to suffer from catcode changes.
%
%    Why not just put the star at the beginning in the usual way?  It
%    seemed to me that the lack of a trailing option
%    feature was a deficiency in current \latex/ and could be given an
%    experimental implementation in a package like this without any
%    adverse effect on existing documents.
%    \end{macro}
%
%    Ensure non-weird catcode for relevant characters.
%    \begin{macrocode}
\@ifundefined{NormalCatcodes}{\RequirePackage{pcatcode}\relax}{}
\PushCatcodes\NormalCatcodes
%    \end{macrocode}
%
%    \begin{macro}{\extract@group}
%    Extracts ``group'' from |\group'field|.
%    \begin{macrocode}
\def\extract@group#1{%
    \@xp\extract@group@a\string#1\@nil
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\extract@group@a}
%    \begin{macrocode}
\def\extract@group@a#1#2'{#2\@nilgobble}
%    \end{macrocode}
%    \end{macro}
%
% \section{Data structures}
%
%    The result of scanning the key/value pairs is an assignment statement
%    for \cs{rsk@toks}. For example, consider the entry
%    \begin{verbatim}
%    \bib{LelekZ1962}{article}{
%        author={Lelek, A.},
%        author={Zaremba, D.},
%        title={Dimensions of irreducible ...},
%        journal={Fund. Math.},
%        date={1962/63},
%    }
%\end{verbatim}
%    The scanned result is to assign
%    \begin{verbatim}
%    \global\rsk@toks{%
%        \set:bib'author{Lelek, A.}{}%
%        \set:bib'author{Zaremba, D.}{}%
%        \set:bib'title{Dimensions of irreducible ...}{}%
%        \set:bib'journal{Fund. Math.}{}%
%        \set:bib'date{1962/63}{}%
%    }
%\end{verbatim}
%    The extra empty arguments on each line are for auxiliary
%    properties (see below).
%    What happens thereafter with \cs{rsk@toks} depends on the code in
%    the last arg of \cs{RestrictedSetKeys}.
%
%    \section{Auxiliary properties}
%
%    Unfortunately, the previous section isn't the entire story. In
%    addition to the values of each field, we need to store a set of
%    auxiliary properties associated with those values.  Note that
%    properties are explicitly associated with \emph{values}, not with
%    keys, because each value of an additive key could have different
%    properties.
%
%    All such extra data will be stored in a special field named
%    ``aux'', with embedded tags to indicate which field each piece of
%    the \fld{aux} field is associated with. The extra bits can be
%    extracted on demand using standard techniques, and the primary
%    value of each field is not burdened with any attachments, so that
%    comparisons or scanning of the field contents can remain as
%    simple as possible.
%
%    Thus in practice there is at least one bit of auxiliary information
%    in every bib item, and our previous example would have the title
%    language indicated:
%    \begin{verbatim}
%    \DSK@def\bib'title{Eine Bemerkung zur Darstellung von Polynomen
%        \"{u}ber Verb\"{a}nden}%
%    \@append\bib'title\bib'aux{\selectlanguage{german}}%
%\end{verbatim}
%
%    \begin{macro}{\set@property}
%    \begin{macrocode}
\def\set@property#1{%
    \begingroup
        \edef\@tempa{\extract@group#1}%
        \edef\@tempa{%
            \@nx\@append\@nx#1\@xp\@nx\csname \@tempa,aux\endcsname
        }%
    \@xp\endgroup
    \@tempa
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\get@property}
%    \begin{macrocode}
%    \get@property\destination\bib'title
\def\get@property#1#2{%
    \get@nth@property#1#2\m@ne
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\get@nth@property}
%    \begin{macrocode}
%    \get@nth@property\destination\bib'title N
\def\get@nth@property#1#2#3{%
    \begingroup
        \edef\@tempa{\extract@group#2}%
        \@tempcnta#3\relax
        \@tempcntb\z@
        \@xp\scan@properties\@xp#2\csname \@tempa,aux\endcsname
        \edef\@tempa{\def\@nx#1{\@tempa}}%
    \@xp\endgroup
    \@tempa
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\scan@properties}
%    \begin{macrocode}
\def\scan@properties#1#2{%
    \begingroup
        \def\@tempa{#1}%
        \let\@tempc\@empty
        \@xp\find@property #2 \@nil\@nil
        \edef\@tempa{\def\@nx\@tempa{\@tempc}}%
    \@xp\endgroup
    \@tempa
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\find@property}
%    \begin{macrocode}
\def\find@property#1#2{%
    \ifx\@nil#1%
    \else
        \def\@tempb{#1}%
        \ifx\@tempa\@tempb
            \ifnum\@tempcnta<\z@
                \def\@tempc{#2}%
            \else
                \advance\@tempcntb\@ne
                \ifnum\@tempcntb=\@tempcnta
                    \def\@tempc{#2}%
                \fi
            \fi
        \fi
        \@xp\find@property
    \fi
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\reset@property}
%    \begin{macrocode}
\def\reset@property#1#2{%
    \reset@nth@property#1\m@ne{#2}%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\reset@nth@property}
%    \begin{macrocode}
%    \reset@nth@property\bib'title N VALUE
\def\reset@nth@property#1#2#3{%
    \begingroup
        \edef\@tempa{\extract@group#1}%
        \@tempcnta#2\relax
        \@temptokena{#3}%
        \toks@\@emptytoks
        \@tempcntb\z@
        \@xp\reset@scan\@xp#1\csname \@tempa,aux\endcsname
        \edef\@tempa{%
            \def\@xp\@nx\csname \@tempa,aux\endcsname{\the\toks@}%
        }%
    \@xp\endgroup
    \@tempa
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\reset@scan}
%    \begin{macrocode}
\def\reset@scan#1#2{%
    \begingroup
        \def\@tempa{#1}%
        \@xp\reset@scan@a #2 \@nil\@nil
        \edef\@tempa{\toks@{\the\toks@}}%
    \@xp\endgroup
    \@tempa
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\find@property}
%    \begin{macrocode}
\def\reset@scan@a#1#2{%
    \ifx\@nil#1%
    \else
        \def\@tempb{#1}%
        \ifx\@tempa\@tempb
            \ifnum\@tempcnta<\z@
                \@temptokenb\@temptokena
            \else
                \advance\@tempcntb\@ne
                \ifnum\@tempcntb=\@tempcnta
                    \@temptokenb\@temptokena
                \fi
            \fi
        \else
            \@temptokenb{#2}%
        \fi
        \edef\@tempb{%
            \toks@{\the\toks@ \@nx#1{\the\@temptokenb}}%
        }%
        \@tempb
        \@xp\reset@scan@a
    \fi
}
%    \end{macrocode}
%    \end{macro}
%
% \section{Some machinery for finite state automata}
%
%    Coincidentally I needed to write two finite state automaton parsers
%    for two related packages, so after writing them separately I spent
%    some time analyzing the code fragments they shared in common and
%    abstracted them so that the cs names could be shared.
%
%    \begin{macro}{\fsa@l}
%    FSA lookahead.
%    \begin{macrocode}
\def\fsa@l{\futurelet\@let@token\fsa@t}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\fsa@b}
%    FSA bypass a token.  Don't delete the space at the end!
%    \begin{macrocode}
\def\fsa@b{\afterassignment\fsa@l \let\@let@token= }
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\fsa@c}
%    FSA copy a token (not space, bgroup, egroup).
%    \begin{macrocode}
\def\fsa@c#1{\aftergroup#1\fsa@l}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\fsa@n}
%    FSA next action. This is just a placeholder definition.
%    \begin{macrocode}
\let\fsa@n\@empty
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\fsa@t}
%    FSA test. This is just a placeholder definition.
%    \begin{macrocode}
\let\fsa@t\@empty
%    \end{macrocode}
%    \end{macro}
%
% \section{Now some of the real work}
%
%    \begin{macro}{\rsk@toks}
%    \begin{macrocode}
\newtoks\rsk@toks
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rkvIfEmpty}
%    Beginning here.
%    \begin{macrocode}
\def\rkvIfEmpty#1#2{%
    \@xp\ifx\csname#1'#2\endcsname\@empty
        \@xp\@firstoftwo
    \else
        \@xp\@secondoftwo
    \fi
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rkvIfAdditive}
%    \begin{macrocode}
\def\rkvIfAdditive#1{%
    \@xp\let\@xp\@let@token \csname \rkv@setter#1\endcsname
    \afterassignment\@nilgobble
    \@xp\let\@xp\@let@token \@let@token \@empty\@empty\@nil
    \ifx\@let@token\DSK@append
        \@xp\@firstoftwo
    \else
        \@xp\@secondoftwo
    \fi
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rkv@setter}
%    It irritates me that I can't embed the \ncn{csname} and
%    \ncn{endcsname} in here.
%    \begin{macrocode}
\def\rkv@setter#1{set:\@xp\@gobble\string#1}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rkv@DSAK}
%    Define a simple, superseding, or additive key.
%    \begin{macrocode}
\def\rkv@DSAK#1#2{%
    \addto@group@reset#1{\let#1\@empty}%
    \edef\@tempa{\def\csname \rkv@setter#1\endcsname}%
    \@tempa{#2#1}%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rkv@DDK}
%    This function is used for a dummy key whose value (expansion)
%    should be empty but that should appear non-empty to
%    \cs{rkvIfEmpty}.
%    \begin{macrocode}
\def\rkv@DDK#1{%
    \addto@group@reset#1{\def#1{\@empty}}%
    \@xp\let\csname \rkv@setter#1\endcsname\@gobble
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\DSK@def}
%    \begin{macrocode}
\def\DSK@def#1{%
    \ifx#1\@empty\else
        \PackageWarningNoLine{rkeyval}%
            {Key \string#1 should not be repeated}%
    \fi
    \DSK@redef#1%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\DSK@redef}
%    We weed out empty field values for consistency with
%    \cs{DSK@append}.
%    \begin{macrocode}
\def\DSK@redef#1#2{%
    \@ifempty{#2}{\@gobble}{%
        \def#1{#2}%
        \set@property#1
    }%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\init@group@reset}
%    \begin{macrocode}
\def\init@group@reset#1{%
    \begingroup
        \edef\@tempb{\@xp\@nx\csname #1@reset\endcsname}%
        \@xp\ifx\@tempb\relax
            \@xp\xdef\@tempb{\let \csname #1,aux\endcsname\@nx\@empty}
        \fi
    \endgroup
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\addto@group@reset}
%    \begin{macrocode}
\def\addto@group@reset#1{%
    \begingroup
        \edef\@tempa{\extract@group#1}%
        \init@group@reset\@tempa
        \edef\@tempa{%
            \@nx\g@addto@macro\@xp\@nx\csname\@tempa @reset\endcsname
        }%
    \@xp\endgroup
    \@tempa
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\DefineSimpleKey}
%    \begin{macrocode}
\newcommand{\DefineSimpleKey}[2]{%
    \@xp\rkv@DSAK
        \csname #1'#2\endcsname
        {\DSK@def}%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\DefineSupersedingKey}
%    \begin{macrocode}
\newcommand{\DefineSupersedingKey}[2]{%
    \@xp\rkv@DSAK
        \csname #1'#2\endcsname
        {\DSK@redef}%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\DefineAdditiveKey}
%    \begin{macrocode}
\newcommand{\DefineAdditiveKey}[3]{%
    \@xp\rkv@DSAK
        \csname #1'#2\endcsname
        {\DSK@append#3}%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\DSK@append}
%    We weed out empty field values (e.g., |editor={}| or
%    \verb*|editor={ }|) because otherwise an additive field could
%    end up with a value like |\name{}| which appears non-empty to
%    \cs{rkvIfEmpty} but produces no output on the page.
%    \begin{macrocode}
\def\DSK@append#1#2#3{%
    \@ifempty{#3}{\@gobble}{%
        \@append#1#2{#3}%
        \set@property#2
    }%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\DefineDummyKey}
%    \begin{macrocode}
\newcommand{\DefineDummyKey}[2]{%
    \@xp\rkv@DDK \csname #1'#2\endcsname
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\RestrictedSetKeys}
%    \begin{macrocode}
\newcommand{\RestrictedSetKeys}[3]{%
    \global\rsk@toks\@xp{\csname #2@reset\endcsname}%
    \def\rsk@finish{#3}%
    \gdef\rsk@set{\@xp\rsk@set@a\csname#2'}%
    #1\relax
    \begingroup
        \rsk@changecase
        \aftergroup\rsk@set
%    \end{macrocode}
%    Start by removing the opening brace.
%    \begin{macrocode}
        \let\fsa@t\rsk@z
        \fsa@l
}
%    \end{macrocode}
%    \end{macro}
%
%    The aftergroup tokens end up looking like this:
%    \begin{verbatim}
%    \lowercase{\rsk@set FIELDNAME\endcsname}
%     --> \@xp\rsk@set@a\csname bib'fieldname\endcsname
%     --> \rsk@set@a\bib'abcdef
%\end{verbatim}
%
%    \begin{macro}{\rsk@unknown@key}
%    \begin{macrocode}
\def\rsk@unknown@key#1{%
    \PackageWarning{rkeyval}{Unknown key: \string#1}%
    \@xp\def\csname\rkv@setter#1\endcsname {\DSK@redef#1}%
}
%    \end{macrocode}
%    \end{macro}
%
% \section{The state machine}
%
%\begin{verbatim}
% State 0: Skip opening brace (\rsk@z).
%   space -> 0
%   {     -> 2
%   other -> error "Missing open brace"
%
% State 1: Skip comma (\rsk@a).
%   space -> 1
%   \par  -> 1
%   comma -> 2
%   @     -> read optional arg; 1
%   }     -> 6
%   other -> error "Missing comma"; 2
%
% State 2: Find field name (\rsk@b).
%   space  -> 2
%   \par   -> 2
%   comma  -> 2
%   letter -> 3
%   {      -> error "Missing key name"; 4
%   }      -> 6
%   other  -> error "Invalid key name character"; 2
%
% State 3: Scan field name (\rsk@c).
%   letter -> 3
%   comma  -> error "Invalid key name character"; 3
%   equal  -> 4
%   other punct  -> 3
%   space  -> 4
%   {      -> error "Missing equal sign"; 4
%   }      -> error "Missing equal sign"; 4
%   other  -> error "Invalid key name character"; 3
%
% State 4: Skip equals (\rsk@d).
%   space  -> 4
%   equal  -> 4
%   {      -> 5
%   other  -> error "Missing { for value of current key"; 5
%
% State 5: Read field value (\rsk@set@a).
%   any -> 1
%
% State 6: Done (\rsk@end).
%\end{verbatim}
%
%    \begin{macro}{\rsk@z}
%    State 0: Skip opening brace.
%    \begin{macrocode}
\def\rsk@z{%
    \ifx\bgroup\@let@token
        \let\fsa@t\rsk@b
        \let\fsa@n\fsa@b
    \else
        \ifx\@sptoken\@let@token
            \let\fsa@n\fsa@b
        \else
            \rsk@errf
        \fi
    \fi
    \fsa@n
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@a}
%    State 1: Skip comma.
%    \begin{macrocode}
\def\rsk@a{%
    \ifx\@let@token\@sptoken
        \let\fsa@n\fsa@b
    \else
        \ifx\@let@token\par
            \let\fsa@n\fsa@b
        \else
            \ifx,\@let@token
                \endgroup
                \let\fsa@t\rsk@b
                \let\fsa@n\fsa@b
            \else
                \ifx\egroup\@let@token
                    \endgroup
                    \let\fsa@n\rsk@end
                \else
                    \endgroup
                    \let\fsa@n\rsk@erraa
                \fi
            \fi
        \fi
    \fi
    \fsa@n
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@b}
%    State 2: Find field name.
%
%    Allow \cs{par} here to permit a blank line after
%    the end of one key-val pair and the start of the next (perhaps to
%    break up a long list into sections).
%    \begin{macrocode}
\def\rsk@b{%
    \ifcat\@nx\@let@token A%
        \let\fsa@t\rsk@c
        \let\fsa@n\fsa@c
    \else
        \ifx\@sptoken\@let@token
            \let\fsa@n\fsa@b
        \else
            \rsk@bb
        \fi
    \fi
    \fsa@n
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@bb}
%    \begin{macrocode}
\def\rsk@bb{%
    \ifx,\@let@token
        \let\fsa@n\fsa@b
    \else
        \ifx\bgroup\@let@token
            \let\fsa@n\rsk@errb
        \else
            \ifx\egroup\@let@token
                \let\fsa@n\rsk@end
            \else
                \ifx\par\@let@token
                    \let\fsa@n\fsa@b
                \else
                    \let\fsa@n\rsk@errc
                \fi
            \fi
        \fi
    \fi
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@c}
%    State 3: Scan field name.
%    \begin{macrocode}
\def\rsk@c{%
    \ifcat\@nx\@let@token A%
        \let\fsa@n\fsa@c
    \else
        \ifx\@sptoken\@let@token
            \let\fsa@t\rsk@d
            \let\fsa@n\fsa@b
        \else
            \ifx=\@let@token
                \let\saw@equal T%
                \let\fsa@t\rsk@d
                \let\fsa@n\fsa@b
            \else
                \rsk@cb
            \fi
        \fi
    \fi
    \fsa@n
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@cb}
%    \begin{macrocode}
\def\rsk@cb{%
    \ifx,\@let@token
        \let\fsa@n\rsk@errc
    \else
        \ifcat\@nx\@let@token .%
            \let\fsa@n\fsa@c
        \else
            \ifx\bgroup\@let@token
                \let\fsa@n\rsk@noequal
            \else
                \ifx\egroup\@let@token
                    \let\fsa@n\rsk@noequal
                \else
                    \let\fsa@n\rsk@errc
                \fi
            \fi
        \fi
    \fi
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\saw@equal}
%    \begin{macrocode}
\let\saw@equal=F
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@d}
%    State 4: Skip equals.
%
%    If no equal sign ever came
%    along, then give a warning about it and set \cs{saw@equal} to true
%    so that when \cs{rsk@noequal} cycles through again it will take the
%    other branch.
%    \begin{macrocode}
\def\rsk@d{%
    \ifx\bgroup\@let@token
        \ifx\saw@equal T%
            \aftergroup\endcsname
            \rsk@endcase
            \let\fsa@n\endgroup
        \else
            \let\saw@equal T%
            \let\fsa@n\rsk@noequal
        \fi
    \else
        \ifx\@sptoken\@let@token
            \let\fsa@n\fsa@b
        \else
            \ifx=\@let@token
                \let\saw@equal T%
                \let\fsa@n\fsa@b
            \else
                \let\fsa@n\rsk@erre
            \fi
        \fi
    \fi
    \fsa@n
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@casesensitive}
%    \begin{macrocode}
\def\rsk@casesensitive{%
    \let\rsk@changecase\@empty
    \let\rsk@endcase\@empty
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@startlc}
%    \begin{macrocode}
\def\rsk@startlc{\aftergroup\lowercase\aftergroup{\iffalse}\fi}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@endlc}
%    \begin{macrocode}
\def\rsk@endlc{\iffalse{\fi\aftergroup}}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@lowercase}
%    \begin{macrocode}
\def\rsk@lowercase{%
    \let\rsk@changecase\rsk@startlc
    \let\rsk@endcase\rsk@endlc
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macrocode}
\rsk@lowercase
%    \end{macrocode}
%
%    \begin{macro}{\rsk@resume}
%    Here we get improved reporting of error context by changing
%    end-of-line to be different from normal space. If we don't find a
%    comma on the current line, assume it is an error.
%    \begin{macrocode}
\def\rsk@resume{%
    \begingroup
        \rsk@changecase
        \aftergroup\rsk@set
        \let\fsa@t\rsk@a
        \begingroup
            \catcode\endlinechar=\active
            \lccode`\~=\endlinechar
            \lowercase{\let~\par}%
            \fsa@l
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@set@a}
%    State 5: Read field value.
%    \begin{macrocode}
\def\rsk@set@a#1#2{%
    \star@{\rsk@set@b#1{#2}}{}%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@set@b}
%    \begin{macrocode}
\def\rsk@set@b#1#2#3{%
    \@xp\ifx \csname\rkv@setter#1\endcsname \relax
        \rsk@unknown@key#1%
    \fi
    \edef\@tempa{\@xp\@nx\csname \rkv@setter#1\endcsname}%
    \toks@\@xp{\@tempa{#2}{#3}}%
    \edef\@tempa{%
        \global\rsk@toks{\the\rsk@toks \the\toks@}%
    }%
    \@tempa
    \rsk@resume
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@end}
%    State 6: Done.
%
%    Lets see, now why did I add this?
%    \begin{macrocode}
\def\rsk@end{%
        \global\let\rsk@set\rsk@terminate
        \rsk@endcase
    \endgroup
    \endcsname
    \afterassignment\rsk@finish
    \toks@\bgroup
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@terminate}
%    \begin{macrocode}
\def\rsk@terminate{\@xp\@gobble\csname}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\NoCommaWarning}
%    \begin{macrocode}
\def\NoCommaWarning{\PackageWarning{rkeyval}{Missing comma}}%
%    \end{macrocode}
%    \end{macro}
%
%%  %    \begin{macro}{\NoCommaError}
%%  %    \begin{macrocode}
%%  \def\NoCommaError{\rsk@err{Missing comma}\@ehc}
%%  %    \end{macrocode}
%%  %    \end{macro}
%
%    \begin{macro}{\rsk@nocomma}
%    \begin{macrocode}
\def\rsk@nocomma{\NoCommaWarning}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@err}
%    \begin{macrocode}
\def\rsk@err{\PackageError{rkeyval}}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@errf}
%    \begin{macrocode}
\def\rsk@errf{\rsk@err{Missing open brace}\@ehc\rsk@b}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@erraa}
%    \begin{macrocode}
\long\def\rsk@erraa{\rsk@nocomma \let\fsa@t\rsk@b \fsa@l}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@errb}
%    \begin{macrocode}
\def\rsk@errb{\rsk@err{Missing key name}\@ehc\rsk@d}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@errc}
%    \begin{macrocode}
\def\rsk@errc{\rsk@err{Invalid key name character}\@ehc\fsa@b}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macro}{\rsk@noequal}
%    \begin{macrocode}
\def\rsk@noequal{\rsk@err{Missing equal sign}\@ehc\rsk@d}
%    \end{macrocode}
%    \end{macro}
%    \begin{macro}{\rsk@erre}
%    In this case we guess that the value is without braces but probably
%    terminated with a comma. We want to scan up to the comma in order
%    to get back in synch.
%    \begin{macrocode}
\def\rsk@erre#1,{%
        \rsk@err{Missing open brace for key value}\@ehc
        \iffalse{\fi
    \endgroup
    \endcsname
    \rsk@endcase }{#1},%
}
%    \end{macrocode}
%    \end{macro}
%
%    \begin{macrocode}
\PopCatcodes
%    \end{macrocode}
%
%    The usual \cs{endinput} to ensure that random garbage at the end of
%    the file doesn't get copied by \fn{docstrip}.
%    \begin{macrocode}
\endinput
%    \end{macrocode}
%
% \CheckSum{871}
% \Finale
