5

Consider this code:

\documentclass{article}
\usepackage{filecontents}
\begin{filecontents}{foo.txt}
abc
\end{filecontents}
\begin{filecontents}{bar.txt}
abc
\end{filecontents}
\begin{document}
\makeatletter
\def\einput#1{\@@input #1 \space}
\newcommand{\showexpanded}[1]{%
  \everyeof{\noexpand}%
  \long\edef\@tempcontents{#1}%
  \show\@tempcontents
}
\makeatother
\showexpanded{\einput{foo.txt}\einput{bar.txt}}
.
\end{document}

It works approximately as expected, giving

$ pdflatex foo.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.16 (TeX Live 2015/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(./foo.tex
LaTeX2e <2016/02/01>
Babel <3.9q> and hyphenation patterns for 7 language(s) loaded.
(/usr/share/texlive/texmf-dist/tex/latex/base/article.cls
Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
(/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo))
(/usr/share/texlive/texmf-dist/tex/latex/filecontents/filecontents.sty)

LaTeX Warning: Writing file `./foo.txt'.



LaTeX Warning: Writing file `./bar.txt'.


(./foo.aux) (./foo.txt) (./bar.txt)
> \@tempcontents=\long macro:
->abc \space abc \space .
\showexpanded ...ontents {#1}\show \@tempcontents

l.18 ...expanded{\einput{foo.txt}\einput{bar.txt}}

?

However, if I change \noexpand to or \relax, then I get ! File ended while scanning definition of \@tempcontents. Why is this? Is there any way to eat the EOF without causing the subsequent \space to be left over? (I got the use of \noexpand by cargo-culting http://ctan.mirrors.hoobly.com/macros/latex/contrib/oberdiek/catchfile.pdf)

3
  • 2
    "Why?" questions are hard to answer. The question is "What was DEK thinking of" it's just a misdesign of the system that to "help trap errors" most operations are not allowed to cross a file boundary, and just by luck that \everyeof provides a way of hacking round that. Commented Nov 11, 2019 at 21:50
  • 2
    The question is not "why did DEK make the language this way?" but "what are the semantics of the language such that this works?" (and implicitly: what else would work?). Like, is the \noexpand applying to the EOF? (But it seems to be applying to the \space, so it probably isn't that.) I have found that \string also works (but leaves undesireable strings in the output), but \show does not. What is the category of things that work here, and what is the property they share that makes them work? It sounds like you're implying that some operations treat EOF as whitespace, but most error? Commented Nov 11, 2019 at 22:46
  • See also tex core - Use of \everyeof and \endlinechar with \scantokens - TeX - LaTeX Stack Exchange for other notes regarding behavior of \scantokens.
    – user202729
    Commented Dec 27, 2021 at 2:56

1 Answer 1

5

Digging in the TeX program code, my main finding is that there is neither an explicit nor an implicit end-of-file marker inserted in the token stream.

When TeX sees an \input command, it executes some file reading maintenance code (begin_file_reading) and switches to the new file for further reading of input lines. If it either sees an \endinput command or has reached the actual end of the input file, it sets a flag force_eof to true. The latter is used to determine if the current file should be popped from the reading stack (end_file_reading) when reading the next line. Processing then goes back to the place \input was called from.

If force_eof is set to true while TeX tries to read an input line, it calls check_outer_validity, which in turn checks if the end of file is allowed at the current source location. It checks that based on a variable called scanner_status. When the scanner status doesn't indicate that a definition is being formed, a parameter is being read etc., the check passes and TeX goes on processing the next token. Otherwise a "runaway" error message is issued, based on the current status. Additionally, ε-TeX adds the material defined in the \everyeof hook into the token stream.

Conceptually, one can imagine the input token stream of

\everyeof{\noexpand}
\edef\@tempcontents{\@@input foo.txt \space}

at the time \@tempcontents is being defined as the following sequence:

<contents of input file> <endline char> \noexpand <EOF> \space

<endline char> is the character TeX inserts based on the value of \endlinechar after every line is has read. This is `\^^M by default, which expands to a space character and leads to the extra space after abc in your console output. Next comes the contents of the \everyeof token list, which is \noexpand in this example.

Now the mysterious <EOF> marker occurs. The important thing here is, <EOF> is not an actual or internal token, it's just the brief moment in which TeX checks if an \outer macro is allowed at this position of processing.

As TeX is in an \edef at that moment, the commands we may use in \everyeof to trick TeX into ignoring the outer test have to be a) expandable and b) allow an \outer macro to follow them. By hints in the code, at least the following three do work in this place:

  • \noexpand
  • \string
  • \meaning

The reason those work is that they temporarily switch to scanner status normal before reading the next token, thus negating the error condition in check_outer_validity. TeX is dazed for a little moment and believes it is in normal processing mode, while actually still in the process of scanning for the end of the macro definition.


If you also want to remove the \space token, you can use the following:

\newcommand{\showexpanded}[1]{%
  \everyeof{\expandafter\@gobble\noexpand}%
  \long\edef\@tempcontents{#1}%
  \show\@tempcontents
}

which outputs

> \@tempcontents=\long macro:
->abc abc .
\showexpanded ...ontents {#1}\show \@tempcontents
3
  • Is "the following token" in this case the EOF, or the \space? (I'm still confused about what's actually going on under the hood that suppresses the EOF error.) And thanks for the \@gobble trick! Commented Nov 11, 2019 at 22:50
  • @JasonGross In order to remove the \space, just remove the blank space before it.
    – egreg
    Commented Nov 12, 2019 at 10:50
  • Talk about the "at least" part, there's at least one more -- \ifx also satisfies the condition (excluding \immediateassignment\futurelet which is "cheating"); although you need to remember to expand another \fi after that.
    – user202729
    Commented Jul 12, 2022 at 5:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .