As pointed out by egreg, what we gain from such a \dynamictable
command accepting an “arbitrary number of arguments”, as compared to using standard tabular markup where rows are terminated by \\
, is not very clear. But let's study it as an exercise. :-)
What happens with your code
The error you get is due to the fact that your \checknextline
call is immediately followed by \end{supertabular}
.As a consequence, after \dynamictable
has gobbled and inserted the first argument (1,1) & (1,2)
, \checknextline
finds that the next token is the \end
from \end{supertabular}
; as it is not \let
-equal to a \bgroup
, \checknextline
inserts \\
and doesn't recurse. So, the supertabular
environment finishes, then TeX finds the second “argument” in the input stream. In fact, for TeX, this is not an argument but a braced group. TeX finds the &
inside this group, and since we are not inside any alignment anymore (tabular
& friends), this character token of category code 4 (alignment tab) is invalid, which gives the error:
./orig.tex:22: Misplaced alignment tab character &.
l.22 \dynamictable{(1,1) & (1,2)}{(2,1) &
How to fix it
Let's leave supertabular
out of the game for now, it just complicates things. The first attempt at a fix could be (ugly, but shows the reasoning):
\documentclass{article}
\makeatletter
\newcommand{\dynamictable}[1]{%
\begin{tabular}[l]{c l}
#1 \checknextline
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{\\}}
\newcommand{\gobblenextline}[1]{#1\@ifnextchar\bgroup{\gobblenextline}{}}
\makeatother
\begin{document}
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}
\end{tabular}
\end{document}
This fails with:
./a.tex:16: Extra alignment tab has been changed to \cr.
<recently read> \endtemplate
l.16 \dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}
because \checknextline
calls \gobblenextline
which inserts its #1
(the second “argument”) without prepending any \\
. Therefore TeX finds two alignment tab tokens (&
) on the same line of the alignment, which at this point looks like:
(1,1) & (1,2)(2,1) & (2,2)
This doesn't match the preamble you specified (at least 3 columns would be required), hence the error. Inserting the appropriate \\
can be done with:
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{}}
\newcommand{\gobblenextline}[1]{\\ #1\@ifnextchar\bgroup{\gobblenextline}{}}
With a document body like this:
\begin{document}
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}
\end{tabular}
\end{document}
this works flawlessly.
But this is not beautiful syntax, so as the next step, one could make an environment that inserts the \end{tabular}
itself at the end, instead of a command that has to be followed by \end{tabular}
:
\documentclass{article}
\makeatletter
\newenvironment{dynamictable}[1]{%
\begin{tabular}[l]{c l}
#1\checknextline
}{%
\end{tabular}%
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{}}
\newcommand{\gobblenextline}[1]{\\ #1\@ifnextchar\bgroup{\gobblenextline}{}}
\makeatother
\begin{document}
\begin{dynamictable}{(1,1) & (1,2)}{(2,1) & (2,2)}
\end{dynamictable}
\end{document}
This works, but:
error reporting can be improved by using \tabular
and \endtabular
inside our custom environment, instead of \begin{tabular}
and \end{tabular}
(this way, if there is an error while processing the dynamictable
environment, it will be reported as occurring inside this environment as opposed to within tabular
which the end-user didn't explicitly use);
taking the first braced group as an argument of the environment doesn't seem very useful; the tabular contents could as well belong to the environment body.
Implementing these little improvements and customary whitespace handling for environments, we obtain:
\documentclass{article}
\makeatletter
\newenvironment{dynamictable}{%
\tabular[l]{c l}
\checknextline
}{%
\unskip
\endtabular
\ignorespacesafterend
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{}}
\newcommand{\gobblenextline}[1]{#1\@ifnextchar\bgroup{\\ \gobblenextline}{}}
\makeatother
\begin{document}
\begin{dynamictable}
{(1,1) & (1,2)}
{(2,1) & (2,2)}
\end{dynamictable}
\end{document}
\@ifnextchar
has the property of ignoring space tokens when looking ahead for the “next char”, which allowed us to put the two braced groups on separate lines in order to improve readability.
Alas, this doesn't work with supertabular
(I haven't checked why), but it seems this package has been superseded by longtable
for many use cases, and the approach described above does work with longtable
:
\documentclass{article}
\usepackage{longtable}
\makeatletter
\newenvironment{dynamictable}{%
\longtable[l]{c l}
\checknextline
}{%
\unskip
\endlongtable
\ignorespacesafterend
}
\newcommand{\checknextline}{\@ifnextchar\bgroup{\gobblenextline}{}}
\newcommand{\gobblenextline}[1]{#1\@ifnextchar\bgroup{\\ \gobblenextline}{}}
\makeatother
\begin{document}
\begin{dynamictable}
{(1,1) & (1,2)}
{(2,1) & (2,2)}
\end{dynamictable}
\end{document}
(screenshot identical to the previous one)
Retaining the command syntax
As pointed out by Skillmon, it is possible to retain the syntax of a command for \dynamictable
as opposed to making it an environment (I believe I prefer the environment syntax, but you are of course free to choose whichever you prefer). For this, all that needs to be done is to insert the appropriate \endtabular
or \endlongtable
from within \checknextline
and \gobblenextline
when they detect that there is no other braced group following:
\documentclass{article}
\usepackage{longtable}
\makeatletter
\newcommand{\dynamictable}{%
\longtable[l]{c l}
\checknextline
}
\newcommand{\checknextline}{%
\@ifnextchar\bgroup{\gobblenextline}{\endlongtable}%
}
\newcommand{\gobblenextline}[1]{%
#1\@ifnextchar\bgroup{\\ \gobblenextline}{\endlongtable}%
}
\makeatother
\begin{document}
\dynamictable
{(1,1) & (1,2)}
{(2,1) & (2,2)}
\end{document}
Of course, you may write the body as:
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}
if you want. In case you use this approach, you can use \relax
to terminate the table and be sure not to mistakenly take a following opening brace as the start of another line:
\dynamictable{(1,1) & (1,2)}{(2,1) & (2,2)}\relax
{\bfseries This doesn't belong to the table.}
That said, at this point, you should realize that we are back to using an end delimiter, only with less readability than with \end{dynamictable}
. Thus, I still prefer the environment-based approach.
\dynamictable{a & b}{c & d}
is better and easier to maintain thana & b \\ c & d
?\checknextline
(no need for\gobblenextline
). If we make this recursive\checknextline
take a delimited argument as opposed to a brace-delimited one, we are back to atabular
-like syntax, and I'm not sure of the usefulness of all this—apart from trying to improve one's LaTeX error diagnosing and programming skills. :-)