The core question
How can I process text delimited by one of two terminators? For instance, if I want to process text up to the next \A, I can just write
\def\CmdA#1\A{...}
But suppose that \A might not be present, in which case \B should terminate processing instead. Is there a way to write a command
\def\CmdAB#1[\A|\B]{...} % Nonsense syntax
such that
\CmdAB\first\A\second\B
sets #1 to \first and leaves \second\B in the input stream, and
\CmdAB\only\B
sets #1 to \only and leaves nothing in the input stream? \A should take precedence over \B, so that
\CmdAB\other\B\order\A\after\B
sets #1 to \other\B\order and leaves \after\B in the input stream.
Furthermore, since my goal is to pre-process the text and then use it, I need to know which of \A or \B was selected.
What I've tried
I had the idea to scan tokens one at a time, saving the scanned ones in a token register for later processing, and stop scanning when I saw either end token, but I couldn't get it to work. I wrote some simpler code which uses \let to pick characters out of the input, checking for a single terminator, and adding to a token register along the way, but this doesn't work because \let control sequences don't expand. The code is the following:
\newtoks\ScanToks
\newcommand*{\AddTok}[1]{\ScanToks\expandafter{\the\ScanToks#1}}
\newcommand*{\QScan}{\QScan}
\newcommand*{\Scan}{\afterassignment\DoScan\let\Scanned=}
\newcommand*{\DoScan}{%
\ifx\Scanned\QScan\else
\expandafter\AddTok\Scanned
\expandafter\Scan
\fi
}
This looks good, and seems to work when you run it, but it doesn't quite when you look at the output:
*\Scan 1 2 3 \QScan
*\showthe\ScanToks
> \Scanned \Scanned \Scanned .
Once you think about it (and read the right parts of The TeXbook and the right TeX.SE questions), this makes a frustrating sort of sense; if we have \let\a=1 and \let\b=\a, then how could we possibly we tell that \a ought to "expand" into 1 but \b ought to "expand" into \a? But unfortunately, I don't see a way to write a \def which only absorbs one token from the input stream. If \def took arbitrary balanced text, I could probably work with that, but instead \def requires a braced group, and otherwise parses things as being part of the argument specification.
Also, this \Scan command isn't processing the spaces, but that's small potatoes in comparison.
The motivation
I'm trying to write a command which processes the first line of an align-like environment. This command needs to:
- Absorb tokens up until the first
\\. - Process those tokens (that is, count ampersands to determine the number of columns).
- Place those tokens back in the input stream, preceded by the computed information (that is, the number of columns).
However, the \\ delimiter might not be present if the environment is only one line long; in that case, I'd need to instead process the whole environment, up through \end{EnvName} (and all the rest is the same). But at the start of an environment, I can't know which of these two situations is the case, and so I want to scan ahead to the next \\ or the \end{EnvName} (thus motivating the question).
environpackage) and then check whether that content contains\\. – Stephan Lehmke Dec 15 '12 at 05:20longtable) I wouldn't worry about that. For instance, theamsmathenvironments all pre-read their contents so that the width of the alignment can be measured in a preprocessing step. That's the standard way to do it. All else will lead to a huge amount of work. If you really want to scan tokens, you can study this answer. – Stephan Lehmke Dec 15 '12 at 05:33amsmath-like environments anyway.) Would you consider leaving this as an answer, then? I'll leave the question open for now to see if anybody can answer it as asked, but your answer is a good one. (And thanks for the link.) – Antal Spector-Zabusky Dec 15 '12 at 05:39amsmaththen you can also hook into the measuring step and extract the first line without additional overhead. – Stephan Lehmke Dec 15 '12 at 06:01\collect@body/\collect@@body; for the environments that call into\start@align(e.g.,alignandalignatand their starred variants), this body is passed to\measure@, which you can hack into. Note thatalignedandalignedat(at least) don't pre-gather their input (as far as I can tell). – Antal Spector-Zabusky Dec 16 '12 at 00:48tokcyclepackage. – Steven B. Segletes Jan 23 '21 at 00:59