如何打造“延迟扩展”环境?

如何打造“延迟扩展”环境?

我不想使用以下内容

\begingroup
\edef\next{\endgroup
    \noexpand\const{Xp}{\X(tp)}
    \noexpand\const{Yp}{\Y(tp)}
    \noexpand\const{Xf}{\X(tf)}
    \noexpand\const{Yf}{\Y(tf)}
}\next

\begingroup
\edef\next{\endgroup
\noexpand\psparametricplot[algebraic,plotpoints=100]{0}{\tf}{\X(t)|\Y(t)}}\next

因为它们看上去很神秘。

相反,我想创建一个新的环境,如下所示,

\newenvironment{DelayExpansion}
{\begingroup
\edef\next\bgroup\endgroup\ignorespaces}
{\egroup\next\ignorespacesafterend}

但不幸的是,它会产生编译错误,您可以自己查看。

如果错误可以修复,则环境将按如下方式使用,

\DelayExpansion
  \noexpand\const{Xp}{\X(tp)}
  \noexpand\const{Yp}{\Y(tp)}
  \noexpand\const{Xf}{\X(tf)}
  \noexpand\const{Yf}{\Y(tf)}
\endDelayExpansion

\DelayExpansion
    \noexpand\psparametricplot[algebraic,plotpoints=100]{0}{\tf}{\X(t)|\Y(t)}}
\endDelayExpansion

或者不指定,\noexpand如下所示。

\DelayExpansion
  \const{Xp}{\X(tp)}
  \const{Yp}{\Y(tp)}
  \const{Xf}{\X(tf)}
  \const{Yf}{\Y(tf)}
\endDelayExpansion

\DelayExpansion
    \psparametricplot[algebraic,plotpoints=100]{0}{\tf}{\X(t)|\Y(t)}}
\endDelayExpansion

真实场景的 MWE 如下所示。

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-plot}

\addtopsstyle{gridstyle}{gridlabels=0,griddots=0,subgriddiv=5,gridwidth=0.4pt,subgridwidth=0.2pt}

