[Tex/LaTex] Parsing a macro argument character-by-character for conditional execution

chessconditionalsmacros

In an attempt to specify a seemingly endless amount of arguments to a macro, I would like to supply only one #1, where #1 is a string that contains a bunch of specific characters. For example

\mymacro{1jq4PK2/62Bb/-krkR/...}

Then, I'd like to sequentially parse #1 on a character-by-character basis and do something based on the character. As in the example above, characters could be anything from numbers 0...9, letters a...z and A...Z (so conditioning should be case sensitive) or symbols -,/,…

The bigger picture here is create a macro that uses chessfss to typeset a chess board or arbitrary shape using FEN notation. The symbol - will denote a blank square, numbers will denote empty squares (with no piece on it), / will denote the next rank, and letters will denote the colour (via capitalization) and type (via the specific letter) of the piece.

The code provided by the skak package is limited to a fixed 8-rank (or file) chess board. Specifically, skak provides \InitBoard that is defined as

\def\InitBoard(#1/#2/#3/#4/#5/#6/#7/#8){%
  %<code>
}

where each one of the 8 arguments is processed in a very "nested fashion" using (I'm skipping some intermediate steps for the sake of brevity):

\def\FenConvert#1{%
  \EqStr{8}{#1}%
  {EEEEEEEE}%
  {\EqStr{7}{#1}%
    {EEEEEEE}%
    {\EqStr{6}{#1}%
      {EEEEEE}%
      {\EqStr{5}{#1}%
        {EEEEE}%
        {\EqStr{4}{#1}%
          {EEEE}%
          {\EqStr{3}{#1}%
            {EEE}%
            {\EqStr{2}{#1}%
              {EE}%
              {\EqStr{1}{#1}%
                {E}%
                {#1}}}}}}}}}

\def\ParseFenRank#1{\ParseFenRankA(#1Z)}
\def\ParseFenRankA(#1#2){%
  \EqStr{Z}{#1}%
  {}%
  {\FenConvert{#1}\ParseFenRankA(#2)}}

I would like to steer clear from this since it is not entirely reader-friendly and somewhat unappealing, and rather use some "case statement", where I condition on the characters in the macro argument, one by one. The boolexpr package provides such a switch environment, but I'm not sure how to extend it to work in the way I want to.

Best Answer

I have implemented character or token parsers like this in my tikz-timing, collcell and the ydoc packages. They are all available under https://bitbucket.org/martin_scharrer/ and on CTAN, of course. Basically you are either use \futurelet or a macro argument (#1) to read the next token or character and branch accordantly, then call your input collection macro recursively.

In collcell I added a case statement for tokens which could be changed to take a character instead. The current syntax is:

\cc@case
   <token>{<code>}
   <token>{<code>}
   <token>{<code>}
   <token>{<code>}
\endcc@case

where the token was read before using \futurelet\collect@cell@lettoken\next. See the source code of the case statement for more details.

In ydoc I started with a large \ifcase statement but then changed it to a look-up table-like feature: There is one macro define for every input character, e.g. \@namedef{mypkg@char@<char>}{<code for this character>}, and then the parser macro only need to read the next character and use \@nameuse{mypkg@char@#1} (or \csname directly). As long you don't have any special tokens like {, } and spaces this works fine. Otherwise you need to use \futurelet/\@ifnextchar before to check for these first.

It is also possible to always use tokens with \futurelet and use \meaning to build the macro names, i.e. \@nameuse{mypkg@char@\meaning\@let@token}. This works with all tokens, normal or special. The handler macro should then remove the token from the input stream.

A simply character-by-character parser based on this looks like this:

\documentclass{article}

\makeatletter
\newcommand\mymacro[1]{%
    \@mymacro#1\@nnil
}
\def\@mymacro#1{%
    \ifx\@nnil#1\relax\else
        \@nameuse{mymacro@char@#1\expandafter}%
    \fi
}
\def\defcharcode#1{%
    \@namedef{mymacro@char@#1}%
}
\defcharcode{1}{%
    (1)
    \@mymacro
}
\defcharcode{j}{%
    (j)
    \@mymacro
}
\defcharcode{q}#1{% reads further character!
    (q#1)
    \@mymacro
}
\defcharcode{.}{%
    (.)
    \@mymacro
}
\makeatother

\begin{document}

\mymacro{1jq4...}

\end{document}

This prints: (1) (j) (q4) (.) (.) (.).

The recursive call is placed in every character handler so that there is nothing after it so that it can read further input character if required.

Related Question