Delaying command expansion that appears in \begin handler until \end handler executes

I have a custom environment for handling a code interface declaration that is specified as:

\begin{decl}{FOO}
\param{in}{bar} the bar input parameter
\param{out}{baz} the baz input parameter
\end{decl}


This is being typeset as a nested list by my package (note: reality is much more complicated, but this captures the idea, mostly).

\newrobustcmd*{\param}[2]{
\item[#1 #2]
}

\newenvironment{decl}[1]{
\begin{enumerate}
\item #1(<param names here>)
\begin{description}
}{
\end{description}
\end{enumerate}
}


My problem is that I need to accumulate the \param names, and then in the \end{decl} handler I need to inject them in the location tagged as <param names here>. Unfortunately, I don't know the list I want to inject at the time that I'm generating that item.

Is there a canonical way to do this?

I was considering using the etoolbox list functionality to build internal lists as I process \params, and then generating the whole enumerate structure in the \end{decl} handler, but I don't know how to capture the "description" \parbox for the \param for use later without using \par, which as far as I know requires a paragraph break between \param items, which doesn't exist in the source. I could introduce a third parameter to \param such that the description can be dealt with as a third parameter, but this involves quite a large change to the .tex source base.

I'd really prefer a way to \appto a command used in place of <param names here>, but this doesn't seem to work because use of the \apptoed command is eagerly expanded in the \begin{decl} handler and the appended strings aren't getting used.

-
Welcome to TeX.sx! Could you add a minimal working example (MWE) starting with \documentclass{...} and ending with \end{document}? Include also an example how you use your environment/command and how it should look like. And: The syntax for \newenvironment is backslash-less: \newenvironment{decl}… –  Qrrbrbirlbel Mar 28 '13 at 16:50
Sorry about the newenvironment bug. I'll wrap it in a document the next time I have a question. Thanks so much. –  Luke Mar 28 '13 at 17:43

Is there a canonical way? Well, this is one way.

The environ package allows us to use the collected \BODY of the environment to use more than once.

We use it twice:

1. To build the \@paramlist that contains the two arguments of \param. This is “executed” inside a box that is not type set.
2. With the original definition of \param the \BODY is actually typeset in the description environment.

Code

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

\makeatletter
\newrobustcmd*\orig@param[2]{\item[{#1 #2}]}%
\newrobustcmd*\decl@param[2]{\gappto\@paramlist{,\ #1 #2}}% needs to be global
\NewEnviron{decl}[1]{%
\def\@paramlist{\@gobbletwo}% \@gobbletwo will remove the first comma and space
\let\param\decl@param
\sbox0{\BODY}% will not be typeset but "executed"
\let\param\orig@param
\begin{enumerate}
\item #1 (\@paramlist\@empty\@empty)
\begin{description}
\BODY
\end{description}
\end{enumerate}
}
\makeatother
\begin{document}
\begin{decl}{Environment contains}
\param{Foo}{Bar} Foobar
\param{Ding}{Dong} Dingenskirchen
\param{La}{TeX} LaTeX
\end{decl}
\end{document}


Output

-
That's great, I haven't run into that \sbox usage before. This should work out perfectly. –  Luke Mar 28 '13 at 17:41

You can use \newenvironment without loading helper packages provided you're willing to run your document twice. Anyway, you must run it twice to get references (section, etc.) right. Thanks to egreg for his comment.

\documentclass{article}
\makeatletter
\newcount\paramenvcount
\expandafter\let\expandafter\tempa\csname paramlist/#1\endcsname
\ifdefined\tempa
\ifx\tempa\relax\def\tempa{}\fi
\else
\def\tempa{}%
\fi
\expandafter\protected@xdef\csname paramlist/#1\endcsname{%
\tempa\ifx\tempa\empty\else, \fi#2%
}%
}
\newcommand*{\param}[1]{\@testopt{\param@a{#1}}\space}
\def\param@a#1[#2]#3{%
\immediate\write\@auxout{%
}%
\item[#1#2#3]%
}
\newenvironment{decl}[1]{%
\begin{enumerate}%
\item #1 (\@nameuse{paramlist/\the\paramenvcount})
\begin{description}%
}{%
\end{description}%
\end{enumerate}%
}
\makeatother

% Tests:
\begin{document}
\begin{decl}{Environment 1 contains}
\param{Foo}{Bar} Foobar
\param{Ding}{Dong} Dingenskirchen
\param{La}[]{TeX} \LaTeX
\end{decl}

\begin{decl}{Environment 2 contains}
\param{Foo2}[*]{Bar2} Foobar2
\param{Ding2}[*]{Dong2} Dingenskirchen2
\param{La}[]{TeX2} \LaTeX2
\end{decl}
\end{document}


In your approach, there is always only one \item of \enumerate within \decl. I find the outcome rather uninteresting. I would have used the following scheme.

\documentclass{article}
\makeatletter
\newcount\entryenvcount
\newcommand*\gettempa[1]{%
\begingroup\expandafter\endgroup\expandafter
\let\expandafter\tempa\csname paramlist/#1\endcsname
}
\gettempa{#1}%
\ifdefined\tempa\else\def\tempa{}\fi
\expandafter\protected@xdef\csname paramlist/#1\endcsname{%
\tempa\ifx\tempa\empty\else, \fi#2%
}%
}
\newenvironment{decl}[1]{%
\def\entry{\@testopt\entry@a{}}%
\def\entry@a[##1]{%
\immediate\write\@auxout{%
}%
\labelwidth=-\if\relax\detokenize{##1}\relax3.25\else5\fi mm\relax
\item[##1]%
}%
\gettempa{\the\entryenvcount}%
\protected@xdef\tempa{%
\ifdefined\tempa\ifx\tempa\empty\else(\tempa)\fi\fi
}%
\noindent\hbox to 5mm{\the\entryenvcount.\hfil}#1~\tempa
\begin{description}%
}{%
\end{description}%
}
\makeatother

% Tests:
\begin{document}
\begin{decl}{Environment 1 contains}
\entry[Foo, Bar] Foobar
\entry[Ding, Dong] Dingdong
\entry[La,TeX] \LaTeX
\end{decl}

\begin{decl}{Environment 2 contains}
\entry[Foo2, Bar2] Foobar2
\entry[Ding2, Dong2] Dingdong2
\entry[La,TeX2] \LaTeX2
\end{decl}

\begin{decl}{Environment 3 contains no tags}
\entry Foobar3
\entry Dingdong3
\entry \LaTeX3
\end{decl}
\end{document}


-