2

I tried to make an environment that shows its contents if @showtrue and doesn't otherwise. When I try to run the MWE below, I get an error message:

! Extra }, or forgotten \endgroup. 
\endToShowOrNotToShow ->\egroup
                                \ignorespacesafterend 
l.24 \end{ToShowOrNotToShow}

My MWE:

\documentclass{article}
\makeatletter
\newif\if@show
\def\to@show#1{#1}
\def\not@to@show#1{}
\newenvironment{ToShowOrNotToShow}{%
    \if@show%
        \to@show\bgroup%
            \hfill\\ {\bfseries Behold: }%
    \else%
        \not@to@show\bgroup%
    \fi%
    \ignorespaces%
    }%
    {%
    \egroup%
    \ignorespacesafterend%
    }
\makeatother
\begin{document}
Normal text
\begin{ToShowOrNotToShow}
    Text that is not necessarily shown.
\end{ToShowOrNotToShow}
Rest
\end{document}

I would expect this to expand to:

Normal text
\not@to@show\bgroup\ignorespaces Text that is not necessarily shown.\egroup\ignorespacesafterend

Which, to my best understanding, should do the same as

Normal text
\not@to@show{Text that is not necessarily shown.}

Where is the flaw in my reasoning?

  • You can't use \bgroup/\egroup like this see e.g. tex.stackexchange.com/questions/27972/bgroup-and-egroup. Check the comment package or the environ package instead. – Ulrike Fischer Jan 16 '18 at 10:24
  • I've been looking at some other packages and the solution-environment of the exam-package is defined using \bgroup/\egroup. How come it works there? – gebruiker Jan 16 '18 at 10:51
  • \bgroup and \egroup are only equivalent to { and } when it comes to plainTeX boxes. This is because the boxed content is processed while collected and not just collected as with macro arguments. In order for your code to work you need to define \not@to@show to be a savebox command which then discards the content. – Martin Scharrer Jan 16 '18 at 11:03
  • For example \def\not@to@show{\adjustbox{discard}} would do the trick, as it reads its 'argument' as box. (Needs adjustbox package, add varwidth=\linewidth as first argument if you have paragraph content). You need something similar for \to@show. – Martin Scharrer Jan 16 '18 at 11:05
  • @MartinScharrer I've used what you said to create the solution I posted. Would you care to comment? – gebruiker Jan 16 '18 at 11:38
2

(Answering the “Where is the flaw in my reasoning?” part for understanding…)

Debugging

First, let's debug what's going on. Your MWE defines a \newenvironment, which does one thing or another based on the \newif called \if@show. In the example, the error comes from the “false” path, so we can specialize the MWE to consider just that part (and also leave out all the catcode and \ignorespaces stuff, for simplicity). So your MWE is further minimized to:

\documentclass{article}
\def\notToShow#1{}
\newenvironment{ToShowOrNotToShow}{%
    \notToShow\bgroup%
}%
{%
    \egroup%
}
\begin{document}
Normal text
\begin{ToShowOrNotToShow}
    Text that is not necessarily shown.
\end{ToShowOrNotToShow}
Rest
\end{document}

which exhibits the same error as in the question. Going further, we can actually do what we imagine \newenvironment does, namely put its two arguments before and after the stuff that goes inside when the environment is used. (This is exactly what you did in the question where you wrote “I would expect this to expand to…”.) So:

\documentclass{article}
\def\notToShow#1{}
\begin{document}
Normal text
\notToShow\bgroup
    Text that is not necessarily shown.
\egroup
Rest
\end{document}

Now the error is slightly different (if we're interested we can come back later to why the error is different), but an error nevertheless:

! Too many }'s.
l.7 \egroup

It may be already clear what the error is, but if not you can add:

\tracingonline=1
\tracingmacros=1

(or just \tracingall and search in the profuse output) to see, in the TeX output:

\notToShow #1->
#1<-\bgroup 

which shows what's happening: the macro \notToShow gets the token \bgroup as its argument (#1), and then (per its definition) replaces the token with nothing. Later, when the \egroup is encountered, it does not correspond to any earlier \bgroup (recall that \notToShow consumed the token), which explains the error.

Advice

