11

I don't want to use the following

\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

and

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

because they seem to be cryptic.

Instead I want to create a new environment as follows,

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

but unfortunately it produces compilation errors that you can see by yourself.

If the errors can be fixed, the environment will be used as follows,

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

and

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

Or without specifying the \noexpand as follows.

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

and

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

The MWE for the real scenario is given as follows.

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

How to create a "delay expansion" environment?

The latest update:

I have tried the solution given by Ryan, but it still produces some errors (please try it by yourself to see the error messages).

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

I will offer 4 bounties of 500 each to a single accepted answer. Does it sound excited?

6
  • 3
    It sounds excitable, particularly given your deficit of 500 points to offer the fourth... :)
    – Ryan Reich
    Commented Mar 17, 2013 at 19:33
  • @RyanReich: Getting 500 in a week is possible. Don't worry. :D Commented Mar 17, 2013 at 19:48
  • Now that you've accepted my answer, I'm going to refuse three of your four bounties. Although I put some effort into my answer I've written better, and other people put as much into their answers, and in all it's just wrong to single this one out and say "none of the others even compare!" If it's worth so much to you one bounty at the maximum rate is enough.
    – Ryan Reich
    Commented Mar 19, 2013 at 15:08
  • @RyanReich: I am sorry. I have to do what I mentioned above. You devotes to get the 4 bounties of 500 each. An extra bounty of 500 will be given to David because of his "out-of-topic" idea but I use it in my production. :-) Commented Mar 19, 2013 at 15:28
  • Then, I find that your answer tex.stackexchange.com/questions/55943/… is exemplary and deserving of 1500 reputation worth of bounties :)
    – Ryan Reich
    Commented Mar 19, 2013 at 15:30

5 Answers 5

12
+2000

This kind of thing is what the environ package exists for. Try:

\usepackage{environ}

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

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

It collects the entire environment before applying the \edef to it.

The reason your attempt failed is that \edef (and \def, and \xdef) require not just implicit braces such as \bgroup to set off their contents, but actual braces {. It is not easy to split a \def across several macros for this reason, which is exactly why environ exists to collect the contents of an environment contained between two "start" and "end" macros.

I should also say that it's unlikely that you'll be able to write a version of this environment that works without \noexpand being explicitly specified at the desired places. If you only want to \noexpand the beginning of the lines you can try to make ^^M active before the environment and have it insert \noexpand itself, but that sounds very risky...

Edit: The cause of your errors is that your macro \const is internally making definitions via \def, yet the environ package runs its code inside the environment, where it is protected by a group (by design of environments). Normally you'd just use \gdef or \xdef to circumvent this, but since to do that is to acquiesce to the peculiarities of one's coding environment a more automatic approach is to simply export the \edef outside the group using \aftergroup. I've declared this export in two stages. First, I export \BODY itself via \global\let\BODY\BODY and then, using the optional argument to \NewEnviron (which runs its contents at \end{DelayExpansion}), I export the expansion code.

This doesn't require any trickery (other than \aftergroup, which is pretty tricky) and also doesn't require hacking into the specific code of \end (as in \end{DelayExpansion}, except for knowing that there's a group in it.

Edit 2: Here is a slightly more complex environment that, in imitation of egreg's answer, can automatically preserve specified macros. I'll give an example in a minimal case:

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

(output: a)

Or, in your example,

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

Any macro named in the optional argument is not expanded (the list is comma-separated and may be empty, in which case the environment does nothing).

It doesn't use LaTeX3 but instead etoolbox, which provides some of the same programming conveniences for LaTeX2e.

4
  • @RyanReich Mixing global and local assignments to the same variable can be dangerous; at each call of the environment you're retaining \BODY; I'd use a different name: \global\let\xBODY\BODY and \expandBODY can use \xBODY.
    – egreg
    Commented Mar 17, 2013 at 22:52
  • @egreg: good point, fixed.
    – Ryan Reich
    Commented Mar 17, 2013 at 22:54
  • Could you please apply your latest edit to my real scenario such that I can compare it with Joseph's and Egreg's answer. The example in your latest edit seems to be sophisticated, but how to apply to my real scenario? Commented Mar 18, 2013 at 12:07
  • Done. It is mostly the same as my minimal example since only one macro is held back, but in general you can name more than one.
    – Ryan Reich
    Commented Mar 18, 2013 at 13:09
9

I wouldn't use an environment, which has the problem of doing its work in a group. Here's a proposal:

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

The \DelayExpansion has two arguments; the first is a list of macros that shouldn't be expanded, the second argument is what will be expanded, except for the macros in the list.

enter image description here

In the example you have to use twice \DelayExpansion, because the macros \Yp, \Xf and \Yf wouldn't be available until they have been defined (by the first \DelayExpansion).

How does \DelayExpansion work?

First of all it makes (in a group) all chosen macros equivalent to \relax (which is \scan_stop: in LaTeX3 parlance). Then the second argument is given as argument to \use:x that fully expands everything (but \scan_stop: is not expandable, so in the first one \const will remain itself).

This is just a wrapper around the well known trick

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

which is used also by Ryan. The \group_end: will make \const (or whatever macro is in the first argument) return to its previous value and LaTeX will continue its normal operations.

2
  • 3
    @Karl'sstudents Why? Of course this is only to show the potential; the \begin{pspicture} instruction can go outside the \DelayExpansion macro. But I'll edit. I believe that it's better to announce what shouldn't be expanded, rather than placing \noexpand manually: you don't risk to forget some.
    – egreg
    Commented Mar 17, 2013 at 20:39
  • +1 for getting the automatic \noexpands
    – Ryan Reich
    Commented Mar 17, 2013 at 22:34
6

Building on Ryan's approach, the definitions can be moved out of the group with a bit of expansion trickery:

\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}
9
  • +1 So far, it is the best candidate for the bounty. Thank you very much! It will be better (but not necessary) if you can implicitly add \noexpand behind the scene to free users from specifying it explicitly. Commented Mar 17, 2013 at 20:48
  • @Karl'sstudents You'll have to say how, exactly, you want that \noexpand added. At the beginning of each line? Automatically to specific macros? Because it is impossible, in general, to read the author's mind as to which macros should be held back.
    – Ryan Reich
    Commented Mar 17, 2013 at 22:32
  • @RyanReich: It should prefix each macro defined inside the DelayExpansion with \noexpand automatically. Commented Mar 17, 2013 at 22:35
  • @Karl'sstudents: So how would it know to catch \const but not \X or \Y?
    – Ryan Reich
    Commented Mar 17, 2013 at 22:43
  • @RyanReich: \psparametricplot apparently does not need delayed expansion. See my update please. Commented Mar 17, 2013 at 22:46
6

Since there are several environment forms posted I think I'll ignore the suggested solution of an environment and offer an alternative. Using an environment for this seems inherently fragile and not at all LaTeX-like.

Just define an \econst variant of your \const macro that expands its second argument. (Such variants are the basis of the whole LaTeX3 xparse stack, but I'll use classic TeX syntax here).

\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}%
}
\def\econst#1#2{\edef\next##1{\noexpand\const{##1}{#2}}\next{#1}}


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





