5

I'm trying to write myself a package, similar to the exercise package, so I can prepare problem sets for my students. I wish to delay the output of problems and/or solutions until a suitable place in the document has been reached. I have something that sort of works but it's kind of clunky. For example, this works pretty well:

\usepackage{environ}
\NewEnviron{testb}{\global\expandafter\let\csname bar\endcsname\BODY}

I use \csname because the names are dynamically generated. This doesn't work so well if the command is anything other than \BODY. For example, if I do \let\bar{\BODY}, latex has an aneurysm (it binds \bar to just {).

There is an alternate way of doing this, which is:

\NewEnviron{testc}{\global\expandafter\edef\csname foo\endcsname{\BODY}}

This sort of works. The following thing works: \begin{testc}hi\end{testc}, but this gives LaTeX an aneurysm: \begin{testc}\bf hi\end{testc}. (The error message is \incomplete). I tried to debug it myself but I get hopelessly tangled up in packages that I don't understand. If you do it with \tiny instead of \bf you get the wildly different LaTeX error message: ! TeX capacity exceeded, sorry [input stack size=5000].

How can I squirrel away for later, not just \BODY, but some sophisticated combination of \BODY and other stuff, while having it robust to what's between the begin and end?

Edit: ok multiple solutions below

  • 1
    Why not use \def? \let does what it should do: use the the next token. – TeXnician Mar 19 at 13:02
  • If you mean this: \NewEnviron{testc}{\global\expandafter\def\csname foo\endcsname{\BODY}}, I get: ! Undefined control sequence. \foo ->\BODY – Sébastien Loisel Mar 19 at 13:09
  • 1
    I'm not sure why you don't like \global\expandafter\let\csname bar\endcsname\BODY: it's entirely normal TeX. – Joseph Wright Mar 19 at 13:31
  • I started writing a class file to prepare conference programmes using the same sort of "save text for later" features. github.com/scmbradley/schedule I have just realised that this was six years ago. My god. Six years!? – Seamus Mar 19 at 13:47
  • You should definitely use \let with \BODY, \def with anything whose definition doesn't change, and \edef with things that do change (and you don't mind expanding them). – John Kormylo Mar 19 at 16:05
5

With \unexpanded you can avoid worrying about \protected@xdef.

\documentclass{article}
\usepackage{environ}

\NewEnviron{exercise}{%
  \xdef\savedexercises{%
    \unexpanded\expandafter{\savedexercises}%
    \noexpand\begin{printedexercise}%
    \unexpanded\expandafter{\BODY}%
    \noexpand\end{printedexercise}%
  }%
}
\newcommand{\printexercises}{%
  \savedexercises
  \gdef\savedexercises{}%
}
\newcommand{\savedexercises}{}

\newtheorem{printedexercise}{Exercise}

\begin{document}

Here we talk about addition and show that $1+1=2$.

\begin{exercise}
Compute $1+2$
\end{exercise}

Here we talk about integrals.

\begin{exercise}
Compute the following integrals:
\begin{itemize}
\item $\displaystyle\int_0^x e^{-t^2}\,dt$

\item $\displaystyle\int_1^x \frac{e^t}{t}\,dt$, for $t>0$.
\end{itemize}
\end{exercise}

Now we can print the exercises.

\printexercises

\end{document}

I used \newtheorem just for the example.

enter image description here

With xparse released 2019-03-05 or later:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentEnvironment{exercise}{+b}
 {
  \tl_gput_right:Nn \g_loisel_exercises_tl
   {
    \begin{printedexercise}
    #1
    \end{printedexercise}
   }
 }{}

\NewDocumentCommand{\printexercises}{}
 {
  \tl_use:N \g_loisel_exercises_tl
  \tl_gclear:N \g_loisel_exercises_tl
 }

\tl_new:N \g_loisel_exercises_tl

\ExplSyntaxOff

\newtheorem{printedexercise}{Exercise}

\begin{document}

Here we talk about addition and show that $1+1=2$.

\begin{exercise}
Compute $1+2$
\end{exercise}

Here we talk about integrals.

\begin{exercise}
Compute the following integrals:
\begin{itemize}
\item $\displaystyle\int_0^x e^{-t^2}\,dt$

\item $\displaystyle\int_1^x \frac{e^t}{t}\,dt$, for $t>0$.
\end{itemize}
\end{exercise}

Now we can print the exercises.

\printexercises

\end{document}
6

If you want BODY to be saved then add stuff dynamically, you are likely best using two macros:

\documentclass{article}
\usepackage{environ}
\NewEnviron{testb}{%
  \global\expandafter\let\csname bar\endcsname\BODY
  \expandafter\xdef\csname barplus\endcsname{%
    \expandafter\noexpand\csname bar\endcsname
    \noexpand\bf Hi
  }%
}
\begin{document}

\begin{testb}
  \bfseries
  Hi
\end{testb}
\show\barplus
\end{document}

If you want to avoid using \BODY you could use xparse

