[Tex/LaTex] Arguments possibly delimited by \bgroup and \egroup

expansiongroupingtex-core

I still don't see (and haven't thought about) pros and cons of making available the arguments as \foo{…} or \foo\bgroup …\egroup (or even unbalanced, \bgroup …} and {…\egroup). But I'm curious about enabling it for certain commands (in a way that, for instance, you could create environments from commands).

How can we create a macro like

\def\foo#1{…}

that does accept

\foo\bgroup …\egroup

Another option I thought about is a command like

\delimitedbybegroup{<pre code>}<argument delimited by braces or \bgroup and \egroup>

So one could say \delimitedbybegroup\emph{Some text} or \delimitedbybegroup\emph\bgroup Some text\egroup both of them giving \emph{Some text}.

A more advanced one, for instance, might be

\delimitedbybegroup{\def\foo#1#2}\bgroup Something with #1 and #2\egroup

In any case any thoughts about why and why not are useful this kind of commands would be welcome.

And, in a similar way… how are (usually) handled commands that do look for a “closing” token, if the token is not visible. Like

\def\foo{\delimiter}
\def\baz#1\delimiter{…#1…}
\baz some code \foo{} and some more here that should be outside the argument of \string\baz.

Best Answer

The question gives me a sense if it is read from its end: give the possibility of creating a macro which expands its parameter during parameter scanning. Then the variants } or \egroup as a delimiter of the parameter is serviceable.

I've created the \eparam macro with this syntax:

\def\mymacro #1{the #1 parameter is declared as undelimited}
...
\eparam\mymacro parameter-text

The parameter-text is equal to real-parameter-text enclosed by braces or by another control sequences declared by \eparamopen and \eparamclose.
Example:

