20

This question led to a new package:
newenviron

I want to define some environments, which place their content into multiple different other environments. To access the contents I used the environ package, which introduces the \BODY command. However at some point pdflatex seemed to be stuck in an infinite loop.

I reduced it to a minimal example:

\documentclass{article}
\usepackage{environ}

\NewEnviron{assertion}{Assertion: \BODY}
\NewEnviron{outerassertion}{\begin{assertion}\BODY\end{assertion}}

\begin{document}

\begin{assertion}
test
\end{assertion}

\begin{outerassertion}
test2
\end{outerassertion}
\end{document}

Any ideas how to work around this problem? I guess, the problem is with the \BODY command since everything works fine as long as I use it in only one of the new environments.

Werner
  • 603,163
grackkle
  • 549

2 Answers2

19

As you suspected you are getting a clash on \BODY essentially the outer environment does

  \def\BODY{\BODY}

which puts TeX in a loop, you can do

\documentclass{article}
\usepackage{environ}

\NewEnviron{assertion}{Assertion: \BODY}{}
\NewEnviron{outerassertion}{\let\xBODY\BODY\begin{assertion}\xBODY\end{assertion}}{}

\begin{document}



\begin{assertion}
test
\end{assertion}

\begin{outerassertion}
test2
\end{outerassertion}
\end{document}

Although in this case it would be simpler and more efficient to use a standard \newenvironment definition which would avoid the problem completely.

David Carlisle
  • 757,742
16

David has given a simple, commonsense solution, but your question has led to a package called newenviron, which may be used without a trick on the part of the user. \envbody and \<env name>body can be used to apply some code to the body of an environment.