\documentclass{article}
\usepackage{xparse}
\NewDocumentEnvironment{testb}{+b}{\expandafter\gdef\csname bar\endcsname{#1}}{}
\begin{document}

\begin{testb}
  \bfseries
  Hi
\end{testb}
\show\bar
\end{document}
  • This needs TL'19 or up-to-date MiKTeX: this is a new feature in xparse. – Joseph Wright Mar 19 at 13:34
  • Ah, thanks for this. By the way, I just tried this: \NewEnviron{testc}{\expandafter\protected@xdef\csname foo\endcsname{\BODY}} , and it seems to work. Is there any situation where this would break again? – Sébastien Loisel Mar 19 at 14:42
  • @SébastienLoisel Well if you have anything that's not LaTeX2e robust, yes, but that is nowadays quite a low risk. – Joseph Wright Mar 19 at 14:43
  • What's an example of something not LaTeX2e robust? – Sébastien Loisel Mar 19 at 15:04
  • Nevermind, I tried with \begin{itemize}...\end{itemize} and it broke. – Sébastien Loisel Mar 19 at 15:14
0

A trick for triggering expansion is using \romannumeral:

When due to \romannumeral (La)TeX does gather together a sequence of digits trailed by a space as the number which it has to convert, expandable tokens get expanded.

When in the end a number is gathered together which is not positive, as the result of the conversion (La)TeX will not deliver any token at all.

Thus one can nicely (ab?)use \romannumeral for triggering a lot of expansion-work and flipping-arguments-around-work as long as one ensures that in the end \romannumeral will not find a positive number.

Here is a variant of egreg's answer which does with \romannumeral and \exchange instead of \xdef and \unexpanded.

\documentclass{article}
\usepackage{environ}

\newcommand\exchange[2]{#2#1}

\NewEnviron{exercise}{%
  \expandafter\gdef\expandafter\savedexercises\expandafter{%
    \romannumeral0\expandafter\exchange\expandafter{\BODY}{%
      \exchange{ }{\expandafter}\savedexercises
      \begin{printedexercise}%
    }%
    \end{printedexercise}%
  }%
}
\newcommand{\printexercises}{%
  \savedexercises
  \gdef\savedexercises{}%
}
\newcommand{\savedexercises}{}

\newtheorem{printedexercise}{Exercise}

\begin{document}

Here we talk about addition and show that $1+1=2$.

\begin{exercise}
Compute $1+2$
\end{exercise}

Here we talk about integrals.

\begin{exercise}
Compute the following integrals:
\begin{itemize}
\item $\displaystyle\int_0^x e^{-t^2}\,dt$

\item $\displaystyle\int_1^x \frac{e^t}{t}\,dt$, for $t>0$.
\end{itemize}
\end{exercise}

Now we can print the exercises.

\printexercises

\end{document}

enter image description here

If you wish to wrap the name of the macro that is to be defined into \csname..\endcsname, i.e., if you wish to use \csname savedexercises\endcsname rather than \savedexercises, you can take advantage of the fact that (La)TeX does expand expandable tokens while due to \csname gathering the name of a control sequence token and hereby searching for the matching \endcsname:

\documentclass{article}
\usepackage{environ}

\newcommand\exchange[2]{#2#1}

\NewEnviron{exercise}{%
  \expandafter\gdef\csname savedexercises\expandafter\endcsname\expandafter{%
    \romannumeral0\expandafter\exchange\expandafter{\BODY}{%
      \exchange{ }{\expandafter\expandafter\expandafter}\csname savedexercises\endcsname
      \begin{printedexercise}%
    }%
    \end{printedexercise}%
  }%
}
\newcommand{\printexercises}{%
  \csname savedexercises\endcsname
  \expandafter\gdef\csname savedexercises\endcsname{}%
}
\expandafter\newcommand\expandafter{\csname savedexercises\endcsname}{}

\newtheorem{printedexercise}{Exercise}

\begin{document}

Here we talk about addition and show that $1+1=2$.

\begin{exercise}
Compute $1+2$
\end{exercise}

Here we talk about integrals.

\begin{exercise}
Compute the following integrals:
\begin{itemize}
\item $\displaystyle\int_0^x e^{-t^2}\,dt$

\item $\displaystyle\int_1^x \frac{e^t}{t}\,dt$, for $t>0$.
\end{itemize}
\end{exercise}

Now we can print the exercises.

\printexercises

\end{document}

enter image description here

Be aware that with the approaches presented by now you cannot use \printexercises for having exercises occur in arbitrary places. You can have exercises occur only in places of the document which in the source correspond to places behind the exercise-environments.

Perhaps an environment which does read its content under verbatim-catcode-régime for unexpanded-writing it to .aux-file in a way where from the .aux-file it gets read back under verbatim-catcode-régime also for defining a macro where \scantokens will be applied to, and thus some sort of re-implementation of the \label-\ref-mechanism or of the \tableofcontents-mechanism for verbatimized stuff might make it possible to make exercises printable throughout the entire document.

Implementing such a mechanism might be a nice challenge. But before taking that into consideration at all, exact information is needed on the intended usage and the desired "user-interface", i.e., what additional things you wish to be able to specify in which ways, etc, ...

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.