2

I've been looking for duplicates for a bit, can't seem to find one so here goes nothing. I'm trying to create a generic environment that can receive an environment name as an argument and generate that specific environment. If you are wondering why do something so apparently useless, the reason is that I need to redefine a lot of environments so the quickest way I could think of was to start with a generic template. As it turns out, this is already a challenge in and of itself:

\documentclass{article}

\newenvironment{mynewenv}[1]
{
    {\begin{#1}}
    {\end{#1}}
}

\begin{document}
    \begin{mynewenv}{math}
        1+1
    \end{mynewenv}
\end{document}

This MWE generates 2 errors:

  • Missing } inserted
  • Extra }, or forgotten $

which are not useful at all for debugging... what am I doing wrong?

2
  • 1
    note that some envs looks explicitly for the string \end{env}, and in this construction it is hidden. Additionally, that is not how \newenvironment work, it takes two {} after the [1], not one like you have here.
    – daleif
    Feb 21, 2022 at 10:51
  • 1
    Why exactly should a generic env be a good idea here? There are many cases where this will never work because of the way the original env works.
    – daleif
    Feb 21, 2022 at 11:42

3 Answers 3

3

Two issues: Correct use of \newenvironment is

\newenvironment{mynewenv}[<args>][<opt>]{<begdef>}{<enddef>}

and #1 cannot be used in <enddef>.

This works, but may fail in other circumstances (see @Rmanos's comment):

\documentclass{article}

\newenvironment{mynewenv}[1]
  {\def\envname{#1}\begin{#1}}
  {\expandafter\end\expandafter{\envname}}

\begin{document}
\begin{mynewenv}{math}
  1+1
\end{mynewenv}
\end{document}
17
  • 1
    You can use #1 in the end part if you use \NewDocumentEnvironment which is now in the kernel. BTW: does this work with align?
    – daleif
    Feb 21, 2022 at 10:57
  • Thanks, I was sure there would be a smarter way ;)
    – AlexG
    Feb 21, 2022 at 10:58
  • 1
    This way the order in which ending-commands that underlie the environments and hooks at end of environment are carried out is reversed. Feb 21, 2022 at 11:01
  • @UlrichDiez that is a really good point. Since many "normal" users don't really know about the hooks (yet), this is often overlooked.
    – daleif
    Feb 21, 2022 at 11:15
  • @UlrichDiez Not sure what you mean by this. Could you please explain?
    – AlexG
    Feb 21, 2022 at 11:19
2

You want to save the current environment name.

\documentclass{article}

\makeatletter
\newenvironment{mynewenv}[1]
  {\def\mynewenv@currenvir{#1}\csname\mynewenv@currenvir\endcsname}
  {\csname end\mynewenv@currenvir\endcsname}
\makeatother

\begin{document}

\begin{mynewenv}{math}
  1+1
\end{mynewenv}

\begin{mynewenv}{enumerate}
  \item a
  \item b
  \begin{mynewenv}{itemize}
    \item c
    \item d
  \end{mynewenv}
  \item e
\end{mynewenv}

\end{document}

enter image description here

But it's a bad idea anyway.

1
  • I thought about this too, but if i get things right, this way only the hooks belonging to mynewenv but not the hooks belonging to math/enumerate/itemize are carried out. (However, with my way of using things, that would probably be irrelevant.) Feb 21, 2022 at 13:52
2

I take this question as an exercise in playing with category codes and macro expansion.

But honestly, I don't see much point in writing
\begin{mynewenv}{EnvironmentWhichIActuallyWant}<Arguments>
instead of directly writing
\begin{EnvironmentWhichIActuallyWant}<Arguments>.

Be that as it may.


You attempt to have an environment "mynewenv" carry out another environment which is called "math".

A problem with this is:

"mynewenv" starts "math". Then in the .tex-input the \end{...}-command for "mynewenv" is encountered which in turn does the \end{...}-command for "math".

This way the order in which ending-commands for "math" and "mynewenv" are found, and thus the order in which underlying ending-commands of these environments and hooks at the end of these environments are carried out, is reversed in relation to the order in which these things should be carried out.

This can be made obvious by patching the \begin- and \end-command so that these commands also deliver a message about which environment is currently carried out:

The following example delivers messages in the order

BEGINNING ENVIRONMENT: document
BEGINNING ENVIRONMENT: mynewenv 
BEGINNING ENVIRONMENT: math 
ENDING ENVIRONMENT: mynewenv 
ENDING ENVIRONMENT: math 
ENDING ENVIRONMENT: document 

But messages should be delivered in the order

BEGINNING ENVIRONMENT: document
BEGINNING ENVIRONMENT: mynewenv 
BEGINNING ENVIRONMENT: math 
ENDING ENVIRONMENT: math 
ENDING ENVIRONMENT: mynewenv 
ENDING ENVIRONMENT: document 
\documentclass{article}

\makeatletter
\newenvironment{mynewenv}[1]{\begin{#1}}%
                            {\expandafter\end\expandafter{\@currenvir}}%
%%
\expandafter\def\csname begin \endcsname#1{%
  \message{^^JBEGINNING ENVIRONMENT: \detokenize{#1}}%
  \UseHook{env/#1/before}%
  \@ifundefined{#1}%
               {\def\reserved@a{\@latex@error{Environment #1 undefined}\@eha}}%
               {\def\reserved@a{\def\@currenvir{#1}\edef\@currenvline{\on@line}%
                                \@execute@begin@hook{#1}\csname #1\endcsname}}%
  \@ignorefalse
  \begingroup
  \@endpefalse
  \reserved@a
}%
%%
\expandafter\def\csname end \endcsname#1{%
  \message{^^JENDING ENVIRONMENT: \detokenize{#1}}%
  \romannumeral
  \IfHookEmptyTF{env/#1/end}%
                {\expandafter\z@}%
                {\z@\UseHook{env/#1/end}}%
  \csname end#1\endcsname
  \@checkend{#1}%
  \expandafter\endgroup\if@endpe\@doendpe\fi
  \UseHook{env/#1/after}%
  \if@ignore \@ignorefalse\ignorespaces\fi 
}%
\makeatother


\begin{document}
    \begin{mynewenv}{math}
        1+1
    \end{mynewenv}
\end{document}

What you can do is have "mynewenv" collect everything verbatimized until reaching \end{mynewenv} and via \scantokens spit out either
\end{mynewnv}\begin{#1}<collected stuff>\end{#1} or
\begin{#1}<collected stuff>\end{#1}\end{mynewnv}.

So some time ago I wrote my own interface

\UD@GatherCodeSnippetLoop{<non-special characters gathered so far>}%
                         {<subset of name of environment gathered so far>}%
                         {<tokens to apply to stuff gathered>}%
                         <non-special characters>\end{<name of environment>}

which switches to verbatim-catcode-régime and gathers everything in catcode-12-régime until encountering \end{<name of environment>}, switches back to normal catcode-régime, passes the tokens gathered as a brace-nested undelimited argument behind <tokens to apply to stuff gathered> and carries out \end{<name of environment>}.

Within \newenvironment you can use it as follows:

\newenvironment{foobar}[<args>][<opt-default>]{%
  <stuff using parameters according to <args> >
  \UD@GatherCodeSnippetLoop{<non-special characters gathered so far>}%
                           {<subset of name of environment gathered so far>}%
                           {<tokens to apply to stuff gathered>}%
}{%
  <code at end of environment>
}%

The environment delivers something similar to

<tokens to apply to stuff gathered>{<stuff gathered in catcode-12-régime>}

<tokens to apply to stuff gathered> can consist of directives for prepending and appending more stuff and passing things to \scantokens. This can be tricky because with \scantokens you need to ensure that things get re-tokenized correctly.

<tokens to apply to stuff gathered> must also trigger carrying out \end{foobar} as the sequence \end{foobar} occurring in the .tex-input-file is swallowed by \UD@GatherCodeSnippetLoop as the marker which denotes where to end gathering material in verbatim-category-code-régime.

Environments that use \UD@GatherCodeSnippetLoop have some shortcomings:

  • They cannot be nested!
  • They cannot be called by macros/\the-expansion of token-registers.
\makeatletter
%%///////////// Code for interface \UD@GatherCodeSnippetLoop and ///////////////
%%///////////// and all suplementary things ////////////////////////////////////
%%===============================================================================
%% End \romannumeral-driven expansion safely:
%%===============================================================================
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%===============================================================================
%% Check whether argument is empty:
%%===============================================================================
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\expandafter\UD@stopromannumeral\@secondoftwo}{%
  \expandafter\UD@stopromannumeral\@firstoftwo}%
}%
\newcommand\UD@exchange[2]{#2#1}%
%%===============================================================================
%% Loop for replacing catcode-10-spaces by catcode-12-spaces
%%===============================================================================
\begingroup
\def\UD@ReplaceSpace#1{%
  \endgroup
  \@ifdefinable\UD@ReplaceSpace{\long\def\UD@ReplaceSpace##1 {##1#1}}%
}%
\catcode`\ =12\relax
\UD@ReplaceSpace{ }%
\@ifdefinable\UD@GobbleToSpace{\long\def\UD@GobbleToSpace#1 {}}%
\newcommand\UD@SpaceReplaceLoop[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\UD@GobbleToSpace#1 }{%
    \UD@stopromannumeral#1%
  }{%
    \expandafter\UD@SpaceReplaceLoop\expandafter{\UD@ReplaceSpace#1}%
  }%
}%
%%===============================================================================
%% Check if string of non-special character-tokens is substring
%% of another string of non-special character-tokens:
%%===============================================================================
%% \UD@checkstringsubsetof{<possible substring>}{<string>}%
%%   {<tokens if <possible substring> and <string> are equal>}%
%%   {<tokens if <possible substring> is strict subset of <string>>}%
%%   {<<tokens if <possible substring> is not subset of <string>>}%
%% The length of <possible substring> must not exceed the length of <string>!!!
\newcommand\UD@checkstringsubsetof[5]{%
  \UD@checkstringsubsetofloop#1\relax#2\relax{{#3}{#4}}{#5}%
}%
\@ifdefinable\UD@checkstringsubsetofloop{%
  \def\UD@checkstringsubsetofloop#1#2\relax#3#4\relax{%
    \if\string#1\string#3\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {%
      \ifx\relax#2\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
      {%
        \ifx\relax#4\relax\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
        {\expandafter\@firstoftwo\@firstoftwo}{\expandafter\@secondoftwo\@firstoftwo}%
      }%
      {\UD@checkstringsubsetofloop#2\relax#4\relax}%
    }{\expandafter\@secondoftwo}%
  }%
}%
%%===============================================================================
%% Gather non-special character-tokens until encountering
%% \end{<name of environment>} :
%%===============================================================================
%% \UD@GatherCodeSnippetLoop{<non-special characters gathered so far>}%
%%                          {<subset of name of environment gathered so far>}%
%%                          {<tokens to apply to stuff gathered>}%
%%                          <non-special characters>\end{<name of environment>}
%%
%% Character-wise gathers <non-special characters>, replaces any non-space by its
%% \catcode-12-pendant. (Space is handled by giving it catcode 12 before applying 
%% the routine.) 
%% Then does 
%%  <tokens to apply to stuff gathered>{<non-special characters gathered so far>}
\newcommand\UD@GatherCodeSnippetLoop{%
  \begingroup
  \let\@tempa\@currenvir
  \@onelevel@sanitize\@tempa
  \expandafter\endgroup
  \expandafter\UD@@GatherCodeSnippetLoop
  \expandafter{\romannumeral\expandafter\UD@SpaceReplaceLoop\expandafter{\@tempa}}%
}%
\newcommand\UD@@GatherCodeSnippetLoop[1]{%
  \begingroup
  \let\do\@makeother % <- this and the next line switch to
  \dospecials        %    verbatim-category-code-régime.
  \do\^^I%
  \do\^^M%
  \begingroup
  \escapechar=-1\relax
  \edef\@tempa{\string\\end\string{#1\string}}%
  \expandafter\endgroup
  \expandafter\UD@@@GatherCodeSnippetLoop
  \expandafter{\@tempa}{#1}%
}%
\newcommand\UD@@@GatherCodeSnippetLoop[6]{%
  \UD@checkstringsubsetof{#4#6}{#1}{%
    \endgroup#5{#3}%
  }{%
    \expandafter\UD@exchange\expandafter{\expandafter{%
      \romannumeral
      \expandafter\UD@exchange\expandafter{%
        \romannumeral\UD@StringifyIfNotSpace{#6}%
      }{\UD@stopromannumeral#4}%
    }}{\UD@@@GatherCodeSnippetLoop{#1}{#2}{#3}}{#5}%
  }{%
    \UD@checkstringsubsetof{#6}{#1}{%
      #5{#3#4}%
    }{%
      \expandafter\UD@exchange\expandafter{\expandafter{%
         \romannumeral\UD@StringifyIfNotSpace{#6}%
      }}{\UD@@@GatherCodeSnippetLoop{#1}{#2}{#3#4}}{#5}%
    }{%
      \expandafter\UD@exchange\expandafter{\expandafter{%
        \romannumeral
        \expandafter\UD@exchange\expandafter{%
          \romannumeral\UD@StringifyIfNotSpace{#6}%
        }{\UD@stopromannumeral#3#4}%
      }}{\UD@@@GatherCodeSnippetLoop{#1}{#2}}{}{#5}%
    }%
  }%
}%
\newcommand\UD@StringifyIfNotSpace[1]{%
  \if\string#1 \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
  {\UD@stopromannumeral}{\expandafter\UD@stopromannumeral\string}#1%
}%
%%///////////// End of code for interface \UD@GatherCodeSnippetLoop /////////////
\makeatother

\makeatletter
%%///////////// Code for environment mynewenv ///////////////////////////////////
\newenvironment{mynewenv}[1]{%
  \UD@GatherCodeSnippetLoop{}{}{\wrapmynewenv{#1}}%
}{}%
\begingroup
\def\wrapmynewenv#1#2#3{%
  \endgroup
  \newcommand\wrapmynewenv[2]{%
    \begingroup
    \newlinechar=\endlinechar
    \scantokens\expandafter{\string\endgroup#1{##1}##2#2{##1}#3}%
    \end{mynewenv}%
  }%
}%
\catcode`\%=12\relax
\expandafter\expandafter\expandafter\wrapmynewenv
\expandafter\expandafter\expandafter{\expandafter
\string\expandafter\begin\expandafter}\expandafter
{\string\end}{%}%
%%///////////// End of code for environment mynewenv /////////////////////////////
\makeatother

\documentclass{article}

%\makeatletter
%\expandafter\def\csname begin \endcsname#1{%
%  \message{^^JBEGINNING ENVIRONMENT: \detokenize{#1}}%
%  \UseHook{env/#1/before}%
%  \@ifundefined{#1}%
%               {\def\reserved@a{\@latex@error{Environment #1 undefined}\@eha}}%
%               {\def\reserved@a{\def\@currenvir{#1}\edef\@currenvline{\on@line}%
%                                \@execute@begin@hook{#1}\csname #1\endcsname}}%
%  \@ignorefalse
%  \begingroup
%  \@endpefalse
%  \reserved@a
%}%
%%
%\expandafter\def\csname end \endcsname#1{%
%  \message{^^JENDING ENVIRONMENT: \detokenize{#1}}%
%  \romannumeral
%  \IfHookEmptyTF{env/#1/end}%
%                {\expandafter\z@}%
%                {\z@\UseHook{env/#1/end}}%
%  \csname end#1\endcsname
%  \@checkend{#1}%
%  \expandafter\endgroup\if@endpe\@doendpe\fi
%  \UseHook{env/#1/after}%
%  \if@ignore \@ignorefalse\ignorespaces\fi 
%}%
%\makeatother

\begin{document}

\noindent
\begin{mynewenv}{math}
1+1
\end{mynewenv}


\begin{mynewenv}{verbatim*}
#1 $_ {^ blebb
\end{mynewenv}

\end{document}

enter image description here

6
  • Thanks, I see the point. But isn't reverse order of hook execution a general problem of \newenvironment{}{}?
    – AlexG
    Feb 21, 2022 at 13:31
  • @AlexG Syntax is: \newenvironment{environment-name}[amount of args][optional's default]{<Begin>}{<End>}. If <Begin> contains \begin{WhatsoeverOtherEnvironment} and <End> contains \end{WhatsoeverOtherEnvironment}, then reverse order of hook execution is a problem. But even before the hooks existed it was frowned upon to use <Begin> or <End> to call further instances of \begin{...} or \end{...}. It was advised to call the macros underlying the environments directly if possible.E.g., \newenvironment{FOO}{\BAR}{\endBAR} instead of \newenvironment{FOO}{\begin{BAR}}{\end{BAR}}. Feb 21, 2022 at 13:44
  • But Lamport gives an example where \begin{itemize} and \begin{itemize} are used in <Begin> and <End>, respectively. (And another one, using \begin{quote}, \end{quote}.)
    – AlexG
    Feb 21, 2022 at 13:56
  • @AlexG Yes, no rule without exceptions.;-) Afaik itemize and quote are based on \list/\trivlist and \begin{...}/\end{...} are used for opening up another local scope wherein things like \leftmargin and \item etc can be changed/redefined. Feb 21, 2022 at 14:13
  • Good to know, thanks! Nevertheless, he doesn't state this rule his book.
    – AlexG
    Feb 21, 2022 at 14:20

You must log in to answer this question.

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