% \begin{meta-comment}
%
% $Id: mdwkey.dtx,v 1.1 2003/09/05 16:09:56 mdw Exp $
%
% Parsing key/value pairs
%
% (c) 2003 Mark Wooding
%
%----- Revision history -----------------------------------------------------
%
% $Log: mdwkey.dtx,v $
% Revision 1.1  2003/09/05 16:09:56  mdw
% New feature\!
%
%
% \end{meta-comment}
%
% \begin{meta-comment} <general public licence>
%%
%% mdwkey package -- yet another key/value parser
%% Copyright (c) 2003 Mark Wooding
%<*package>
%%
%% This program 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.
%%
%% This program 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 this program; if not, write to the Free Software
%% Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
%</package>
%%
% \end{meta-comment}
%
% \begin{meta-comment} <Package preamble>
%<+package&!plain>\NeedsTeXFormat{LaTeX2e}
%<+package&!plain>\ProvidesPackage{mdwkey}
%<+package&!plain>                [2003/08/21 1.0 key/value parser]
% \end{meta-comment}
%
% \CheckSum{316}
%\iffalse
%<*package>
%\fi
%% \CharacterTable
%%  {Upper-case    \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
%%   Lower-case    \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
%%   Digits        \0\1\2\3\4\5\6\7\8\9
%%   Exclamation   \!     Double quote  \"     Hash (number) \#
%%   Dollar        \$     Percent       \%     Ampersand     \&
%%   Acute accent  \'     Left paren    \(     Right paren   \)
%%   Asterisk      \*     Plus          \+     Comma         \,
%%   Minus         \-     Point         \.     Solidus       \/
%%   Colon         \:     Semicolon     \;     Less than     \<
%%   Equals        \=     Greater than  \>     Question mark \?
%%   Commercial at \@     Left bracket  \[     Backslash     \\
%%   Right bracket \]     Circumflex    \^     Underscore    \_
%%   Grave accent  \`     Left brace    \{     Vertical bar  \|
%%   Right brace   \}     Tilde         \~}
%%
%\iffalse
%</package>
%\fi
%
% \begin{meta-comment}
%
%<*driver>
\input{mdwtools}
\describespackage{mdwkey}
\mdwdoc
%</driver>
%
% \end{meta-comment}
%
%^^A-------------------------------------------------------------------------
% \section{User's guide}
%
% This is a key/value-pair parser, rather like the one in David Carlisle's
% \package{keyval} package but a little more powerful.  There's no problem
% with having both in the same program.
%
% \subsection{Terminology}
%
% A \emph{key-value pair} is a pair \syntax{<key> `=' <value>}, where the
% \lit{=} appears at the topmost bracing level.  A \emph{tag} is just a
% single \syntax{<key>}.  A \emph{list} is a sequence of key-value pairs and
% tags separated by commas \lit{,} at the topmost bracing level.  A \<key> or
% \<value> has a leading and/or trailing space removed, if there are any, and
% if the whole thing is enclosed in braces, then the braces are removed.
% Examples:
% \begin{itemize} \synshorts
% \item "foo = bar" is a key-value pair.  The key is `foo' and the value is
%   `bar'. 
% \item `{foo = bar}' is a tag.  The key is `foo = bar'.
% \item `foo = { bar }' is a key-value pair.  The key is `foo' and
%   the value is ` bar ' (with the leading and trailing spaces).
% \item `foo = { bar }x' is a key-value pair.  The key is `foo' and
%   the value is `{ bar }x'.
% \item `one, two' is a list of two tags, `one' and `two'.
% \item `one, {two, three}' is a list of two tags, `one' and `two, three'.
% \end{itemize}
% There is no way to get an unmatched brace into a \<key> or \<value> without
% stupid catcode tricks.
%
% \subsection{Using the system}
%
% \DescribeMacro\mkdef
% New key names, and what to do when they're encountered, is all defined
% using |\mkdef|.  Keys are gathered into \emph{groups}, so that lots of
% people can use the system without treading on their toes.  I recommend that
% people use \syntax{<package-name>":"<label>} for their group names.
%
% There's a lot which can be done using |\mkdef|.
% \begin{itemize} \synshorts
% \item "\\mkdef{"<group>"}{"<key>"}{"<stuff>"}" will perform <stuff> when
%   <key> is given a value: the value is available as "#1" in <stuff>.
% \item "\\mkdef{"<group>"}{"<key>"}["<default>"]{"<stuff>"}" is the same,
%   but additionally if <key> is found as a tag, then treat it as if we found
%   <key>"={"<default>"}" instead. 
% \item "\\mkdef{"<group>"}{"<key>"}*{"<stuff>"}" will perform <stuff> when
%   <key> is found as a tag. 
% \item "\\mkdef*{"<group>"}{"<stuff>"}" will perform <stuff> when an unknown
%   key (one for which there is no specific definition in this group) is
%   given a value: the key is available as "#1" and the value as "#2" in
%   <stuff>. 
% \item "\\mkdef*{"<group>"}["<default>"]{"<stuff>"}" is the same, but
%   additionally if an unknown key is found as a tag then treat it as if 
%   it had been assigned the value <default> instead.
% \item "\\mkdef*{"<group>"}*{"<stuff>"}" will perform <stuff> when  an
%   unknown key is found as a tag: the key is available as "#1" in <stuff>.
% \end{itemize}
%
% \DescribeMacro\mkparse
% All that remains now is to learn how to use the thing.  Once you have a
% list, you can say \syntax{"\\mkparse{"<group>"}{"<list>"}"} to perform all
% the appropriate actions.  (This will mess up |\toks0| and |\next@| and some
% other standard scratch macros.)
%
%^^A-------------------------------------------------------------------------
% \implementation
% \section{Implementation}
%
%    \begin{macrocode}
%<*package|macro>
%    \end{macrocode}
%
% \subsection{Provide bits of \LaTeX\ for plain \TeX}
%
% This lot is the infrastructure needed to make the macros work under Plain
% \TeX.
%
%    \begin{macrocode}
%<*plain>
\edef\done{\catcode`\noexpand\@=\the\catcode`\@}
\catcode`\@=11
\def\@gobble#1{}
\def\@firstoftwo#1#2{#1}
\def\@firstofthree#1#2#3{#1}
\def\@secondoftwo#1#2{#2}
\def\@ifnextchar#1#2#3{%
  \def\next@{%
    \ifx\char@#1\expandafter\@firstoftwo%
    \else\expandafter\@secondoftwo\fi{#2}{#3}%
  }%
  \@ifn@i%
}
\def\@ifn@i{\futurelet\char@\@ifn@ii}
\def\@ifn@ii{%
  \ifx\char@\@sptoken\expandafter\@ifn@i\else%
  \expandafter\next@\fi%
}
\def\@ifstar#1#2{%
  \def\next@{%
    \ifx\char@*\expandafter\@firstofthree%
    \else\expandafter\@secondoftwo\fi{#1}{#2}%
  }%
  \futurelet\char@\next@%
}
\def\@namedef#1{\expandafter\def\csname#1\endcsname}
\def\PackageError#1#2#3{\errhelp{#3}\errmessage{#1 error: #2}\errhelp{}}
%</plain>
%    \end{macrocode}
%
% \subsection{Removing spaces}
%
% \begin{macro}{\withoutspaces}
%
% Saying \syntax{"\\withoutspaces{"<macro>"}{"<stuff>"}"} calls \<macro>,
% passing it the argument which is \<stuff>, shorn of (a) a single leading
% and/or space token, and (b) a single layer of |{|\ldots|}| grouping, if
% present.  This improves over \package{keyval}'s attempt by being a little
% simpler and only stripping off one layer of braces.
%
%    \begin{macrocode}
\def\q@delim{\q@delim}
\def\next@#1{%
\let\@sptoken=#1
\def\withoutspaces##1##2{%
  \def\next@{##1}\futurelet\char@\wsp@i##2%
  \q@delim#1\q@delim\q@delim\relax%
}
\def\wsp@i{%
  \ifx\char@\@sptoken\expandafter\wsp@ii%
  \else\expandafter\wsp@iii\fi%
}
\def\wsp@ii#1{\wsp@iii}
\def\wsp@iii##1#1\q@delim##2\relax{\wsp@iv##1\q@delim\relax}
\def\wsp@iv##1\q@delim##2\relax{\next@{##1}}
}\next@{ }
%    \end{macrocode}
%
% \end{macro}
%
% \begin{macro}{\withoutspacesdef}
%
% As a trivial but useful application of the above,
% \syntax{"\\withoutspacesdef{"<name>"}{"<stuff>"}"} defines \<name> as a
% macro containing \<stuff> with a leading and trailing space deleted and a
% level of bracing removed.
%
%    \begin{macrocode}
\def\withoutspacesdef#1#2{\withspaces\toks@{#2}\edef#1{\the\toks@}}
%    \end{macrocode}
%
% \end{macro}
%
% \subsection{Parsing key/value lists}
%
% \begin{macro}{\mkparse}
%
% The main parser macro.  Stash some information away and then start on the
% main loop.
%
%    \begin{macrocode}
\def\mkparse#1#2{%
  \def\mk@group{#1}%
  \def\mk@{mk$#1$}%
  \mk@loop!#2,\q@delim,\relax%
}
%    \end{macrocode}
%
% And already the subtlety begins.  Note that there's a leading \lit{!} at
% the front of the token list.  This prevents our delimited argument from
% being entirely brace-enclosed, which in turn stops \TeX\ from removing it
% until we're good and ready.
%
% This doesn't trap empty items -- that happens later.
%
%    \begin{macrocode}
\def\mk@loop#1,{%
  \expandafter\def\expandafter\next@\expandafter{\@gobble#1}%
  \ifx\next@\q@delim\expandafter\mk@x%
  \else\mk@i#1=\q@delim\expandafter\mk@loop\expandafter!\fi%
}
\def\mk@x#1\relax{\relax}
%    \end{macrocode}
%
% Now we have to split an entry into a key and a value.  If we have
% \<key>|=|\<value> then |#1| = |!|\<key> and |#2| = \<value>|=|; if we have
% only \<key>, then |#1| = |!|\<key> as before, and |#2| is empty.  The first
% thing to do is strip the |!| and spaces from |#1|.  If |#2| is empty then
% we're done with this stage and can just call |\mk@k| with what we've got;
% otherwise we swap the trailing |=| on |#2| for a leading |!| and strip that
% off, and then call |\mk@kv| with the answer.
%
%    \begin{macrocode}
\def\mk@i#1=#2\q@delim{%
  \expandafter\withoutspaces\expandafter%
    \mk@ii\expandafter{\@gobble#1}{#2}%
}
\def\mk@ii#1#2{%
  \ifx\q@delim#2\q@delim\mk@k{#1}%
  \else\mk@iii{#1}!#2\q@delim\fi%
}
\def\mk@iii#1#2=\q@delim{%
  \expandafter\withoutspaces\expandafter%
    \mk@iv\expandafter{\@gobble#2}{#1}%
}
\def\mk@iv#1#2{\mk@kv{#2}{#1}}
%    \end{macrocode}
%
% We just have \<key>, shorn of spaces and outer braces.  If it's empty then
% the whole entry was empty and we should ignore it.  Otherwise, if there's a
% defined command for handling the token then we use that; if not, then we
% look for a general unknown-key command.  If nothing works, we raise an
% error.
%
%    \begin{macrocode}
\def\mk@k#1{%
  \ifx\q@delim#1\q@delim\else%
    \expandafter\let\expandafter\next@\csname\mk@!#1\endcsname%
    \ifx\next@\relax%
      \expandafter\let\expandafter\next@\csname\mk@*!\endcsname%
      \ifx\next@\relax\mk@err{#1}%
      \else\next@{#1}\fi%
    \else\next@\fi%
  \fi%
  }
%    \end{macrocode}
%
% We have a \<key> and a \<value>, both stripped of spaces and braces.  If
% there's a command for this key, then give it the value; otherwise look for
% a general unknown-key-with-value command.  If nothing works, raise an
% error.
%
%    \begin{macrocode}
\def\mk@kv#1#2{%
  \expandafter\let\expandafter\next@\csname\mk@=#1\endcsname%
  \ifx\next@\relax%
    \expandafter\let\expandafter\next@\csname\mk@*=\endcsname%
    \ifx\next@\relax\mk@err{#1}%
    \else\next@{#1}{#2}\fi%
  \else\next@{#2}\fi%
}
%    \end{macrocode}
%
% How to raise an error.  Not so difficult.
%
%    \begin{macrocode}
\def\mk@err#1{%
  \PackageError{mdwkey}{Key `#1' not found in group `\mk@group'}{%
    I've never heard of the key you tried to set.  I'm going to ignore it.
  }
}
%    \end{macrocode}
%
% \end{macro}
%
% \subsection{Defining keys}
%
% \begin{macro}{\mkdefkey}
%
% This is all quite dull, really.  I tried to merge the two cases, but it
% failed because I can't pass around macro parameter names through
% |\@ifnextchar| and their friends.  If anyone has any bright ideas, I'd be
% delighted.
%
%    \begin{macrocode}
\def\mkdef{\@ifstar\mkdef@star@\mkdef@}
\def\mkdef@#1#2{%
  \@ifstar%
    {\mkdef@ii{#1}{#2}}%
    {\@ifnextchar[%
      {\mkdef@iii{#1}{#2}}%
      {\mkdef@i{#1}{#2}}}%
}
\def\mkdef@i#1#2{\@namedef{mk$#1$=#2}##1}
\def\mkdef@ii#1#2{\@namedef{mk$#1$!#2}}
\def\mkdef@iii#1#2[#3]{%
  \toks@{#3}%
  \expandafter\edef\csname mk$#1$!#2\endcsname%
    {\expandafter\noexpand\csname mk$#1$=#2\endcsname{\the\toks@}}%
  \@namedef{mk$#1$=#2}##1%
}
\def\mkdef@star@#1{%
  \@ifstar%
    {\mkdef@star@ii{#1}}%
    {\@ifnextchar[%
      {\mkdef@star@iii{#1}}%
      {\mkdef@star@i{#1}}}%
}
\def\mkdef@star@i#1{\@namedef{mk$#1$*=}##1##2}
\def\mkdef@star@ii#1{\@namedef{mk$#1$*!}##1}
\def\mkdef@star@iii#1[#2]{%
  \toks@{#2}%
  \expandafter\edef\csname mk$#1$*!\endcsname##1%
    {\expandafter\noexpand\csname mk$#1$*=\endcsname{##1}{\the\toks@}}%
  \@namedef{mk$#1$*=}##1##2%
}
%    \end{macrocode}
%
% \end{macro}
%
% And with that, we're done.
%
%    \begin{macrocode}
%<+plain>\done
%</package|macro>
%    \end{macrocode}
%
% \hfill Mark Wooding, \today
%
% \Finale
%
\endinput