\usepackage[nomessages]{fp}
\newcommand\const[3][3]{%
    \expandafter\FPeval\csname#2\endcsname{round(#3:#1)}%
    \pstVerb{/#2 \csname#2\endcsname\space def}%
}

\const{Vox}{2}
\const{Xo}{1}

\const{Voy}{4}
\const{Yo}{10}

\const{g}{10}

\def\X(#1){Xo+Vox*#1}
\def\Y(#1){Yo+Voy*#1-0.5*g*#1^2}

\const{tp}{Voy/g}
\const{toffset}{0.5}
\const{tf}{2*tp+toffset}

%\newenvironment{DelayExpansion}
%{\begingroup
%\edef\next\bgroup\endgroup\ignorespaces}
%{\egroup\next\ignorespacesafterend}
%
%\DelayExpansion
  %\noexpand\const{Xp}{\X(tp)}
    %\noexpand\const{Yp}{\Y(tp)}
    %\noexpand\const{Xf}{\X(tf)}
    %\noexpand\const{Yf}{\Y(tf)}
%\endDelayExpansion

\begingroup
\edef\next{\endgroup
    \noexpand\const{Xp}{\X(tp)}
    \noexpand\const{Yp}{\Y(tp)}
    \noexpand\const{Xf}{\X(tf)}
    \noexpand\const{Yf}{\Y(tf)}
}\next

\begin{document}
\begin{pspicture}[showgrid](0,\Yp)(\Xf,\Yf)
    \psframe[fillstyle=vlines,fillcolor=gray](0,\Yf)(\Xo,\Yo)
    \psparametricplot[algebraic,plotpoints=100]{0}{\tf}{\X(t)|\Y(t)}
\end{pspicture}
\end{document}

如何打造“延迟扩展”环境?

最新更新:

我尝试了 Ryan 给出的解决方案,但仍然会产生一些错误(请自行尝试查看错误信息)。

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-plot}

\usepackage[nomessages]{fp}

% cannot be simplified unless I must break my real scenario

\newcommand\const[3][3]{%
    \expandafter\FPeval\csname#2\endcsname{round(#3:#1)}%
    \pstVerb{/#2 \csname#2\endcsname\space def}%
}

\const{Xo}{1}
\const{Yo}{10}
\const{tp}{0.4}
\const{tf}{2*tp+1.5}

\def\X(#1){Xo+2*#1}
\def\Y(#1){Yo+4*#1-0.5*5*#1^2}


% try this for comparison first
%\begingroup
%\edef\next{\endgroup
    %\noexpand\const{Yp}{\Y(tp)}
%}\next

% Ryan's suggestin
\usepackage{environ}
\NewEnviron{DelayExpansion}{%
  \begingroup
  \edef\next{\endgroup\BODY}%
  \next%
}

% Ryan's suggestion
\begin{DelayExpansion}
    \noexpand\const{Yp}{\Y(tp)}
\end{DelayExpansion}


\begin{document}
\begin{pspicture}(0,6)(6,\Yp)
    \psparametricplot[algebraic,plotpoints=100]{0}{\tf}{\X(t)|\Y(t)}
\end{pspicture}
\end{document}

我将为一个被接受的答案提供 4 个赏金,每个 500 美元。听起来是不是很兴奋?

答案1

这个environ包就是为此而存在的。尝试一下:

\usepackage{environ}

\NewEnviron{DelayExpansion}{%
  \global\let\xBODY\BODY
}[\aftergroup\expandBODY]

\def\expandBODY{%
  \begingroup
  \edef\next{\endgroup\xBODY}%
  \next
}

它在应用之前收集整个环境\edef

您尝试失败的原因是\edef(和\def,和\xdef) 不仅需要隐式括号 (例如 ) 来\bgroup设置其内容,还需要实际括号{。拆分\def,这是确切地为什么environ存在收集两个“开始”和“结束”宏之间包含的环境的内容。

\noexpand我还应该说,如果不在所需位置明确指定,您不太可能编写出此环境的一个版本。如果您只想要\noexpand行的开头,您可以尝试^^M在环境之前激活并让它插入\noexpand自身,但这听起来非常冒险……

编辑:导致错误的原因是您的宏\const在内部通过进行定义\def,但environ包运行其代码里面环境,它受组保护(根据环境设计)。通常,您只需使用\gdef\xdef来规避这种情况,但由于这样做会默认一个人的编码环境的特性,因此更自动化的方法是使用简单地将\edef组导出到组之外\aftergroup。我分两个阶段声明了此导出。首先,我\BODY通过导出自身\global\let\BODY\BODY,然后使用可选参数\NewEnviron(在 处运行其内容\end{DelayExpansion})导出扩展代码。

这不需要任何技巧(除了\aftergroup,这非常棘手),也不需要破解的特定代码\end(如\end{DelayExpansion},除了知道其中有一个组)。

编辑2:这是一个稍微复杂一点的环境,模仿 egreg 的答案,可以自动保留指定的宏。我将在一个最小的情况下给出一个例子:

\documentclass{article}
\usepackage{environ,etoolbox}

\NewEnviron{DelayExpansion}[1][]{%
  \global\let\xBODY\BODY
  \global\def\xARGS{{#1}}
}[\aftergroup\expandBODY]

\def\expandBODY{%
  \begingroup
  \def\do##1{\let##1\relax}%
  \expandafter\docsvlist\xARGS
  \edef\next{\endgroup\xBODY}%
  \next
}

\begin{document}

\begin{DelayExpansion}[\macro]
  \def\macro{a}
\end{DelayExpansion}
\macro

\end{document}

(输出:a

或者,在你的例子中,

\begin{DelayExpansion}[\const]
    \const{Yp}{\Y(tp)}
\end{DelayExpansion}

可选参数中命名的任何宏都不会扩展(列表以逗号分隔并且可能为空,在这种情况下环境不执行任何操作)。

它不使用 LaTeX3,而是使用etoolbox,它为 LaTeX2e 提供了一些相同的编程便利。

答案2

我不会使用在团队中工作存在问题的环境。以下是建议:

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-plot}
\usepackage{xparse}

\usepackage[nomessages]{fp}
\newcommand\const[3][3]{%
    \expandafter\FPeval\csname#2\endcsname{round(#3:#1)}%
    \pstVerb{/#2 \csname#2\endcsname\space def}%
}

\ExplSyntaxOn
\NewDocumentCommand{\DelayExpansion}{mm}
 {
  \group_begin:
  \clist_map_inline:nn { #1 } { \cs_set_eq:NN ##1 \scan_stop: }
  \use:x { \group_end: #2 } 
 }
\ExplSyntaxOff

\const{Vox}{2}
\const{Xo}{1}

\const{Voy}{4}
\const{Yo}{10}

\const{g}{10}

\def\X(#1){Xo+Vox*#1}
\def\Y(#1){Yo+Voy*#1-0.5*g*#1^2}

\const{tp}{Voy/g}
\const{toffset}{0.5}
\const{tf}{2*tp+toffset}

\begin{document}
\DelayExpansion{\const}
 {
  \const{Xp}{\X(tp)}
  \const{Yp}{\Y(tp)}
  \const{Xf}{\X(tf)}
  \const{Yf}{\Y(tf)}
 }

\begin{pspicture}[showgrid](0,\Yp)(\Xf,\Yf)
  \psparametricplot[algebraic,plotpoints=100]{0}{\tp}{\X(t)|\Y(t)}
\end{pspicture}
\end{document}

\DelayExpansion两个参数;第一个是不应扩展的宏列表,第二个参数是将要扩展的内容,除了列表中的宏之外。

在此处输入图片描述

在示例中,您必须使用两次\DelayExpansion,因为宏\Yp\Xf\Yf只有在被定义(由第一个\DelayExpansion)后才可用。

怎么\DelayExpansion運作?

首先,它使(在一个组中)所有选定的宏等效于\relax(这是\scan_stop:LaTeX3 的说法)。然后,第二个参数作为参数给出,\use:x该参数完全展开所有内容(但\scan_stop:不可扩展,因此第一个参数\const将保留其自身)。

这只是一个众所周知的技巧的包装

\begingroup\edef\x{\endgroup<tokens>}\x

Ryan 也使用了它。这\group_end:将使\const(或第一个参数中的任何宏) 返回其先前的值,并且 LaTeX 将继续其正常操作。

答案3

构建于Ryan 的方法,可以通过一些扩展技巧将定义移出组:

\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-plot}

\usepackage[nomessages]{fp}

% cannot be simplified unless I must break my real scenario

\newcommand{\const}[3][3]{%
    \expandafter\FPeval\csname#2\endcsname{round(#3:#1)}%
    \pstVerb{/#2 \csname#2\endcsname\space def}%
}

\const{Xo}{1}
\const{Yo}{10}
\const{tp}{0.4}
\const{tf}{2*tp+1.5}

\def\X(#1){Xo+2*#1}
\def\Y(#1){Yo+4*#1-0.5*5*#1^2}


\usepackage{environ}
\makeatletter
\NewEnviron{DelayExpansion}{%
  \expandafter\endgroup
  \expandafter\begingroup
  \expandafter\edef\expandafter\next\expandafter
    {\expandafter\endgroup\BODY}%
  \next
  \begingroup
    \def\@currenvir{DelayExpansion}%
}
\makeatother

\begin{DelayExpansion}
    \noexpand\const{Yp}{\Y(tp)}
\end{DelayExpansion}


\begin{document}
\begin{pspicture}(0,6)(6,\Yp)
    \begin{DelayExpansion}
        \noexpand\psparametricplot[algebraic,plotpoints=100]{0}{\tf}{\X(t)|\Y(t)}
    \end{DelayExpansion}
\end{pspicture}
\end{document}

答案4

以下是 Ryan 的解决方案的一个版本,无需电子工具箱包。\dolist可以被 LaTeX 的 替换\@for

\documentclass{article}
\usepackage{environ}
\begingroup
\catcode`\,=\active
\gdef\dolist#1#2{%
  \begingroup\toks0{}%
  \def,##1{\toks0\expandafter{\the\toks0 #2}}%
  \catcode`\,=\active
  \scantokens{,#1}%
  \expandafter\endgroup\the\toks0 %
}
\endgroup

\NewEnviron{DelayExpansion}[1][]{%
  \global\let\xBODY\BODY
  \gdef\xARGS{#1}%
}[\aftergroup\expandBODY]

\def\expandBODY{%
  \begingroup
  \ifx\xARGS\empty\else
    \expandafter\dolist\expandafter{\xARGS}{\let##1\relax}%
  \fi
  \edef\next{\endgroup\xBODY}\next
}

\begin{document}
\def\xmacro{a}
\begin{DelayExpansion}[\macro,\foo]
  \def\foo##1{##1}%
  \def\macro{\xmacro}
  x
\end{DelayExpansion}
\macro
\end{document}

相关内容