2

So, I know if I get a mismatch error like error: \begin{problem} ended by \end{document}, that means I have mismatched number of \begin and \end commands. But here, I am getting the following error:

! LaTeX Error: \begin{problem} on input line 52 ended by \end{problem}.

This baffles me... it seems to be complaining that I'm ending the environment correctly or something? My guess is that there has to be some sort of weird background flag getting eaten or flipped because of the way I've broken apart the \begin{problem} and \end{problem}, but that is the ultimate goal.

Here is a MWE which reproduces the error (using TexStudio if it matters):

\documentclass{article}

\usepackage{forloop}
\newenvironment{problem}{}{}

\newcounter{assessmentDepth}
\setcounter{assessmentDepth}{0}
\newcounter{IterationPH}
\newcounter{TMP}

\newcommand{\newstep}[1][problem]{% This identifies a new "step" in the assessment/build process.
\stepcounter{assessmentDepth}
\expandafter\expandafter\expandafter\edef\expandafter\csname assessname\roman{assessmentDepth}\endcsname{#1}
\begin{#1}% Begin a new "problem", where the name can be given as an optional argument (defaults to problem)

}

\newcommand{\assessmentEnd}{%
\setcounter{TMP}{\arabic{assessmentDepth}}% We need to start at the last depth level and iterate backwards.

\forloop{IterationPH}
    {0}%
    {\value{IterationPH} < \arabic{assessmentDepth}}% iterate through the assessment depth one step at a time.
    {
    \expandafter\expandafter\expandafter\end\expandafter{\csname assessname\roman{TMP}\endcsname}
    \addtocounter{TMP}{-1}
    }
}

\newcommand{\buildAssess}[3][problem]{%
%\buildAssess{QuestionCode}{BuildUpCode} Optional argument allows it to be called something other than problem, which is default.
\begin{#1}

#2

\end{#1}

#3
\assessmentEnd
}


\newif\ifHW
\HWtrue

\begin{document}

\buildAssess{Test1}{

\newstep
test
}


\end{document}

Further experimentation reveals that if I replace the line:

\expandafter\expandafter\expandafter\end\expandafter{\csname assessname\roman{TMP}\endcsname}

with simply

\end{problem}

it compiles again. Which means there is something goofy about how the expansion is occurring to get the "problem" part of \end{problem} in the more generic method, but I don't see what it is and the "error" given suggests that it's being expanded to the correct term. Is it possible there is some kind of "type" that isn't being generated correctly because it's expanding a command?

  • The \end{problem} for each \newstep is generated in \assessmentEnd which is called at the end of the (master) command \buildAssess. – Jason May 7 '18 at 2:59
6

Always be careful and very picky about the proper usage of \expandafter for influencing the order in time in which expansion of expandable tokens takes place. :-)

The \begin-macro as its argument gets a sequence of tokens which denotes the name of the environment that is about to be started. Beneath other things it will save that name-denoting token-sequence as the replacement-text of a macro without arguments which is a control-word-token and whose name is \@currenvir.

Beneath other things the \end-command for ending an environment will compare the token sequence delivered as its argument to the name-denoting token-sequence delivered by expanding \@currenvir.

In case the token-sequences are the same, no error message will occur.
In case the token-sequences differ, an error will be raised.

The crucial point is:

If you do something like

\def\foobar{MyEnvironment}
\begin{MyEnvironment}
...
\end{\foobar}

, then the replacement-text of the macro \@currenvir will consist of the character-token-sequence MyEnvironment while the token sequence delivered as argument to \end will consist of the control-word-token \foobar.

These are different token-sequences.

Therefore an error-message will be raised.

(The fun with that error-message is that LaTeX usually will expand expandable tokens when writing them to console/screen. Therefore when writing the error-message to console/screen, the control-word-token \foobar will get expanded and will yield the character-token-sequence MyEnvironment. It would have been better to have expansion suppressed properly at the time of writing the error-message to console/screen in order to get an error-message ! LaTeX Error: \begin{MyEnvironment} on input line ... ended by \end{\foobar}. instead of getting the somewhat confusing error-message ! LaTeX Error: \begin{MyEnvironment} on input line ... ended by \end{MyEnvironment}.)

Therefore it must be ensured that also the sequence of tokens delivered as \end's argument is expanded completely before TeX will fetch the argument for the \end-macro:

\def\foobar{MyEnvironment}
\begin{MyEnvironment}
...
\expandafter\end\expandafter{\foobar}

If you use \csname..\endcsname for first forming the control-word-token \foobar, you need two \expandafter-chains instead of just one.
The first chain will deliver the control-word-token from the \csname..\endcsname-construct.
The second chain will expand the control-word-token, delivering its replacement-text:

\def\foobar{MyEnvironment}
\begin{MyEnvironment}
...
\expandafter\expandafter
\expandafter            \end
\expandafter\expandafter
\expandafter            {%
\csname foobar\endcsname}

Also I strongly recommend avoiding the coming into being of unwanted space-tokens within the replacement-texts of your macro definitions by using comment-characters (%) in the right places.

At first glimpse your concept of nesting and automatically terminating nested environments looks nice.

But what about things like

\buildAssess{Test1}{%

  \begin{itemize}%
  \item Another step as item:
    \newstep
    test
  \item Another item.
  \end{itemize}%
}

?

The environments/scopes opened up via \newstep will not be closed yet when it comes to an attempt of closing the itemize-environment. Therefore an error-message about problem-environment being closed by \end{itemize} might occur.

Same when nesting \newstep within whatsoever other directly written environments.

Leaving that aside, the following code, which is the result from slightly modifying your code, might in some situations/scenarios probably do what you wish:

\documentclass{article}

\usepackage{forloop}
\newenvironment{problem}{Problem-Environment:}{}
\newenvironment{problemB}{ProblemB-Environment:}{}

\newcounter{assessmentDepth}
\setcounter{assessmentDepth}{0}
\newcounter{IterationPH}
\newcounter{TMP}

\newcommand{\newstep}[1][problem]{%
  % This identifies a new "step" in the assessment/build process:
  \stepcounter{assessmentDepth}%
  \expandafter\edef\csname assessname\roman{assessmentDepth}\endcsname{#1}%
  % Begin another instance of an environment, where the name can be given
  % as an optional argument (defaults to the "problem"-environment):
  \begin{#1}%
}%

\newcommand\assessmentEnd{%
  %
  % We need to start at the last depth level and iterate backwards:
  %
  \setcounter{TMP}{\arabic{assessmentDepth}}%
  %
  % iterate through the assessment depth one step at a time:
  %
  % ( By the way:
  %   Why cumbersomely using \forloop and the counters 
  %   IterationPH and TMP instead of, e.g., lightheartedly
  %   - calling the \csame..\endcsname-construct using 
  %     the assessmentDepth-counter
  %   - decrementing the assessmentDepth-counter
  %   - (recursively) calling \assessmentEnd again only in case
  %     the value of the assessmentDepth-counter is greater than
  %     zero?????
  % )
  \forloop{IterationPH}%
    {0}%
    {\value{IterationPH} < \arabic{assessmentDepth}}%
    {%
      % The first expandafter-chain will form the control-sequence-token
      % from the \csname..\endcsname-construct.
      % The second expandafter-chain will expand that control-sequence-token.
      \expandafter\expandafter
      \expandafter            \end
      \expandafter\expandafter
      \expandafter            {%
      \csname assessname\roman{TMP}\endcsname}%
      \addtocounter{TMP}{-1}%
    }%
    % PERHAPS the assessmentDepth-counter should be reset 
    % to zero now as well???????
    % \setcounter{assessmentDepth}{0}%
}%

\newcommand{\buildAssess}[3][problem]{%
  % \buildAssess{QuestionCode}{BuildUpCode}
  %  Optional argument allows it to be called something other
  % than problem, which is default.
  \newstep[#1]%

  #2%

  #3%
  \assessmentEnd
}%

%% This is not used at all, thus I comment it out:
%% \newif\ifHW
%% \HWtrue

\begin{document}

\buildAssess{%
  QuestionCode 1:%
}{%
  BuildUpCode 1:

  \newstep[problemB]%
  test problemB-environment in BuildUpCode 1

  \newstep
  test problem-environment nested inside problemB-environment in BuildUpCode 1
}

\end{document}
  • Welcome to TeX.SX! Thank you for answering. – Bobyandbob May 15 '18 at 17:12
  • This is exactly what I was after, thanks! I thought it was an issue with the expansion, but was confused since it "appeared" to be correctly expanded in the log file; I had no idea that the log file expanded tokens before printing, even if they weren't expanded when the error occurred. Is there a way to prevent this behavior? (As in, have the log file display the token as it was when the error occurred, and not expand it when it gets printed). Thanks for the comprehensive answer. Side note: Can you give an example of the recursive strategy you mentioned rather than the forloop strategy? – Jason May 16 '18 at 15:40
  • 1
    In this case there is no way to prevent this expansion-behavior as the way in which error-messages get created for environments is hard-coded within the LaTeX2e-kernel. You could redefine the \end-macro-mechanism so that the tokens that behind the scenes go to the call to \@latex@error are put into a token-register instead. When that token-register is flushed via \the during the \write that takes place behind the scenes of \@latex@error , no further expansion will take place. But I recommend not to hack around in kernel macros as this might break packages that do so too. ;-) – Anno nymous May 16 '18 at 16:01

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.