7

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.

morbusg
  • 25,490
  • 4
  • 81
  • 162

2 Answers2

8

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.

David Carlisle
  • 757,742
  • So the outer one is expanded before the inner one? That's... gross. – morbusg Aug 09 '14 at 11:27
  • Well how come it works with the \foo then? – morbusg Aug 09 '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 09 '14 at 11:32
  • @morbusg If you want to do delimiter matching such a case you've got to work hard: it's doable but tedious (see xparse for an example which does work at the cost of a lot of effort). – Joseph Wright Aug 09 '14 at 11:33
  • @morbusg see updated answer – David Carlisle Aug 09 '14 at 11:36
  • I'm having a hard time understanding this. I mean, I understand what the issue is, but I don't understand how to sidestep it. Any way I could make it “accidentally work” with the \args without explicitly bracing the arguments when using the macro? :) – morbusg Aug 09 '14 at 11:42
  • 1
    @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 09 '14 at 11:45
  • @Joseph: Thanks, but the L3 syntax is a bit too alien for me to follow. I'll just take your word for it being hard and tedious. :) – morbusg Aug 09 '14 at 11:47
  • @morbusg I'll see if I can write a demo using the same algorithm and primitives: may take a little while! – Joseph Wright Aug 09 '14 at 12:11
  • Now let's see. What happens with \args(bar\args(baz))? I guess the height satisfies condition 1, so the first \args should expand to \bigl(bar\args(baz\bigr)), then the internal \args is expanded, and should get to \bigl(bar\bigl(baz\bigr)\bigr), but the \bigr) that seems to close the internal \args is in fact part of its argument. So we guess the problem somehow comes from the sizing, which prevents the first ) to be taken as the closing of the internal \args's argument as happens with the \foo. In all that, I don't quite see how we get an Extra }. Let's test this with … – MickG Aug 09 '14 at 20:18
  • … a \foo with sizing. Result: Missing delimiter (. inserted). <to be read again> \mathclose l.5 $$\foo(bar\foo(baz) )$$, error detected between the two )s. – MickG Aug 09 '14 at 20:18
  • 1
    @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 09 '14 at 21:01
  • @JosephWright Any news on that “small demo”? – Manuel Sep 28 '14 at 16:20
5

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.

Or your example:

\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}%
          \ifx#3#1\advance\tmpnum by1\fi
          \ifx#3#2\advance\tmpnum by-1\fi
    \fi \next
}
wipet
  • 74,238