After learning a little bit about TeX and writing macros and all that, I find there's a trick to it that seems to help. The trick is to not start writing out the definition of a macro, the way you would when writing say a function in another language. Instead, recall that macros are simply text (or token) substitution (i.e. they work by expansion), and write out the expanded part first. Make sure it works. Only then start writing the macros, purely as a shortcut to avoid typing out the whole thing each time. (That was after all the original purpose of including the feature in TeX.)

(Actually the even bigger lesson I learned is to avoid writing macros at all if possible, and instead do text processing in an external program or LuaTeX callbacks….)

In this case, if you started with the fully expanded out

\toShow\bgroup
    Text that is not necessarily shown.
\egroup

and

\notToShow\bgroup
    Text that is not necessarily shown.
\egroup

(for the if and else cases respectively), you would have already encountered an issue in the else case. (You don't encounter an issue in the if case because \def\toShow#1{#1} applied to \bgroup (not as intended) simply puts back \bgroup, so the \egroup is not unexpected.)

Solution

For macro expansion, instead of invoking the macro with \foo {bar}, you cannot write \foo \bgroup bar\egroup. TeX doesn't work like that; it looks for an explicit { or for any character with that catcode (1). (In fact, if it allowed \bgroup and \egroup here, then that would defeat their purpose, and you could just directly write { or } everywhere.)

But \bgroup and \egroup work for many other TeX primitives, and one that you can use here (as suggested in the comments) is \vbox.

So, to write the macro, first verify that you have what you want, when fully expanded out:

\documentclass{article}
\begin{document}

% To show the text, just insert it into a vbox.
Normal text
\vbox\bgroup
    Text that is not necessarily shown.
\egroup
Rest

% To hide the text, assign to box 0 and don't use it.
Normal text
\setbox0\vbox\bgroup
    Text that is not necessarily shown.
\egroup
Rest

\end{document}

output

(Actually this does not look desirable, but it more or less matches what you said in the other answer with “This does what I want it to do”. I'm dropping the “Behold” and the \ignorespaces etc. for simplicity, but imagine you have the appropriate things here.)

Next introduce the \ifs:

\documentclass{article}
\begin{document}

\newif\ifshow
\showtrue % Try with and without this

Normal text
\ifshow
  \vbox\bgroup
\else
  \setbox0\vbox\bgroup
\fi
    Text that is not necessarily shown.
\egroup
Rest

\end{document}

Finally, only once you're satisfied, define your environment:

\documentclass{article}

\newif\ifshow

\newenvironment{ToShowOrNotToShow}{%
\ifshow
  \vbox\bgroup
\else
  \setbox0\vbox\bgroup
\fi
}{%
  \egroup
}

\begin{document}

\showtrue % Try with and without this

Normal text
\begin{ToShowOrNotToShow}
    Text that is not necessarily shown.
\end{ToShowOrNotToShow}
Rest

\end{document}

and here you can add the \ignorespacesafterend and other stuff specific to LaTeX environments.

1

Based on the comments of Martin Scharrer I made the example below. I create and print the box in seperate steps. Also I no longer need \not@to@show and \to@show.

\documentclass{article}
\makeatletter
\newif\if@show
\newenvironment{ToShowOrNotToShow}{%
\if@show%
    \vbox\bgroup%
        \hfill\\ {\bfseries Behold: }%
\else%
    \setbox0\vbox\bgroup%
\fi%
\ignorespaces%
}%
{%
\egroup%
\ignorespacesafterend%
}
\makeatother
\begin{document}
Normal text
\begin{ToShowOrNotToShow}
    Text that is not necessarily shown.
\end{ToShowOrNotToShow}
Rest
\end{document}

This does what I want it to do.

  • You could leave the \setbox0 and \box0 out. If you just write \vbox\bgroup .. \egroup you get the same output without storing it as the box is placed directly. – Martin Scharrer Jan 16 '18 at 11:41
  • When \@showtrue this works, but if I leave \setbox0 out in the \else part the content gets printed immediately. The new edit is what I have now. – gebruiker Jan 16 '18 at 11:48
  • Yes, I meant to leave \setbox0 out in the true clause, not in both. – Martin Scharrer Jan 16 '18 at 11:51

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.