\begin{filecontents*}{newenviron.sty}
% Collect environment body in macros \envbody and \<env name>body.
\@ifpackageloaded{catoptions}{}{\RequirePackage{catoptions}[2011/12/12]}
\UseNormalCatcodes
\StyleFilePurpose{Collect and execute environment body (AM)}
\StyleFileRCSInfo
$Id: newenviron.sty,v 1.0 2013/03/08 09:00:00 Ahmed Musa Exp $
\ProvidesPackage{newenviron}[\StyleFileInfo]
\NeedsTeXFormat{LaTeX2e}[2011/06/27]
\cptnewvariables{toks}[nenv@]{temptoks}
\new@def\nenv@gobbletomarker#1\nenv@endmarker{}
\new@def*\nevn@quark{}
\new@def*\AlwaysTrimEnvEntries{\global\nenv@alwaystrimtrue}
\new@def*\nenv@trimspace{%
  \ifdefboolTF{nenv@alwaystrim}\cpttrimspace\unexpanded
}
\new@def*\nenv@everybegin@hook{}
\robust@def*\EveryBeginOfEnvironment#1{%
  \xifinsetTF
  {\detokenize{\nevn@quark#1\nevn@quark}}
  {\cptoxdetok\nenv@everybegin@hook}{}{%
    \edef\nenv@everybegin@hook{%
      \expandcsonce\nenv@everybegin@hook
      \noexpand\nevn@quark\unexpanded{#1}\noexpand\nevn@quark
    }%
  }%
}
\new@def*\nenv@everyend@hook{}
\robust@def*\EveryEndOfEnvironment#1{%
  \xifinsetTF
  {\detokenize{\nevn@quark#1\nevn@quark}}
  {\cptoxdetok\nenv@everyend@hook}{}{%
    \edef\nenv@everyend@hook{%
      \expandcsonce\nenv@everyend@hook
      \noexpand\nevn@quark\unexpanded{#1}\noexpand\nevn@quark
    }%
  }%
}
\EveryEndOfEnvironment{\@ignoretrue}
\robust@def*\nenv@appto#1#2{%
  \ifdefTF#1{%
    \edef#1{\expandcsonce#1\unexpanded{#2}}%
  }{%
    \edef#1{\unexpanded{#2}}%
  }%
}
%
% \newenviron<optional *>
%   {<name>}[<narg>][<default of 1st arg>]{<start-code>}{<end-code>}
%
% \renewenviron<optional *>
%   {<name>}[<narg>][<default of 1st arg>]{<start-code>}{<end-code>}
%
\robust@def*\newenviron{\cpt@starorlong\nenv@newenviron}
\robust@def*\nenv@newenviron#1{%
  \edef\cpt@tempa{\nenv@trimspace{#1}}%
  \cptexpandarg\cpt@testopt
    {\nenv@newenviron@a{\expandcsonce\cpt@tempa}}{0}%
}
\robust@def*\nenv@newenviron@a#1[#2]{%
  \cpt@ifbrack{\nenv@newenviron@b#1[#2]}{\nenv@newenviron@c{#1}{[#2]}}%
}
\robust@def*\nenv@newenviron@b#1[#2][#3]{\nenv@newenviron@c{#1}{[#2][{#3}]}}
\robust@def\nenv@newenviron@c#1#2#3#4{%
  \ifcsndefTF{#1}{}{\letcsntocsn{#1}{end#1}}%
  \aftercsname\new@command{#1}#2{%
    \edef\nenv@beforebody{\nenv@trimspace{#3}}%
    \nenv@everybegin@hook
    \nenv@collectbody
  }%
  \l@ngrel@x\csn@edef{end#1}{%
    \def\noexpand\cpt@prova{\nenv@trimspace{#4}}\noexpand\cpt@prova
    \noexpand\nenv@everyend@hook
  }%
}
\robust@def*\nenv@collectbody{%
  \begingroup
  \toks@{}%
  \everyeof{\end{EOF}\relax}%
  \nenv@collectbody@a
}
\robust@def\nenv@collectbody@a#1\end#2{%
  \nenv@temptoks{%
    \cptexpanded{%
      \toks@{%
        \the\toks@\nenv@trimspace{#1}%
        \noexpand\end{\expandcsonce\cpt@argofend}%
      }%
    }%
    \nenv@collectbody@a
  }%
  \edef\cpt@argofend{\cpttrimspace{#2}}%
  \ifcseqTF\cpt@argofend\@currenvir{%
    \def\cpt@tempa{}%
    \nenv@pushbegin#1\begin\end\nenv@endmarker
    \ifcsemptyTF\cpt@tempa{%
      \cptexpanded{\endgroup
        \csn@edef{\@currenvir body}{%
          \noexpand\unexpanded{\the\toks@\nenv@trimspace{#1}}%
        }%
        \letcstocsn\noexpand\envbody{\@currenvir body}%
        \unexpanded{%
          \nenv@beforebody\relax
          \ifdefboolTF{nenv@alwaystrim}\@ignoretrue\relax
        }%
        \noexpand\end{\cpt@argofend}%
      }%
    }{%
      \the\nenv@temptoks
    }%
  }{%
    \oifstrcmpTF{\cpt@argofend}{document}{%
      \expandafter\endgroup\expandafter
        \@checkend\expandafter{\cpt@argofend}%
    }{%
      \oifstrcmpTF{\cpt@argofend}{EOF}{%
        \expandafter\endgroup\expandafter
          \@checkend\expandafter{\cpt@argofend}%
      }{%
        \the\nenv@temptoks
      }%
    }%
  }%
}
\new@def\nenv@pushbegin#1\begin#2{%
  \expandafter\ifx\cpt@car#2x\car@nil\end
    \expandafter\@gobble
  \else
    \edef\cpt@prova{\cpttrimspace{#2}}%
    \ifx\cpt@prova\cpt@argofend
      \def\cpt@tempa{x}%
      \expandafter\expandafter\expandafter\nenv@gobbletomarker
    \else
      \expandafter\expandafter\expandafter\nenv@pushbegin
    \fi
  \fi
}
\robust@def*\renewenviron{\cpt@starorlong\nenv@renewenviron}
\robust@def*\nenv@renewenviron#1{%
  \edef\cpt@tempa{\nenv@trimspace{#1}}%
  \ifcsndefTF\cpt@tempa
    {}
    {\@latex@error{Environment #1 is undefined}\@ehd}%
  \letcsntocs\cpt@tempa\relax
  \letcsntocs{end\cpt@tempa}\relax
  \expandafter\nenv@newenviron\expandafter{\cpt@tempa}%
}
\XDeclareBooleanOption{alwaystrim}[true](nenv@){}{}
\XDeclareOption*{\@@warning{Unknown option '\CurrentOption' ignored}}
\XExecuteOptions{alwaystrim}
\XProcessOptions*\relax
\endinput
\end{filecontents*}

Example:

\documentclass{article}
\usepackage{newenviron,xcolor}

% \envbody will always work for 'unnested' environments:
%   \newenviron{assertion}{}{Assertion: \envbody}

% But if you intend to nest the environment, it will be safer to use \assertionbody:
\newenviron{assertion}{%
  \def\acmd##1{##1}% test <start-code>.
}{%
  \def\bcmd##1{##1}% test <end-code>.
  Assertion: \assertionbody
}
\newenviron{outerassertion}{%
  % Put any start code here.
}{%
  \textcolor{blue}{Outer assertion:} %
  \begin{assertion}\outerassertionbody\end{assertion}%
}

\begin{document}

% Just for testing:
\EveryEndOfEnvironment{\def\ccmd#1{#1}}

\begin{assertion}
  test
\end{assertion}
\endgraf\bigskip

\begin{outerassertion}
  test2
\end{outerassertion}
\endgraf\bigskip

\begin{assertion}
  Level-1 test.
  \begin{assertion}
  Another level-1 test.
  \end{assertion}
\end{assertion}
\endgraf\bigskip

% Another test. Note the use of \usename{env-1body} and \usename{env-2body}:
\newenviron{env-1}[2][blue]{%
  \fboxrule=#2\relax
  \cptdimdef\temp{.5\textwidth}%
  \endgraf\noindent
  \fcolorbox{#1}{gray!10}{\parbox{\temp}{\textcolor{#1}{\usename{env-1body}}}}%
}{}
\newenviron*{env-2}[1][black]{%
  \noindent
  \fcolorbox{#1}{gray!30}{%
    \parbox{.7\textwidth}{%
      \leftskip=1cm
      \textcolor{#1}{\usename{env-2body}}%
    }%
  }%
}{%
  \def\testcmd##1{##1}% test <end-code>.
}

\begin{env-2}[red]
  Outer box\endgraf
  \def\tempa#1{***#1***}\tempa{aa}%
  \endgraf\vspace*{5mm}%
  \begin{env-1}[blue]{1pt}%
    Inner box\endgraf\vspace*{5mm}%
    \def\tempa#1{+++#1+++}\tempa{bb}%
  \end{env-1}%
  \begin{env-1}[brown]{4pt}%
    Inner box\endgraf\vspace*{5mm}%
    \def\tempa#1{---#1---}\tempa{cc}%
  \end{env-1}%
\end{env-2}

\end{document}

enter image description here

Werner
  • 603,163
Ahmed Musa
  • 11,742