\eparamopen\start  \eparamclose\stop
\eparam\mymacro {real-parameter-text}
\eparam\mymacro \start real-parameter-text\stop
\eparam\mymacro \start real-parameter-text}
\eparam\mymacro {real-parameter-text\stop

The main point of the \eparam is that it prepares an Expanded Parameter. The real-parameter-text is expanded during parameter scanning like by \edef. This means that all expandable primitives and macros are expanded during the parameter is read. Unexpandable primitives do nothing in this time (like \edef) so you can do reassigmnent of registers/macros inside this parameter but without any effect for parameter scaninng. This is main difference between this case and the \hbox {...} primitive syntax.

There is one little difference between \edef and parameter scanning: undefined control sequences do nothing (like unexpandable primitives) during parameter scanning. The error can be occur only when the parameter is used (no during parameter scanning).

The separator declared by \eparamclose can be hidden in a macro. Example:

\def\x{-text\stop}
\eparam\mymacro {real-parameter\x

The first open brace or delimiter given by \eparamopen is optional. I.e. you can omit it:

\eparam\mymacro real-parameter-text\stop

The parameter is always balanced by braces. This means that the delimiter declared by \eparamclose does no effect inside inner braces pair (like normal parameter scanning):

\eparam \start text{inside \stop braces}text\stop
% the parameter is: "text{inside \stop braces}text"

The implementation (or wipet's sorcery :) and little tests follow.

\def\tmp{% all expandable primitives (only from classical TeX, you can add others):
  \botmark \csname \else \endcsname \endinput \expandafter \fi \firstmark \fontname
  \if \ifcase \ifcat \ifdim \ifeof \iffalse \ifhbox \ifhmode \ifinner       
  \ifmmode \ifnum \ifodd \iftrue \ifvbox \ifvmode \ifvoid \ifx
  \input \jobname \meaning \noexpand \number \or \romannumeral
  \splitbotmark \splitfirstmark \string \the \topmark
}
\def\skipmm#1->{}  \def\showmm#1->{#1}
\edef\textmm{\expandafter\showmm\meaning\empty}
\edef\expandprimitives{\expandafter\skipmm\meaning\tmp}

\def\isinlist#1#2#3{% from opmac.tex
   \def\tmp##1#2##2\end{\def\tmp{##2}%
   \ifx\tmp\empty \csname iffalse\expandafter\endcsname \else
                  \csname iftrue\expandafter\endcsname \fi}% end of \def\tmp
   \expandafter\tmp#1\endlistsep#2\end
}
\def\isexpanded#1#2{% \isexpanded X\iftrue the X is expandable primitive or macro\fi
   \edef\tmpb{\meaning#1\space}%
   \expandafter\isinlist\expandafter\tmpb\expandafter{\textmm}%
   \iftrue \csname iftrue\expandafter\endcsname\else
      \def\nexxt{\expandafter\isinlist\expandafter\expandprimitives\expandafter{\tmpb}.}%
      \expandafter\nexxt\fi
}
\def\eparamopen#1{\def\eparamopenA{\let#1=\eparamopenA}}
\def\eparamclose#1{\def\eparamcloseA{\let#1=\eparamcloseA}}

\newtoks\eparamT
\def\eparam#1{\begingroup
  \toks0={#1}\let\bgroup=\relax \let\egroup=\relax
  \let\ifIamInGroup=\iffalse
  \ifx\eparamopenA\undefined \def\eparamopenA{^\eparam^}\else \eparamopenA\fi
  \ifx\eparamcloseA\undefined \def\eparamcloseA{^\eparam^}\else \eparamcloseA\fi
  \eparamT={}\eparamA
}
\def\eparamA{\futurelet\tmpc\eparamB}
\def\eparamB{\let\next=\eparamD
   \isexpanded\tmpc\iftrue \def\next{\expandafter\eparamA}\fi
   \ifx\tmpc\bgroupOri \let\next=\eparamC \let\nexxt=\eparamD \fi  
   \ifx\tmpc\eparamopenA  \let\next=\eparamC \let\nexxt=\eparamD \fi
   \next
}
\def\eparamC{\afterassignment\nexxt \let\next= }
\def\eparamD{\futurelet\tmpc\eparamE}
\def\eparamE{\let\next=\eparamN
   \isexpanded\tmpc\iftrue \def\next{\expandafter\eparamD}\fi
   \ifx\tmpc\spacetoken \let\next=\eparamC \let\nexxt=\eparamD \eparamX{ }\fi
   \ifx\tmpc\eparamcloseA \ifIamInGroup \let\next=\eparamN
                          \else \let\next=\eparamC \let\nexxt=\eparamF \fi\fi
   \ifx\tmpc\egroupOri \let\next=\eparamC \let\nexxt=\eparamF \fi
   \ifx\tmpc\bgroupOri \let\next=\eparamC \let\nexxt=\eparamG \fi
   \next
}
\def\eparamN#1{\eparamX#1\eparamD}
\def\eparamG{\begingroup \let\ifIamInGroup=\iftrue \eparamT={}\eparamD}
\def\eparamF{\ifIamInGroup \let\next=\eparamY \else \let\next=\eparamZ \fi \next}
\long\def\eparamX#1{\eparamT\expandafter{\the\eparamT#1}}
\def\eparamY{\expandafter\endgroup
   \expandafter\eparamT\expandafter\expandafter\expandafter
      {\expandafter\the\expandafter\eparamT\expandafter{\the\eparamT}}%
   \eparamD
}
\def\eparamZ{\expandafter\endgroup\the\toks0\expandafter{\the\eparamT}}

\let\bgroupOri=\bgroup
\let\egroupOri=\egroup
\def\tmp/{\let\spacetoken= }\tmp/ %

\def\macro#1{\toks0={#1}\message{the parameter is "\the\toks0"}}

\eparam\macro {abc}  % the parameter is "abc"

\def\x{ww}
\eparam\macro {ab\x c}          % the parameter is "abwwc"
\eparam\macro {ab\the\pageno c} % the parameter is "ab1c"
\eparam\macro {ab\ifx\x\x true\else false\fi c}        % the parameter is "abtruec"
\eparam\macro {ab\ifnum\folio=1 true\else false\fi c}  % the parameter is "abtruec"
\eparam\macro {ab\ifcase\pageno oo\or one\or two\fi c} % the parameter is "abonec"

\eparamopen\start \eparamclose\stop

\eparam\macro {abc\stop        % the parameter is "abc"
\eparam\macro \start abc\stop  % the parameter is "abc"
\eparam\macro \start abc}      % the parameter is "abc"

\eparam\macro abc}     % the parameter is "abc"
\eparam\macro abc\stop % the parameter is "abc"

\eparam\macro \start abc{uf\stop fu}ee\stop  % the parameter is "abc{uf\stop fu}ee"

\def\y{end\stop}

\eparam\macro {a\x\y   % the parameter is "awwend"

\end

Edit: I did do a small modification of the code in order to solve the last demand from the question:

\eparamopen\bgroup \eparamclose\egroup
\eparam {\def\foo#1#2}\bgroup Something with #1 and #2\egroup

acts like \edef\foo#1#2{Something with #1 and #2}.

If you need to deactivate the expansion process (i.e. you need to do \def, no \edef) then you can declare

\def\isexpanded#1#2{\iffalse}

Of course, the delimiter declared by \eparamclose cannot be found in nested macro in such case.