13

This is an attempt to make clear what happened in this question of xport (now deleted, sorry). The now deleted question included code that tried to make my answer to this question of xport work for optional arguments, but the code failed. A shorter illustration: The following code doesn't compile if one removes the [optional argument].

\documentclass{minimal}
\usepackage{listings,fancyvrb}

\newenvironment{Row}[1][]
    {\VerbatimEnvironment
     \begin{VerbatimOut}{\jobname.tmp}}
    {\end{VerbatimOut}\lstinputlisting{\jobname.tmp}}

\begin{document}
\begin{Row}[optional argument]
\relax
\end{Row}
\end{document}

The error message says

! FancyVerb Error:
  Extraneous input `\relax ' between \begin{Row}[<key=value>] and line end

Of course, the optional argument (and in fact the whole construction) is utterly pointless here, but hey, it's a minimal example. To make the example in xport's question also work for optional arguments is quite interesting, I think.

The question: What exactly is happening here, and how can one fix the code?

(What I found out so far: \begin{VerbatimOut}{\jobname.tmp}} wants to see a linebreak immediately after it (try putting a %), and this somehow gets messed up by \begin{Row} looking for the optional argument.)

Hendrik Vogt
  • 37,935

4 Answers4

8

I can answer what is happening, but I don't have a fix for you.

What is happening is that when looking for the optional argument, the ^^M that got inserted by TeX at the end of the line is tokenized. Since TeX is in state M, and ^^M has category code 5, this is turned into a space token. LaTeX's \kernel@ifnextchar gobbles the space and keeps looking for a nonspace character. It finds \relax which isn't [ so it expands to \\Row[{}].

\\Row[#1] has the begin environment code. \VerbatimOut changes the category code of ^^M to active and then looks for an active ^^M to follow. However, that was already tokenized with catcode 5 and then swallowed. Since it finds \relax, it complains.

Edit:
If, like me, you have a hard time seeing how the space is getting swallowed by tracing this, the key is this line.

\reserved@c  ->\futurelet \@let@token \@ifnch
            ^

The extra space that I marked with the caret is part of the definition of \@xifnch:

\def\:{\@xifnch} \expandafter\def\: {\futurelet\@let@token\@ifnch}

Edit 23:
Sorry, I should have put a bit a thought into this. This isn't hard to fix. My original comments give the hint of how to fix it, just make ^^M active! A better solution is to reinsert an active ^^M character in the case that there is no optional argument. That way the optional argument can be on the next line as in the third use of \begin{Row} below.

\documentclass{minimal}
\usepackage{listings,fancyvrb}

\begingroup
\catcode`\^^M\active%
\global\def\activeeol{^^M}%
\endgroup
\makeatletter
\newenvironment{Row}{%
        \@ifnextchar[\Row@\Row@noargs
}{%
        \end{VerbatimOut}%
        \lstinputlisting{\jobname.tmp}%
}
\def\Row@[#1]{%
        #1\par
        \VerbatimEnvironment
        \begin{VerbatimOut}{\jobname.tmp}%
}
\def\Row@noargs#1{%
        \edef\temp{[]\activeeol\string#1}%
        \expandafter\Row@\temp
}
\makeatother
\begin{document}
\begin{Row}[optional argument]
\relax
\end{Row}

\begin{Row}
\relax
\end{Row}

\begin{Row}
[Another optional argument]
\relax
\end{Row}
\end{document}
TH.
  • 62,639
  • @TH.: It does not work if the optional argument is omitted. – Display Name Jan 16 '11 at 05:56
  • @xport: It does for me. I made a full minimal example. Does that not work for you? – TH. Jan 16 '11 at 07:46
  • @TH.: yours works! However, when I applied it to this http://tex.stackexchange.com/questions/9008/how-to-make-my-unusual-environment-accept-one-argument/9012#9012, it did not work. – Display Name Jan 16 '11 at 08:17
  • @xport: That question doesn't seem to be related. You deleted the question Hendrik linked to so I guess this is resolved. – TH. Jan 16 '11 at 09:03
  • @TH: The link I gave above your comment is the same. – Display Name Jan 16 '11 at 09:11
  • @xport: No, I linked to your question about one optional argument (which is no longer there); you linked to your question about one mandatory argument. – Hendrik Vogt Jan 16 '11 at 09:31
  • @Hendrik: @TH's suggestion is to close the link so I deleted it. The link about the mandatory argument can be modified a bit to match the deleted one. Or I should repost again? – Display Name Jan 16 '11 at 09:51
  • @xport: TH was right with his suggestion. And please don't modify your question with the mandatory argument: The question is solved and has an accepted answer. You also probably shouldn't repost. Let me read the answers here first; I didn't do that so far. – Hendrik Vogt Jan 16 '11 at 09:55
  • @TH.: Thanks for your answer. I'm still in the process of understanding. I'm puzzled since I did try to remove \@xifnch from \@ifnch, but that didn't help. – Hendrik Vogt Jan 16 '11 at 10:18
  • @Hendrik: The issue is that the ^^M gets tokenized as a space character. I imagine that if you removed the \@xifnch, then you get into an infinite loop because the test of the let token (\@let@token or something) against \@sptoken (or whatever) keeps being true over and over. In any event, once the ^^M has been tokenized, it's too late to change its catcode so the \begin{VerbatimOut} is going to find a token that isn't an active ^^M. A better solution, perhaps, would be to use \@ifnextchar[\Row@{\Row@[]\activeeol} where \activeeol is \let to an active ^^M. – TH. Jan 16 '11 at 10:30
  • @Hendrik: Okay, that doesn't work. I didn't read the trace output closely enough. \FV@BeginScanning#1^^M so it's looking for a literal character. I'll update my answer to the better one. – TH. Jan 16 '11 at 10:43
  • @TH.: Ah, OK, I had already been wondering. – Hendrik Vogt Jan 16 '11 at 10:46
  • @TH.: Sorry, above I wasn't clear enough. What I tried was: remove the whole \ifx\@let@token\@sptoken test from \@ifnch; this in particular removes the \@xifnch. This way I don't get an infinite loop, but still it doesn't work. That I still can't understand. – Hendrik Vogt Jan 16 '11 at 10:51
  • @Hendrik: Still doesn't work meaning it complains about "Extraneous input"? That's what I'd expect. The \futurelet has tokenized the ^^M so it's a space_10 character token. Thus the argument to \FV@BeginScanning is the space_10 character and the \relax token. \FV@BeginScanning checks if #1 is empty and since it isn't, you get the error. – TH. Jan 16 '11 at 11:01
  • @TH.: Seems you're right. But that's really mean of the \futurelet, isn't it? I mean, \futurelet isn't supposed to mess up anything following it; that's what we love about \futurelet. Is this behaviour of \futurelet documented anywhere? – Hendrik Vogt Jan 16 '11 at 12:17
  • @TH.: Your new version works great. Two comments: 1. In the group where \activeeol is defined, the % s are somehow not needed. 2. I'd find it easier if the last argument of \@ifnextchar was \expandafter\Row@noarg\activeeol, with \def\Row@noarg{\Row@[]}. By the way, I didn't find this \futurelet behaviour in the TeXbook, but in TeXbyTopic it says "If a character token has been \futurelet to a control sequence, its category code is fixed. The subsequent <token1> cannot change it anymore." Another lesson learned. – Hendrik Vogt Jan 16 '11 at 12:52
  • @xport: It's straightforward to make TH's first version of the code work for your optional argument. With his second version I can't get it done; don't know why. – Hendrik Vogt Jan 16 '11 at 14:08
  • @Hendrik: Where is the first version and how to apply it to my real scenario? – Display Name Jan 16 '11 at 14:22
  • @xport: just click on " hours ago". – Hendrik Vogt Jan 16 '11 at 14:23
  • @Hendrik: I already tried it. It works but I cannot apply them to my real scenario. See the third comment. – Display Name Jan 16 '11 at 14:57
  • @xport: I applied it to your real scenario without problems: \newenvironment{Row}{\catcode\^^M\active @ifnextchar[\Row@{\Row@[]}} {\end{VerbatimOut}\aftergroup\gtemp} \def\Row@[#1]{\gdef\gtemp{&\tabularnewline\hline}\VerbatimEnvironment\begin{VerbatimOut}{\jobname.tmp}}` – Hendrik Vogt Jan 16 '11 at 15:08
  • @Hendrik: Thanks, it works. But I noticed some bugs. – Display Name Jan 16 '11 at 15:31
  • @Hendrik: Re comments, I think that's because TeX is in vertical mode and active ^^M is \par which is (almost completely) ignored in vertical mode. Re \futurelet, the text you quoted is familiar. I guess I read it in TeX by Topic. I thought it was in The TeXbook, but I cannot find it. – TH. Jan 16 '11 at 16:24
  • @TH.: Thanks for letting me know that I'm not alone. But I just realized that there's a bug in your code. If you replace minimal with article and \relax with \large a in your new code, then you get ! TeX capacity exceeded. (By the way, do you ever sleep?) – Hendrik Vogt Jan 16 '11 at 16:34
  • @Hendrik: Yeah, you're right. The \large was getting tokenized. The fix is simple. But I'm starting to think that supporting optional arguments on the next line was a mistake. At any rate, I'll edit the code. As for sleep, I'm going to do so right now! – TH. Jan 16 '11 at 17:30
  • @TH.: Oh my, so you've got to play a bit verbatim with \string - tricky. And I should have seen it myself, having written "another lesson learned". Once again this \futurelet behaviour that I've only learned today. Thanks a lot! – Hendrik Vogt Jan 16 '11 at 17:51
  • 1
    @xport: In case you didn't see it so far; now you've got another solution. – Hendrik Vogt Jan 18 '11 at 09:02
  • @Hendrik: Thanks for informing. I will try to apply it to my real scenario. – Display Name Jan 18 '11 at 10:00
6

EDIT: TH.'s answer is much simpler. The one below should depend less on the details of the implementation of \VerbatimEnvironment, but that's the only good thing about it.

I propose the following code. The idea is to make a new environment (which I called Row, renaming the old Row to oldRow) check for an optional argument "by hand", and write the relevant thing to a file. Then the file (\jobname.row) is read so that fancyvrb can happily set the relevant catcodes.

All this can probably be done using \scantokens, but I am still quite confused about newlines inside \scantokens.

I also added *#1* to your definition of the environment: this way, we can check what is going on with the optional argument.

\documentclass{minimal}
\usepackage{listings,fancyvrb}

\newenvironment{oldRow}[1][]
{*#1*\VerbatimEnvironment
  \begin{VerbatimOut}{\jobname.tmp}}
  {\end{VerbatimOut}\lstinputlisting{\jobname.tmp}}


\begin{document}

% ==============================================
\makeatletter
\newwrite\RowWrite

\newcommand{\makeallother}{%
  \count0=0\relax 
  \loop\relax 
  \catcode\count0=12\relax 
  \advance\count0 by 1\relax 
  \ifnum\count0<256\relax
  \repeat\relax}

\newenvironment{Row}{%
  \makeallother
  \futurelet\next
  \Row@aux@i}{}

\newcommand{\Row@aux@i}{%
  \def\Row@optional@arg{}%
  \ifx[\next
  \expandafter\Row@grab@until@bracket
  \else
  \expandafter\Row@grab@until@end
  \fi}

\def\Row@grab@until@bracket[#1]{%
  \def\Row@optional@arg{#1}\Row@grab@until@end}

% First expand the \csname...\endcsname,
% then the \string, then the \def.
\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\Row@grab@until@end
\expandafter\expandafter\expandafter#%
\expandafter\expandafter\expandafter1%
\expandafter\string\csname end{Row}\endcsname{%
  \begingroup%
  \newlinechar=`\^^M%
  \immediate\openout\RowWrite\jobname.row\relax%
  \immediate\write\RowWrite{%
    \string\begin{oldRow}[\Row@optional@arg]#1\string\end{oldRow}%
  }%
  \immediate\closeout\RowWrite%
  \endgroup%
  \end{Row}\input{\jobname.row}%
}

\makeatother
% ==============================================

\begin{Row}
  \relax
  \relax
\end{Row}

\begin{Row}[option]
  \relax
  \relax
\end{Row}


\end{document}
  • Wow, this really looks a bit complicated. The only thing I immediately understand is the use of the \expandafter s :-) Thanks nevertheless! – Hendrik Vogt Jan 16 '11 at 12:20
  • @Hendrik: I check whether the Row environment has an optional argument, and then write \begin{oldRow}[...] ... \end{oldRow} to a file. Eveything is written verbatim (thanks to \makeallother). Finally, the whole thing is input. Since I made sure that the optional argument is now there, the old version will now work. – Bruno Le Floch Jan 16 '11 at 14:45
4

As I learned from TH in the comments to his answer, the real culprit is the TeX primitive \futurelet that is used to detect if there's an optional argument. If there is none, then the end-of-line character ^^M (with usually has catcode 5) gets tokenized by the \futurelet, meaning that the catcode can't be changed anymore. Now \begin{VerbatimOut} changes the catcode of ^^M to 13 (active), but this has no effect on the ^^M that is already tokenized, and one gets the error.

Here's another solution along the lines of the definition of fancyvrb's \FV@Environment. Unlike TH's solution, it does not allow the optional argument to start on the next line (but it does allow spaces before the [). However, this is maybe the right behaviour: Who knows; the code could start with a [. Thus, when you compile the code below, then [Another optional argument] will be the first line of the third piece of code.

\documentclass{minimal}
\usepackage{listings,fancyvrb}

\makeatletter
\newenvironment{Row}{%
    \catcode`\^^M=\active
    \@ifnextchar[%
        {\catcode`\^^M=5 \Row@}
        {\catcode`\^^M=5 \Row@[]}
}{%
    \end{VerbatimOut}%
    \lstinputlisting{\jobname.tmp}%
}
\def\Row@[#1]{%
    *#1*\par
    \VerbatimEnvironment
    \begin{VerbatimOut}{\jobname.tmp}%
}
\makeatother
\begin{document}
\begin{Row}[optional argument]
\large a
\end{Row}

\begin{Row}
\large a
\end{Row}

\begin{Row}
[Another optional argument]
\large a
\end{Row}
\end{document}
Hendrik Vogt
  • 37,935
  • Unless I'm mistake, that's essentially what my original "Edit 2" did. It just made ^^M active before using \@ifnextchar. Then I started adding extra code to handle the optional argument being on the next line. – TH. Jan 18 '11 at 09:15
  • @TH.: Yes, almost. That's why (and since I learned the \futurelet stuff from you) I made it community wiki. I just wanted to document (mainly for myself) what \futurelet does and what the fancyvrb way of doing it is. – Hendrik Vogt Jan 18 '11 at 10:53
1

I ran into this same problem with a custom verbatim environment. However, I'm not that savvy with the TeX internals, so I didn't want to mess with the ^^M stuff described here. (I wouldn't know how to explain it to my coauthors, so I don't want to use it in our paper.)

Instead, I added one required (but unused) argument after the optional argument, and that seems to fix the parsing problem. I.e.,

% Original (broken) version
\newenvironment{MyEnv}[1][default-value]{...}{...}

% Updated (work-around) version
\newenvironment{MyEnv}[2][default-value]{...}{...}
%                      ^ extra (unused) explicit arg

The usages look like this:

\begin{MyEnv}{}
% uses default value
\end{MyEnv}

\begin{MyEnv}[X]{}
% uses custom value "X"
\end{MyEnv}

It's obviously not ideal, but again, I found this preferable to adding a bunch of arcane TeX commands that neither I nor my coauthors would understand...


Note: +1 to the other answers for actually explaining what's happening. If not for the great answers here, I would not have realized that adding an extra argument could be a simple work-around for this issue.

DaoWen
  • 441