9

I'm trying to understand the precise semantics of \protect as described in the beginning of section 11.4 Robust commands and protect of the source2e document, the annotated source code of the LaTeX2e format, on pp. 42-43 (I have substituted numbers below for bullet points in source2e):

[...] there are (at least) three different occasions when these commands [i.e. LaTeX commands --Evan Aad] are not safe. These are called 'moving arguments' by LaTeX, and consist of:

  1. writing information to a file, such as indexes or tables of contents.
  2. writing information to the screen.
  3. inside an \edef, \message, \mark, or other command which evaluates its argument fully.

The method LaTeX uses for making fragile commands robust is to precede them with \protect. This can have one of five possible values:

  1. \relax, for normal typesetting. So \protect\foo will execute \foo.
  2. \string, for writing to the screen. So \protect\foo will write \foo.
  3. \noexpand, for writing to a file. So \protect\foo will write \foo followed by a space.
  4. \@unexpandable@protect, for writing a moving argument to a file. So \protect\foo will write \protect\foo followed by a space. This value is also used inside \edefs, \marks and other commands which evaluate their arguments fully.
  5. \@unexpandable@noexpand, for performing a deferred write inside an \edef. So \protect\foo will write \foo followed by a space. If you want \protect\foo to be written, you should use \@unexpandable@protect. (Removed as never used.)

I struggle with the following issues.

  1. Item 4: The term 'moving argument' is used in a way that is inconsistent with the way it was defined before. In the opening paragraph it was defined as one of three occasions when the use of commands is risky, whereas in item 4 it designates a chunk of the input that is written to a file. So what exactly is a moving argument?
  2. Item 4: There's a mismatch between items 3 and 4: Item 3 says 'for writing to a file' and item 4 says 'for writing a moving argument to a file'. So the condition expressed in item 3 subsumes the one expressed in item 4, yet the actions specified in each case are different. How is it possible?
  3. Item 5: What's a 'deferred write'?
  4. Item 5: At the end of the item there's a comment that reads 'Removed as never used.` What does it mean?

This question is related to this one.

  • 1
    Re. 3. There is a difference between \write, \immediate\write and \protected@write. \protected@write does not write until the code is shipped, even if it is inside a savebox. It also prevents \thepage from being expanded immediately. – John Kormylo Jun 26 '17 at 18:06
10

A 'moving' argument is where the argument to some LaTeX command will be 'moved around', either by writing it to a file, writing it to a message or by otherwise fully-expanding it. The confusion likely occurs because these have some overlap, and because two cases may apply simultaneously. In particular, we sometimes need to write material to file which itself will constitute a moving context when read back, which is why there is a difference between 'write to a file' and 'write a moving argument to a file'. We also may need to 'force' expansion for stuff which will later be expanded again: the 'deferred write' business.

The 'classical' example of a moving argument is the mandatory value which goes with a sectioning command:

\section{This-is-a-moving-argument~\cite{Lamport}}

I've also deliberately included a couple of items that need handling properly, ~ and \cite. Both have 'robust' internals:

  • ~ => \nobreakspace {} => \protect \nobreakspace[space]
  • \cite => \protect\cite[space]

(Those [space] markers are part of the token names, so are important.)

Let's see what happens in various cases. The first is easy: typesetting the section heading. Here, \protect is \relax and does nothing, and we execute \nobreakspace[space] or \cite[space], respectively.

To get the second case we need to create a message. I'll use two to illustrate a subtle point:

\PackageWarning{foo}{\cite seen}
\PackageWarning{foo}{\protect\cite seen}

would yield

Package foo Warning: \cite seen on input line ...
Package foo Warning: \citeseen on input line ...

Notice the spacing difference. Applying \tracingall you'll see it happens as in the first case we get \cite expanding to \protect\cite[space], and it's the latter that \protect as \string is applied to. In the second line, we directly apply \protect (\string) to \cite, so there is no space. (Remember that the 'space' after \cite is dropped by TeX at a low level.)

For the third case, we could again 'force the issue' by writing to a file

\makeatletter
\newwrite\mywrite
\immediate\openout\mywrite=\jobname.tmp
\protected@write\mywrite{}{\cite{}}
Foo\newpage % See later!

will yield

\cite  {}

where there are two spaces in the file: that's because we are exploring case 3. Applying \noexpand to \cite[space] inserts an additional space after the name. (Crucially, those spaces are ignored when reading back: a central part of this 'trick'.)

Before we move to case 4, we need to know something about how \write works and what a 'deferred write' is. The TeX \write primitive stores its argument for writing during page shipout: that is necessary to know page numbers and similar. Expansion happens at the point of writing to the file. This means that some content may have changed: not ideal. The LaTeX \protected@write macro works by expanding its argument with a few exceptions, then waiting for the shipout to deal with those 'left overs', which includes page number:

\long\def \protected@write#1#2#3{%
      \begingroup
       \let\thepage\relax
       #2%
       \let\protect\@unexpandable@protect
       \edef\reserved@a{\write#1{#3}}%
       \reserved@a
      \endgroup
      \if@nobreak\ifvmode\nobreak\fi\fi
}

That takes us back to case four and the \section example. For writing TOC data, this will end up calling

\addtocontents{toc}{\protect \contentsline {section}
  {\ifnum 1>\c@secnumdepth \else \protect \numberline {\csname thesection\endcsname }\fi This-is-a-moving-argument~\cite {Lamport}}
  {\thepage }}

which is then passed to \protected@write. The 'payload' gets passed to the \edef, and we get \reserved@a defined as

\write \@auxout {\@writefile{toc}{\protect \contentsline {section
}{\protect \numberline {1}This-is-a-moving-argument\protect \nobreakspace  {}\protect \cite  {Lamport}}{\thepage }}}

because \protect was \@unexpandable@protect. That now goes forward to the 'raw' \write, which will do another exhaustive expansion with \protect as \noexpand, and the content we want will transfer to the .aux file, including the expanded page number.

Case five, as noted, is not used so is removed!


Worth noting is that all of this cleverness predates e-TeX: today one can make macros \protected and they don't expand inside a \write or \edef, and life is a lot easier.

  • The [space] in \cite[space] and \nobreakspace[space] isn't literal, right? It is a meta term that you used to indicating a single space character (input code point 32), rather than the 7 character string [-s-p-a-c-e-], right? – Evan Aad Jun 28 '17 at 7:15
  • 1
    @EvanAad Yes, that's why I wrote it in square brackets :) I should probably hunt about for a 'printable space' character ... – Joseph Wright Jun 28 '17 at 7:25
  • Thanks. What's \protected@write? – Evan Aad Jun 28 '17 at 8:32
  • @EvanAad I've included the definition in my answer: it's a LaTeX wrapper around \write which applies the 'protect' mechanism. – Joseph Wright Jun 28 '17 at 8:58
  • 1
    @EvanAad If you use a 'raw' \write then the 'protect' mechanism doesn't operate (it needs set up), and \protect has the value \relax (typesetting). So \cite[space] is just expanded. We then are back with the starting issue: you can't perform assignments inside an expansion context. To use the LaTeX2e mechanism you have to go via the defined interfaces, you can't use a 'raw' \write or \edef (etc.). See also my point about e-TeX: that's engine-level so can be used inside a 'raw' \write (try \protected\edef\cite{\unexpanded\expandafter{\cite}}. – Joseph Wright Jun 28 '17 at 9:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.