%%% ====================================================================
%%% @LaTeX-doc-source-file{
%%%   filename  = "rkeyval.dtx",
%%%   version   = "1.05",
%%%   date      = "2001/12/22",
%%%   time      = "15:27:19 PST",
%%%   author    = "Michael J Downes",
%%%   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  = "31052 512 1833 16121",
%%%   docstring = "The checksum field, produced by Robert Solovay's
%%%                checksum utility, gives CRC-16 checksum, lines,
%%%                words, and characters.",
%%%  }
%%% ====================================================================
% \iffalse
%<*driver>
\documentclass{amsdtx}
\pagestyle{myheadings}
\begin{document}
\title{The \pkg{rkeyval} package:\protect\linebreak[1]
  Syntactically restricted key-value scanning}
\author{Michael Downes\\American Mathematical Society}
\date{Version \fileversion, \filedate}
\hDocInput{rkeyval.dtx}
\end{document}
%</driver>
% \fi
%
% \maketitle
% \markboth{The \protect\pkg{rkeyval} package}
%          {Version \protect\fileversion, \protect\filedate}
% \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 \verb'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.
%
%    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{extra-setup-code}{group}{key={val}, key2={val2},...}
%\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.
%
%\begin{verbatim}
%\RestrictedDefineKey{group}{key}{default-value}
%\RestrictedDefineKey{group}{key}[\wrapper]{default-value}
%\end{verbatim}
%    The \cn{RestrictedDefineKey} command has three mandatory arguments
%    and one optional argument as illustrated above. Normal handling of
%    each key is
%\begin{verbatim}
%\def\group'key{val}
%\end{verbatim}
%    where \verb|\group'key| is constructed with \cs{csname}.
%
%    When a \cs{wrapper} function is specified, it means that this key
%    has a cumulative value: if the key is used more than once, the new
%    value is appended to the existing value instead of overwriting it,
%    with the wrapper function added to distinguish the separate values.
%    In other words, if a given key is used twice, the two values get
%    stored in this form:
%\begin{verbatim}
%\def\group'key{\wrapper{val1}\wrapper{val2}}
%\end{verbatim}
%
% \StopEventually{}
%
% \section{Implementation}
%    Standard declaration of package name and date.
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{rkeyval}[2001/12/22 v1.05]
%    \end{macrocode}
%
%    \begin{macrocode}
\let\@xp\expandafter \let\@nx\noexpand
\def\@append#1#2#3{\@xp\def\@xp#2\@xp{#2#1{#3}}}
%    \end{macrocode}
%
%    Ensure non-weird catcode for relevant characters.
%    \begin{macrocode}
\edef\@tempa{%
  \@nx\AtEndOfPackage{\catcode\number`\'=\number\catcode`\'\relax}%
}
\@tempa
\catcode`\'=12
%    \end{macrocode}
%
% \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{miller83}{article}{
%  author={Miller, G.},
%  title={Eine Bemerkung zur Darstellung ...},
%  journal={J. Math. Sent.},
%  year={1983},
%}
%\end{verbatim}
%    The scanned result is to assign
%\begin{verbatim}
%\global\rsk@toks{%
%  \@append\name\bib'author{Miller, G.}%
%  \DSK@def\bib'title{Eine Bemerkung zur Darstellung ...}%
%  \DSK@def\bib'journal{J. Math. Sent.}%
%  \DSK@def\bib'year{1983}%
%}
%\end{verbatim}
%    What happens thereafter with \cs{rsk@toks} depends on the code in
%    the last arg of \cs{RestrictedSetKeys}.
%
% \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.
%
%    FSA lookahead.
%    \begin{macrocode}
\def\fsa@l{\futurelet\@let@token\fsa@t}
%    \end{macrocode}
%
%    FSA bypass a token.
%    \begin{macrocode}
\def\fsa@b{\afterassignment\fsa@l \let\@let@token= }
%    \end{macrocode}
%
%    FSA copy a token (not space, bgroup, egroup).
%    We unroll \cs{fsa@l} here for a small speed gain.
%    \begin{macrocode}
\def\fsa@c#1{\aftergroup#1\futurelet\@let@token\fsa@t}
%    \end{macrocode}
%
%    FSA next action. This is just a placeholder definition.
%    \begin{macrocode}
\let\fsa@n\@empty
%    \end{macrocode}
%
%    FSA test. This is just a placeholder definition.
%    \begin{macrocode}
\let\fsa@t\@empty
%    \end{macrocode}
%
% \section{Now some of the real work}
%
%    Beginning here.
%    \begin{macrocode}
\def\rkvIfEmpty#1#2{%
  \@xp\ifx\csname#1'#2\endcsname\@empty \@xp\@firstoftwo
  \else\@xp\@secondoftwo
  \fi
}
%    \end{macrocode}
%
%    \begin{macrocode}
\def\rkv@DSAK#1#2{%
  \ifx\relax#1\global\let#1\@empty\fi
  \g@addto@macro#1{\let#2\@empty}%
  \def#2%
}
%    \end{macrocode}
%
%    This function is used for a dummy key whose normal value should be
%    effectively empty but apparently non-empty.
%    \begin{macrocode}
\def\rkv@DDK#1#2{%
  \ifx\relax#1\global\let#1\@empty\fi
  \g@addto@macro#1{\def#2{\@empty}}%
  \def#2%
}
%    \end{macrocode}
%
%    \begin{macrocode}
\newcommand{\DefineSimpleKey}[2]{%
  \@xp\rkv@DSAK\csname #1@reset\@xp\endcsname
    \csname #1'#2\endcsname{\DSK@def}%
}
\newcommand{\DefineSupersedingKey}[2]{%
  \@xp\rkv@DSAK\csname #1@reset\@xp\endcsname
    \csname #1'#2\endcsname{\def}%
}
\def\DSK@def#1{%
  \ifx#1\@empty\else
    \PackageWarningNoLine{rkeyval}%
      {Key \string#1 should not be repeated}%
  \fi
  \def#1%
}
\newcommand{\DefineAdditiveKey}[3]{%
  \@xp\rkv@DSAK\csname #1@reset\@xp\endcsname \csname #1'#2\endcsname
    {\@append#3}%
}
%    \end{macrocode}
%
%    \begin{macrocode}
\newcommand{\DefineDummyKey}[2]{%
  \@xp\rkv@DDK\csname #1@reset\@xp\endcsname \csname #1'#2\endcsname{\def}%
}
%    \end{macrocode}
%
%    I used to have the \cs{foo@reset} included in the contents of
%    \cs{rsk@toks}. But now I think that was a mistake, because it
%    interferes with combining xref data in the \pkg{amsrefs} package.
%    [mjd,2001-12-06]
%    \begin{macrocode}
\newcommand{\RestrictedSetKeys}[3]{
%  \global\rsk@toks\@xp{\csname #2@reset\endcsname}%
  \global\rsk@toks\@emptytoks
  \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}
%
%    \begin{macrocode}
\def\rsk@unknown@key#1{%
  \PackageWarning{rkeyval}{Unknown key: \string#1}%
  \def#1{\def}%
}
%    \end{macrocode}
%
%    \begin{macrocode}
\newtoks\rsk@toks
\def\rsk@set@a#1#2{%
  \ifx\relax#1\rsk@unknown@key#1\fi
  \toks@\@xp{#1#1{#2}}%
  \edef\@tempa{%
    \global\rsk@toks{\the\rsk@toks \the\toks@}%
  }%
  \@tempa
  \rsk@resume
}
%    \end{macrocode}
%
%\begin{verbatim}
% State 1: look for comma
%   space -> 1
%   {     -> error "Missing comma" a
%   }     -> 6
%   comma -> 2
%   other -> error "Missing comma" a
%
% State 2: skip commas and spaces while looking for a field name
%   space  -> 2
%   {      -> error "Missing key name" b
%   }      -> 6
%   comma  -> 2
%   letter -> 3
%   punct  -> 3
%   other  -> error "Invalid key name character" c
%
% State 3: scan field name
%   space  -> 4
%   {      -> error "Missing equal sign" d
%   }      -> error "Missing equal sign" d
%   letter -> 3
%   equal  -> 4
%   punct  -> 3
%   other  -> error "Invalid key name character" c
%
% State 4: scan equal signs and spaces
%   space  -> 4
%   {      -> 5
%   }      -> error "Missing { for value of current key" e
%   equal  -> 4
%   other  -> error "Missing { for value of current key" e
%
% State 5: read field value
%   any -> 1
%
%\end{verbatim}
%
%    State 0, scan past 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}
%
%    State 1, find comma
%    \begin{macrocode}
\def\rsk@a{%
  \ifx,\@let@token \endgroup \let\fsa@t\rsk@b \let\fsa@n\fsa@b
  \else\rsk@ab\fi
  \fsa@n
}
\def\rsk@ab{%
  \ifx\@sptoken\@let@token \let\fsa@n\fsa@b
  \else\ifcat\@nx\@let@token .\let\fsa@n\rsk@ac
  \else\ifx\bgroup\@let@token \endgroup
    \let\@let@token\bgroup \let\fsa@n\rsk@erra
  \else\ifx\egroup\@let@token \endgroup \let\fsa@n\rsk@end
  \else\ifx\par\@let@token \endgroup \let\fsa@n\rsk@erraa
%    \end{macrocode}
%    Rerun the futurelet so that the meaning of \cs{@let@token} will be
%    restored after the \cs{endgroup} clobbered it.
%    \begin{macrocode}
  \else \endgroup \let\fsa@n\rsk@erraa
  \fi\fi\fi\fi\fi
}
%    \end{macrocode}
%
%    Allow specifying a language with
%\begin{verbatim}
%  name={value}@{german},
%\end{verbatim}

%    \begin{macrocode}
\def\rsk@ac#1{\csname rsk@q#1\endcsname}
\@namedef{rsk@q@}#1{\typeout{Whoa, the language looks like #1!}%
  \let\fsa@n\rsk@a \fsa@l
}
%    \end{macrocode}
%
%    State 2, skip spaces/commas
%
%    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
}
\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}
%
%    State 3, accumulate key name characters
%    \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
}
\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}
%
%    \begin{macrocode}
\let\saw@equal=F
%    \end{macrocode}
%
%    State 4, skip spaces and equal signs. 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}
%
%    \begin{macrocode}
\def\rsk@casesensitive{%
  \let\rsk@changecase\@empty
  \let\rsk@endcase\@empty
}
\def\rsk@startlc{\aftergroup\lowercase\aftergroup{\iffalse}\fi}
\def\rsk@endlc{\iffalse{\fi\aftergroup}}
\def\rsk@lowercase{%
  \let\rsk@changecase\rsk@startlc
  \let\rsk@endcase\rsk@endlc
}
\rsk@lowercase
%    \end{macrocode}
%
%    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}
%
%    The aftergroup tokens end up looking like this:
%\begin{verbatim}
%\lowercase{\rsk@set abcdef}\endcsname
% --> \@xp\rsk@set@a\csname bib'abcdef\endcsname
% --> \rsk@set@a\bib'abcdef
%\end{verbatim}
%
%    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
}
\def\rsk@terminate{\@xp\@gobble\csname}
%    \end{macrocode}
%
%    \begin{macrocode}
\def\NoCommaWarning{\PackageWarning{rkeyval}{Missing comma}}%
\def\NoCommaError{\rsk@err{Missing comma}\@ehc}
\def\rsk@nocomma{\NoCommaWarning}
\def\rsk@err{\PackageError{rkeyval}}
\def\rsk@errf{\rsk@err{Missing open brace}\@ehc\rsk@b}
\def\rsk@erra{\rsk@nocomma\rsk@b}
\long\def\rsk@erraa#1{\rsk@nocomma \let\fsa@t\rsk@b \fsa@l}
\def\rsk@errb{\rsk@err{Missing key name}\@ehc\rsk@d}
\def\rsk@errc{\rsk@err{Invalid key name character}\@ehc\fsa@b}
\def\rsk@noequal{\rsk@err{Missing equal sign}\@ehc\rsk@d}
%    \end{macrocode}
%    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}
%
%    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{586}
% \Finale
