# Delimited arguments and using a macro inside of itself

I wanted to play with delimiter sizing without using the \delimiterfactor, -shortfall, etc. and made the following:

\newdimen\bigdim  \bigdim  = 1.9743ex
\newdimen\Bigdim  \Bigdim  = 2.671ex
\newdimen\biggdim \biggdim = 3.3678ex
\newdimen\Biggdim \Biggdim = 4.0646ex

\def\fence#1#2#3{
\setbox0\hbox{$\displaystyle #3$}
\ifdim\ht0 < .9\bigdim #1\box0 #2\else
\ifdim\ht0 < .9\Bigdim \bigl#1\box0\bigr#2\else
\ifdim\ht0 < .9\biggdim \Bigl#1\box0\Bigr#2\else
\ifdim\ht0 < .9\Biggdim \biggl#1\box0\biggr#2\else
\ifdim\ht0 < .9\Biggdim \Biggl#1\box0\Biggr#2\else
\left#1\box0\right#2
\fi\fi\fi\fi\fi}

\def\args(#1){\fence(){#1}}

$$\args(bar\args(baz))$$

\bye


(before you push that comment-button, I know there should be \mathsurround=0pt and such in there, I just wanted to keep this simple) but this results in an error saying the argument of \args has an extra }.

I thought it would have something to do with macro argument delimiters, so made a quick test:

\def\foo(#1){(#1)}
\foo(bar\foo(baz))
\bye


but that seemed to work just fine. Now I don't know what else could be the issue. Note that if there isn't an \args inside of \args, it works.

-

You have

\args(bar\args(baz))


so the argument to the outer \args is

 bar\args(baz


as it stops at the first )

you want

 \args({bar\args(baz)})


in the \foo example, the arguments are interpreted the same way, but it accidentally works.

for the outer \foo '#1 is

bar\foo(baz


so that expands to

(bar\foo(baz)


then the inner \foo expands to

 (baz)


and finally the ) which was never used as a macro delimiter is typeset.

-
So the outer one is expanded before the inner one? That's... gross. – morbusg Aug 9 '14 at 11:27
Well how come it works with the \foo then? – morbusg Aug 9 '14 at 11:29
@morbusg not expanded, scanned, the arguments are scanned before the macro expands, I'll step through the foo example in a minute – David Carlisle Aug 9 '14 at 11:32
@morbusg not really TeX only has one kind of matching (catcode 1 and 2 so normally { and }) if you delimit an argument as (#1) it will take everything up to the first ) as #1 without matching anything other than {}. You see same in latex's [] delimited optional arguments. as Joseph said xparse will match () if you want but it does it by reading token by token and matching braces "by hand" – David Carlisle Aug 9 '14 at 11:45
@MickG no there is no height to satisfy any condition. The outer \args tries to set its argument in a box so it evaluates \hbox{$\displaystyle bar\args(baz$} and if that box was set its height would be measured but the inner \args fails with a runaway argument error as it has no ) – David Carlisle Aug 9 '14 at 21:01

You can use \ensurebalanced macro for another types of parens by the following way:

\def\macro(#1){\ensurebalanced()\macroA{#1}}
\def\macroA#1{the "#1" is balanced here in () meaning}

\macro(abc(de(f))g(hi)) % prints: the "abc(de(f))g(hi)" is balanced here in () meaning.


\def\args(#1){\ensurebalanced(){\fence()}{#1}}


My macro \ensurebalanced looks like this:

\newcount\tmpnum
\def\ensurebalanced#1#2#3#4{%
\isbalanced#1#2{#4}\iftrue #3{#4}%
\else
\def\ensurebalancedA##1##2#2{%
\isbalanced#1#2{##1#2##2}\iftrue #3{##1#2##2}%
\else \def\next{\ensurebalancedA{##1#2##2}}\expandafter\next\fi
}%
\def\next{\ensurebalancedA{#4}}\expandafter\next\fi
}
\def\isbalanced#1#2#3\iftrue{\tmpnum=0 \isbalancedA#1#2#3\isbalanced}
\def\isbalancedA#1#2#3{%
\ifx\isbalanced#3\def\next{\csname ifnum\endcsname\tmpnum=0 }%
\else \def\next{\isbalancedA#1#2}%

Nice, thank you! :-). I guess one needs a \newcount\tmpnum to go with it. – morbusg Aug 13 '14 at 8:45
@morbusg Thanks. I have \tmpnum declared by default. Thus I forgot it. I corrected the code. – wipet Aug 14 '14 at 11:44