\begin{problem} 错误由 \end{problem} 结束... 嗯?

\begin{problem} 错误由 \end{problem} 结束... 嗯?

因此,我知道如果我收到不匹配错误,例如,这意味着我的和命令error: \begin{problem} ended by \end{document}数量不匹配。但在这里,我收到以下错误:\begin\end

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

这让我很困惑...它似乎在抱怨我正确地结束了环境还是怎么的?我的猜测是,由于我分解了 \begin{problem} 和 \end{problem} 的方式,一定有某种奇怪的背景标志被吃掉或翻转了,但这是最终目标。

下面是重现错误的 MWE(如果重要的话,使用 TexStudio):

\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}

进一步的实验表明,如果我替换该行:

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

只需

\end{problem}

它再次编译。这意味着在更通用的方法中,扩展如何获得 \end{problem} 的“问题”部分有些奇怪,但我看不出它是什么,给出的“错误”表明它被扩展为正确的术语。是否有可能由于扩展命令而导致某种“类型”无法正确生成?

答案1

始终要小心谨慎,并非常挑剔地正确使用\expandafter以影响可扩展令牌扩展发生的时间顺序。:-)

-macro\begin作为其参数获取一个标记序列,该序列表示即将启动的环境的名称。在其他内容之下,它将保存该名称表示标记序列作为无参数宏的替换文本,该宏是一个控制字标记,其名称为\@currenvir

在其他事情之下,\end用于结束环境的 -command 会将作为其参数传递的标记序列与通过扩展传递的名称表示标记序列进行比较\@currenvir

如果 token 序列相同,则不会出现错误消息。
如果 token 序列不同,则会引发错误。

关键点是:

如果你做类似的事情

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

,则宏的替换文本\@currenvir将由字符标记序列组成MyEnvironment,而作为参数传递的标记序列\end将由控制字标记组成\foobar

这些是不同的标记序列。

因此将会引发错误消息。

(该错误消息的趣味之处在于,LaTeX 通常会在将可扩展标记写入控制台/屏幕时将其扩展。因此,在将错误消息写入控制台/屏幕时,控制字标记\foobar将得到扩展并产生字符标记序列MyEnvironment。最好在将错误消息写入控制台/屏幕时正确抑制扩展,以便获取错误消息, ! LaTeX Error: \begin{MyEnvironment} on input line ... ended by \end{\foobar}.而不是获取有些令人困惑的错误消息! LaTeX Error: \begin{MyEnvironment} on input line ... ended by \end{MyEnvironment}.

\end因此,在 TeX 获取 -macro 的参数之前,必须确保作为 参数传递的标记序列也完全扩展\end

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

如果首先使用\csname..\endcsname来形成控制字标记\foobar,则需要两个\expandafter链,而不是一个。第一个链将从..构造
中传递控制字标记。 第二个链将扩展控制字标记,传递其替换文本:\csname\endcsname

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

%另外,我强烈建议在正确的位置使用注释字符(),以避免在宏定义的替换文本中出现不需要的空格标记。

乍一看,您的嵌套和自动终止嵌套环境的概念看起来不错。

但是诸如此类的事情

\buildAssess{Test1}{%

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

\newstep当尝试关闭环境时,通过 打开的环境/范围尚未关闭。因此,可能会出现有关环境被关闭的itemize错误消息。problem\end{itemize}

嵌套\newstep在任何其他直接书写的环境中时也一样。

除此之外,下面的代码(对您的代码进行稍微修改后的结果)在某些情况下可能会实现您希望的效果:

\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}

相关内容