\econst{Xp}{\X(tp)}
\econst{Yp}{\Y(tp)}
\econst{Xf}{\X(tf)}
\econst{Yf}{\Y(tf)}
\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}

Or if you don't need the unexpanded form and want the expanded form to have an optional argument you could directly define \econst rather than make it a variant of \const.

\newcommand\econst[3][3]{%
    \edef\next{round(#3}%
    \expandafter\FPeval\csname#2\expandafter\endcsname
         \expandafter{\next:#1)}%
    \pstVerb{/#2 \csname#2\endcsname\space def}%
}
8
  • +1 You deserve to get my another extra bounty of 500 for this idea and effort. Commented Mar 17, 2013 at 23:58
  • As I can also invoke \econst{g}{10} (for example) in spite of \const{g}{10}, then it will be better to make \const private with different name and changing \econst to \const. Thus we have only a multi-purpose \const at the end. What do you think? Commented Mar 18, 2013 at 0:06
  • @Karl'sstudents I wondered about that but didn't know if you needed the unexpanded form in other contexts, also the way I did econst hid your optional argument with default 3 if you made \const internal you might want econst to have an optional argument too? (which is easy enough to do) Commented Mar 18, 2013 at 0:11
  • I need the optional argument for the final \const (based on my explanation above). Do you have time to make it? :D Commented Mar 18, 2013 at 0:22
  • 1
    You really don't have to bankrupt yourself:-) Commented Mar 18, 2013 at 0:42
2

Here is a version of Ryan's solution without etoolbox package. \dolist could have been replaced by LaTeX's \@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}
2
  • Please show me how to apply your answer to my real scenario given above? Commented Mar 18, 2013 at 0:33
  • Please use my solution the same way you would Ryan’s solution. All I have done is to avoid the need to load etoolbox package. If you needed etoolbox for other purposes, then my solution is only of theoretical interest.
    – Ahmed Musa
    Commented Mar 18, 2013 at 20:25

You must log in to answer this question.

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