6

For some reason I want a macro \BeginControl such that \BeginControl{\environment} is equivalent to \begin{environment}. I have put together some code. It usually works but there is a problem with starred environments I'm trying to solve.

Consider the following code. The two instances of align* environment should behave the same, but it yields a wrong looking error ! LaTeX Error: \begin{align*} on input line 33 ended by \end{align*}. Where is the problem?

\documentclass{article}

\usepackage{amsmath}

\newcommand*{\cdef}{\newcommand*}

\cdef \Expanded [1]{%
    \begingroup
    \edef \x {%
        \endgroup
        #1%
    }%
    \x
}
\cdef \Apply [1]{%
    \Expanded{\noexpand #1}%
}
\cdef \IgnoreNext [1]{%
    % ignore next token
}
\cdef \MacroName [1]{%
    \expandafter\IgnoreNext \string#1%
}
\cdef \BeginControl [1]{%
    \Apply{\begin{\MacroName{#1}}}%
}

\begin{document}
    \begin{align*}
        A \\
        B
    \end{align*}
    \BeginControl{\align*}
        A \\
        B
    \end{align*}
\end{document}

Update: Thanks to the answers I found out that my macro \MacroName is incorrect since it expands to the macro name with catcode 12 instead of 11 due to \string. The following definition should work:

\let \Ex \expandafter
\cdef \ExEx {%
    \Ex\Ex\Ex
}
\cdef \ExExArg [2]{%
    \ExEx#1\ExEx{#2}%
}
\cdef \MacroName [1]{%
    \ExExArg\scantokens{\Ex\IgnoreNext \string#1\noexpand}%
}
  • 1
    Also \BeginControl{\align} would produce the same error. – egreg Sep 6 '15 at 16:06
  • @egreg: I see, I was blaming the star, but it seems to be some ugly implementation detail of \align. The macros work ok for theorems and itemize. If I remember I had also similar problem with frame environment in beamer. – user87690 Sep 6 '15 at 16:13
  • 1
    No, it doesn't work with itemize either. What's the purpose of having \BeginControl{\foo} instead of \begin{foo}? – egreg Sep 6 '15 at 16:26
  • @egreg: Interesting, I use it in conjuction with \cdef \EndCurrent {\Apply{\end{\@currenvir}}}. But with theorems and itemize, it seemed to work with no problem. – user87690 Sep 6 '15 at 16:59
  • align is special: it needs to see \end{align} explicitly, not buried in a macro. See, for instance, tex.stackexchange.com/questions/196238/… – egreg Sep 6 '15 at 17:54
5

Let's see what happens with

\BeginControl{\align}

Step 1: expand \BeginControl

\Apply{\begin{\MacroName{\align}}}

Step 2: expand \Apply

\Expanded{\noexpand\begin{\MacroName{\align}}}

Step 3: expand \Expanded

\begingroup\edef\x{\endgroup\noexpand\begin{\MacroName{\align}}}\x

Step 4: \begingroup is executed; open a group and remove the command.

Step 5: \edef causes full expansion of the given balanced text.

Substep 5.1: \endgroup is unexpandable, push it in the token list to be built.

Substep 5.2: \noexpand makes the next token unexpandable; remove it and push \begin to the token list being built.

Substep 5.3: { is unexpandable, push it.

Substep 5.4: expand \MacroName

\expandafter\IgnoreNext\string\align

Substep 5.5: \expandafter expands \string and is removed. Note that \string\align produces \align as characters all having category code 12.

Substep 5.6: expand \IgnoreNext; the backslash is removed.

Substeps 5.7-5.11: the tokens align are unexpandable; push them to the token list being built.

Substep 5.12: } is unexpandable, push it

Step 6: the token list is \endgroup\begin{align}; TeX executes \def\x{\endgroup\begin{align}}

Step 7: expand \x, which replaces \x with the token list above.

Step 8: \endgroup closes the opened group, the meaning of \x is forgotten.

Step 9: \begin{align} is executed.

It seems it ought to work; alas, no.

Remember that align is a string of characters having category code 12; when LaTeX executes \begin{align} it sets \@currenvir to align (all category codes 12); the execution of \end{align} compares the argument to \@currenvir and they turn out to be different, because in \end{align} the characters have category code 11.

You can check that

\expandafter\begin\expandafter{\string minipage}{25pt}
xyz
\end{minipage}

gives the same error: only m has category code 12, but this is sufficient for the two “strings” at \begin and \end to be different.

The error has been explained; I'm not sure what your aim is with such a complicated approach.


A solution.

\documentclass{article}

\usepackage{amsmath}

\makeatletter
\newcommand{\BeginControl}[1]{%
  \begingroup
  \edef\x{\expandafter\@gobble\string#1}%
  \edef\x{\endgroup\noexpand\begin{\scantokens\expandafter{\x\noexpand}}}%
  \x
}
\makeatother

\begin{document}

\BeginControl{\enumerate}
\item xyz
\end{enumerate}

\BeginControl{\align}
  A \\
  B
\end{align}

\BeginControl{\align*}
  A \\
  B
\end{align*}

\end{document}

The \scantokens rereads the string with the usual category codes.

  • The error message could have been designed better in the case the characters match but not their catcodes. Is there something like \show that shows also the catcodes? – user87690 Sep 6 '15 at 17:07
  • @user87690 -- you can use \the before \catcode to retrieve the current catcode. – Ruben Sep 6 '15 at 17:09
  • @Ruben: What if you want catcodes of all tokens in a defined macro? I mean – is there any standard tool for such debuging? – user87690 Sep 6 '15 at 17:14
  • 1
    @user87690 See \showcat macro here: tex.stackexchange.com/questions/228979 – wipet Sep 6 '15 at 17:32
  • @user87690 As far as TeX is concerned, a with category code 12 is different from a with category code 11. So LaTeX tells you that the environment was started with a different name than it ended; the fact that the shown string seem equal is incidental. – egreg Sep 6 '15 at 17:53
6

Your problem is that LaTeX checks the equivalence of strigs+catcodes in its \@endcheck, because \ifx is used. Normal typed environment has letters with catcodes 11 and others characters in 12 but the string generated by \string has letters in catcode 12.

You must to re-catcode the string to catcodes 11. I suggest a similar expanable macro for this based on \ifcase:

\documentclass{article}

\usepackage{amsmath}

\def\recatcode#1{\ifx#1\relax\else
  \ifcase\numexpr`#1-`A\relax A\or B\or C\or D\or E\or F\or G\or H\or I\or
      J\or K\or L\or M\or N\or O\or P\or Q\or R\or S\or T\or U\or V\or W\or
      X\or Y\or Z\or#1\or#1\or#1\or#1\or#1\or#1\or
      a\or b\or c\or d\or e\or f\or g\or h\or i\or
      j\or k\or l\or m\or n\or o\or p\or q\or r\or s\or t\or u\or v\or w\or 
      x\or y\or z\else #1\fi
  \expandafter\recatcode\fi
}
\def\BeginControl#1{\edef\x{\expandafter\ignoreone\string#1}%
  \edef\x{\expandafter\recatcode\x\relax}%
  \expandafter\begin\expandafter{\x}%
}
\def\ignoreone#1{}

\begin{document}  
    \begin{align*}
        A \\
        B
    \end{align*}

    \BeginControl{\align}
        A \\
        B
    \end{align}
\end{document}
  • Nice alternative to \scantokens for non e-TeX. – egreg Sep 6 '15 at 16:45

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.