%\filename{SynChart.tex}
%\newcommand{\lastedit}{2025-11-24 06:20:20 by stolfi}

% TeX macros for syntax charts
% Adapted from Mike Plass's SAIL-TEX macros (SynChart.TEX)
% New version - using full power of \halign
 
\makeatletter \endlinechar=-1

\def\macrosection{\begingroup\catcode`\ =12\XXmacrosection}
\def\XXmacrosection#1{\message{#1}\endgroup}

\macrosection{ fonts...}

  \def\ARRF{\small\tt}
  \def\BARRF{\tt}

  % The user should also define the fonts \TerminalFont, \KeywordFont,
  %   and \NonTerminalFont.

\macrosection{ space and linebreak control...}

  % These macros control how spaces and line breaks are to be treated:

  \def\IgnoreLinebreaks{\catcode'015=9\endlinechar=-1}
  \def\IgnoreWhiteSpace{\catcode'040=9\catcode'011=9\IgnoreLinebreaks}
  \def\DontIgnoreWhiteSpace{\catcode'040=10\catcode'015=5%
      \catcode'011=10\endlinechar='015}

  \IgnoreWhiteSpace % so the macro definitions may be freely formatted.

  % Because spaces are ignored, we can't write \ifnum \x=0 \Error...\fi
  % since TeX will try to expand \Error until it sees a non-digit.
  % To avoid this, we write \ifnum \x=0 \then ... \fi, where 
  
  \let\then=\relax 

\macrosection{ counting...}

  \def\inc#1{\advance #1 by 1\relax}
  \def\dec#1{\advance #1 by -1\relax}

\macrosection{ dimensions...}

  % Dimensions

  \newdimen\Wid       % Line width
  \newdimen\Eps       % Line half-width
  \newdimen\Unit      % Width of a quarter circle (EXCLUDING brush thickness)
  \newdimen\UPlus     % \Unit plus \Eps
  \newdimen\UMinus    % \unit minus \Eps

  \newdimen\AltSpacing   % Minimum distance between alternatives,

  % Circle size selection

  \def\SmallCircles{
      \chardef\Ichar='010
      \chardef\IIchar='013
      \chardef\IIIchar='012
      \chardef\IVchar='011
      \ComputeDimensions
    }
  \def\BigCircles{
      \chardef\Ichar='014
      \chardef\IIchar='017
      \chardef\IIIchar='016
      \chardef\IVchar='015
      \ComputeDimensions
    }

  \def\ComputeDimensions{
      \Wid=0.05em
      \Eps=0.5\Wid
      \setbox0\hbox{\CIRCF \Ichar}
      \Unit=0.5\wd0
      \UPlus=\Unit \advance \UPlus by \Eps
      \UMinus=\Unit \advance \UMinus by -\Eps
    }

  % Useful spacing macros:

  \def\BackU{\kern -\Unit}
  \def\BackUMinus{\kern -\UMinus}
  \def\BackUU{\kern -2\Unit}
  
  \def\ForwU{\kern \Unit}
  \def\ForwUMinus{\kern \UMinus}
  \def\ForwUU{\kern 2\Unit}

  \def\DnU{\lower \Unit}
  \def\UpU{\raise \Unit}
  
  \def\BackE{\kern -\Eps}
  \def\BackEE{\kern -\Wid}
  \def\ForwE{\kern \Eps}
  \def\ForwEE{\kern \Wid}

  \def\DnE{\lower \Eps}
  \def\DnEE{\lower \Wid}
  \def\UpE{\raise \Eps}
  \def\UpEE{\raise \Wid}

  % A note on spacing:
  
  % The plan is to space things so that the CENTERS of horizontal lines
  % (including the tops and bottoms of symbol boxes) are separated by
  % integer multiples of 2\Unit. This would be easier if every 
  % chart-building macro (\Terminal, \Alternatives, \Arrow, \Empty, etc.)
  % returned its result in a box just big enough to contain it, except
  % that lines along the top and bottom be only half inside (i.e., the ink
  % would extend \Eps outside the box on both sides).
  
  % Unfortunately, it is hard to enforce this on \Fil and related macros.
  % Instead, the chart-building macros construct boxes that are just big
  % enough to contain the diagram, with no extra space around it and with no
  % ink extending out of the bounding box.  Therefore, the size of such boxes
  % is k*2\Unit + \Wid, and in a vertical list they should be separated by
  % \AltSpacing-\Wid worth of glue.
  
\macrosection{ quarter circles...}

  % \I, \II, \III, \IV are the quarter circles in the first, second, third
  % and fourth quadrants, numbered counterclockwise starting with the
  % upper-right one.  
  
  % Each macro produces a box of width =\Unit,
  % height+depth = \Unit+\Wid, with the
  % quarter circle stretching between two opposite corners.  
  
  % Actually, the endpoints of the PATH are on the corners of a square of
  % side \Unit concentric with the box; The box is \Eps taller and deeper to
  % account for the thicknes of the brush.  
  
  % The height and depth are such that the horizontal end of the arc is on
  % the baseline.  So, \I and \II have height=\Eps and depth=\UPlus, whereas
  % \III and \IV have height = \UPlus, depth = \Eps.  

  \def\I{\vbox to \Eps{
      \BackUMinus
      \hbox to\Unit{\ForwUMinus\CIRCF \Ichar \hss}
      \vss
      \hrule height -\Unit depth \UPlus width 0pt
    }}
  
  \def\II{\vbox to \Eps{
      \BackUMinus
      \hbox to \Unit{\BackE\CIRCF \IIchar \hss}
      \vss
      \hrule height -\Unit depth \UPlus width 0pt
    }}

  \def\III{\vbox to\UPlus{
      \ForwE
      \hbox to \Unit{\BackE\CIRCF \IIIchar \hss}
      \vss
      \hrule height 0pt depth \Eps width 0pt
    }}

  \def\IV{\vbox to \UPlus{
      \ForwE
      \hbox to \Unit{\ForwUMinus\CIRCF \IVchar \hss}
      \vss
      \hrule height 0pt depth \Eps width 0pt
    }}

  % End caps for Terminal boxes:
  
  % These macros return boxes with depth=height=\UPlus, width=\Unit,
  % containing a semicircular stroke that begins and ends at two adjacent
  % corners and touches the middle of the opposite side.  The ink protrudes
  % \Wid distance out left and right, but not 
  % top and bottom. 

  \def\LeftRound{\vbox to \UPlus{\CIRCF
      \BackUMinus
      \hbox to \Unit{\BackE\IIchar\hss}\nointerlineskip
      \vss
      \hbox to \Unit {\BackE\IIIchar\hss}\nointerlineskip
      \hrule height -2\Unit depth\UPlus width 0pt
    }}
       
  \def\RightRound{\vbox to \UPlus{\CIRCF
      \BackUMinus
      \hbox to \Unit{\ForwUMinus\Ichar\hss}\nointerlineskip
      \vss
      \hbox to \Unit{\ForwUMinus\IVchar\hss}\nointerlineskip
      \hrule height -2\Unit depth\UPlus width 0pt
    }}
       
  % End caps for Nonterminal boxes:

  \def\LeftSquare
      {\vrule height\UPlus depth\UPlus width \Wid \BackE}
  \def\RightSquare
      {\BackE \vrule height\UPlus depth\UPlus width \Wid}

\macrosection{ arrows...}

  % \LeftGoingArrow, \RightGoingArrow, \BigLeftGoingArrow, and
  % \BigRightGoingArrow always produce arrows pointing in the stated
  % direction.

  % Arrows have height=depth=\Eps, i.e. the arrow tips stick out of the
  % enclosing boxes.
  
  % These arrows use the "<" and ">" characters of font cmtt10

  \newdimen\ArrowLength
  \ArrowLength=0.62em

  \def\LeftGoingArrow{\MakeArrow{\ARRF\lower0.67ex\hbox{<}\hss}}
  \def\RightGoingArrow{\MakeArrow{\hss\ARRF\lower0.67ex\hbox{>}\kern-0.095em}}

  \def\BigLeftGoingArrow{\MakeArrow{\kern-0.095em\BARRF\lower0.67ex\hbox{<}\hss}}
  \def\BigRightGoingArrow{\MakeArrow{\hss\BARRF\lower0.67ex\hbox{>}\kern-0.19em}}

  \def\DownGoingArrow{\hbox to 0pt{\hss\BARRF\char'024\kern-0.095em\hss}}
  
  \def\MakeArrow#1{{
        \LineRule width \ArrowLength
        \kern-\ArrowLength
        \setbox0\hbox to \ArrowLength{#1} \ht0=0pt \dp0=0pt
        \box0
      }}

  % \InputLeftArrow, \InputRightArrow, \OutputLeftArrow, and
  % \OutputRightArrow define the stems that get put on each terminal or
  % nonterminal box.  The appropriate "real" arrow is put on the entry side
  % of each box, and the corresponding "dummy" one is put on the exit side to
  % balance the whole thing.  This means the centers of vertically stacked
  % symbol boxes will be aligned.  

  \def\DontOmitArrows{
      \def\InputLeftArrow{\LeftGoingArrow}
      \def\OutputLeftArrow{\LineRule width \ArrowLength}
      \def\InputRightArrow{\RightGoingArrow}
      \def\OutputRightArrow{\LineRule width \ArrowLength}
    }

  % If the charts get too wide, the following macro can be used to
  % supress all right-going arrows in symbol boxes.
  
  \def\OmitRightArrows{
      \def\InputLeftArrow{\LeftGoingArrow}
      \def\OutputLeftArrow{\LineRule width \ArrowLength}
      \def\InputRightArrow{\LineRule width 2.5\Wid}
      \def\OutputRightArrow{\LineRule width 2.5\Wid}
    }

  % In extreme cases, supress all arrows:
  
  \def\OmitArrows{
      \def\InputLeftArrow{}
      \def\InputRightArrow{}
      \def\OutputLeftArrow{}
      \def\OutputRightArrow{}
    }

  % The internal macros \LeftToRight, \RightToLeft, and \SwitchDirection
  % control the way arrows are pasted on symbol boxes, and the meaning of
  % the \Arrow and \BigArrow user macros: 

  \def\LeftToRight
      {\def\ArrowOnLeftSide{\InputRightArrow}
       \def\ArrowOnRightSide{\OutputRightArrow}
       \def\Arrow{\RightGoingArrow\Empty\ignorespaces}
       \def\BigArrow{\BigRightGoingArrow\Empty\ignorespaces}
       \def\SwitchDirection{\RightToLeft}}

  \def\RightToLeft
      {\def\ArrowOnLeftSide{\OutputLeftArrow}
       \def\ArrowOnRightSide{\InputLeftArrow}
       \def\Arrow{\LeftGoingArrow\Empty\ignorespaces}
       \def\BigArrow{\BigLeftGoingArrow\Empty\ignorespaces}
       \def\SwitchDirection{\LeftToRight}}

% End of input mark:

  \def\StopMark{\hbox{\vrule height 1ex depth 1ex width 0.3ex}}

\macrosection{ symbol boxes...}

  % The next macros make terminal and nonterminal boxes:

  \def\SquareBox#1{\ArrowOnLeftSide\LeftSquare
      \Sandwich{#1}
      \RightSquare\ArrowOnRightSide}
  
  \def\RoundBox#1{\ArrowOnLeftSide\LeftRound
      \Sandwich{#1}
      \RightRound\ArrowOnRightSide}
  
  % The following macros will produce a round or square box containing
  % the given argument, vertically centered. The user can control the
  % vertical positioning of the box contents by including the appropriate
  % strut. For example, \Terminal{+\OpStrut} produces a round box with the
  % plus sign at the exact center (in most fonts).
  
  \def\NonTerminal#1{
      \SquareBox{\NonTerminalFont\ #1\ }
      \ignorespaces}

  \def\Terminal#1{
      \RoundBox{\kern -0.5\Unit \TerminalFont #1\kern-0.5\Unit}
      \ignorespaces}

  \def\Keyword#1{
      \RoundBox{\kern-0.25\Unit\KeywordFont #1\kern-0.25\Unit}
      \ignorespaces}
  
  % \Sandwich puts its argument between two horizontal lines
  % exactly 2\Unit apart, and centers the result vertically.
  % The \halign is there to center the argument horzly if it happens to have
  % negative width (as may be the case for thin terminal symbols).
  
  % The resulting box has height=depth=\UPlus
  
  \def\Sandwich#1{\DnU\vbox
          {\HorzLine\ForwUMinus
          \vbox to 0pt{\vss
             \halign{\hfil##\hfil\cr\null\cr\hbox{#1}\cr}
             \vss\null
          }
          \ForwUMinus
          \HorzLine
         }}

\macrosection{ random macros...}

  % Some useful formatting primitives used internally:

  \def\LineRule{\vrule height \Eps depth \Eps} % Horz line in horz mode
  \def\HorzLine{\hrule height \Eps depth \Eps} % Horz line in vert mode
  \def\VertLine#1{\BackE \vrule #1 width \Wid \BackE} % Vert line in horz mode

  % A strut with height and width like those of box 0:
  
  \def\BZStrut{\vrule height \ht0 depth \dp0 width 0pt}

  \def\LinetoTop{\VertLine{ depth -\Unit}}
  \def\LinetoBot{\VertLine{ height -\Unit}}
  \def\FilVertLine{\BackE \vrule width \Wid \BackE}

  % These macros are meant to be used by the User:

  % \Strut is a strut for text with ascenders and descenders;
  % \OpStrut is a strut that is good for centering math operators
  % \CapStrut is a strut for all-uppercase text
  
  \def\Strut{{\setbox0\hbox{Bg}\BZStrut}}
  \def\OpStrut{{\setbox0\hbox{(}\BZStrut}}
  \def\CapStrut{{\setbox0\hbox{B}\BZStrut}}
  
  % Say things like \HLine 10pt plus 20pt to get stretchable lines
  % in horizontal mode
  
  \def\HLine{\leaders\HorzLine\hskip}

  % Use \Empty in empty alternatives, etc:
  
  \def\Empty{\LineRule width 0pt\ignorespaces}     % For empty alternatives
  \def\ULine{\LineRule width \Unit\ignorespaces}   % A short horizontal line
  \def\UULine{\LineRule width 2\Unit\ignorespaces} % Double that
  
  \def\Fil{\HLine 0pt plus 1fil\ignorespaces}     % A stretchable horz line
  \def\Fill{\HLine 0pt plus 1fill\ignorespaces}   % A more stretchable one
  \def\FilNeg{\HLine 0pt plus -1fil\ignorespaces} % Cancels a \Fil

  \macrosection{ chart sections...}

  % Use \Define to start a section of the diagram:

  \def\LeftHandSide#1{
      \setbox0\hbox{\NonTerminalFont
          \vrule height \UPlus depth \UPlus width 0pt
          #1}
      \dimen0=\wd0
      \leavevmode
      \vbox{\box0\kern -\Wid \hbox{\LineRule width \dimen0}}
    }

  \newbox\LineBox
  \newif\ifgroundlevel  % True only at the top level in a \Define

  \def\Define#1{\par
      \message{ #1...}
      \groundleveltrue
      \vfil\penalty-50\vfilneg
      \vskip \AltSpacing
      \vskip \AltSpacing
      \vskip -\Wid
      \setbox\LineBox\hbox\bgroup
        \LeftHandSide{#1}
        \UULine
        \ignorespaces
    }

  % And use \EndDef to end it:

  \def\EndDef{
        \ifgroundlevel \else \InvalidUseOfNewLine \fi
        \egroup  % Close the \LineBox
      \hbox to \hsize{
          \unhbox\LineBox
          \DefaultFil
          \BigRightGoingArrow
        }
    }
  \def\EndDefStop{
        \ifgroundlevel \else \InvalidUseOfNewLine \fi
        \egroup  % Close the \LineBox
      \hbox to \hsize{
          \unhbox\LineBox
          \DefaultFil
          \StopMark
        }
    }

  % \DefaultFil is a stretchable line that is (almost) infinitely
  % stiffer than \Fil. It is the default left/right filling in
  % \Define, \Alternatives, etc. but is overriden by user's \Fil.
  
  \def\DefaultFil{\leaders\HorzLine\hskip 0pt plus 1000pt}

\macrosection{ line breaks...}

  % The minimum line length for \NewLine and \OptionalLines:

  \newdimen\MinLineWidth 

  % If a section gets too wide, \NewLine will continue the chart on a new line:

  \def\NewLine{\OptionalLines{}}
  
  % In general, \OptionalLines{\Line{Foo}\Line{Bar}...}
  % is equivalent to (but nicer than) the sequence
  %   \NewLine
  %   \Alternatives{\Middle{Foo}\Lower{}}\NewLine
  %   \Alternatives{\Middle{Bar}\Lower{}}\NewLine
  %   ...

  \def\OptionalLines#1{
        \ifgroundlevel \else \InvalidUseOfNewLine \fi
        % Close the \LineBox, print it, and print the 
        % connector to the next line
      \BreakCurrentLine

      % Typeset optional lines:
      \begingroup
        \groundlevelfalse
        \def\Line{\DoOptionalLine}
        % Process the lines:
        \ignorespaces #1
      \endgroup
      
      % Finally, begin the next line:
      \BeginNewLine
      \ignorespaces
    }

  % These internal procedures are acalled by 
  % \NewLine and \OptionalLines:
  
  \def\BreakCurrentLine{
        \DefaultFil\ULine\I\LinetoBot\ForwU
        \egroup % Close the \LineBox
      \halign{##\cr

        % Insert a horizontal strut to ensure minimum line length
        \vrule height0pt depth0pt width\MinLineWidth \cr

        % Insert the line:
        \unhbox\LineBox\cr

        % Insert the connector to the next line:
        \dimen0=\AltSpacing \advance\dimen0 by -\Eps
          \advance\dimen0 by \Unit
          \vrule height \dimen0 depth \dimen0 width 0pt 
          \ForwUU\ForwUU\LinetoBot\II
          \Fil\BigLeftGoingArrow\Fil
          \IV\LinetoTop\ForwU\cr
      }
    }
    
  \def\DoOptionalLine#1{
      % Typeset the optional line:
      \setbox0\hbox{\ForwUU\ForwUU\FilVertLine
          \III
          \ULine#1\DefaultFil\ULine\I\LinetoBot\ForwU
        }

      \halign{##\cr

        % Insert horizontal strut to guarantee minimum line width:
        \vrule height0pt depth0pt width\MinLineWidth \cr
      
        % Now insert the stuff in the line:
        \unhbox0 \cr

        % Now the connector to the next line:
        \dimen0=\AltSpacing \advance\dimen0 by -\Eps
          \advance\dimen0 by \Unit
          \dimen2=\dimen0 \advance\dimen2 by \Unit
          \vrule height \dimen0 depth \dimen2 width 0pt 
          \ForwUU\ForwUU\FilVertLine\II
          \Fil\BigLeftGoingArrow\Fil
          \IV\LinetoTop\ForwU\cr
          
      }
      \ignorespaces
    }

  \def\BeginNewLine{
      \setbox\LineBox\hbox\bgroup
        \ForwUU\ForwUU\LinetoTop\III\ULine
    }

\macrosection{ alternatives...}

  % Both \Alternatives and \Repeat work in roughly the same fashion, so this
  % description applies to them both.  The single argument is composed of the
  % subcomponents, each one enclosed in braces and preceded by a control
  % sequence.  These control sequences get defined as local macros, and grab
  % the subcomponents as parameters.  
  
  % Actually, this happens twice: the first time the argument is interpreted,
  % the local macros just ignore their parameter, and the second time
  % through, the real work is done.  The first pass is used to count the
  % subcomponents, since the last one has to be treated specially, and also
  % does some validation of the arguments.  
  
  % If the structure of the macro isn't tricky enough for you, you can try to
  % understand the way the result is built up out of boxes.  Here is the
  % basic idea: the alternatives are piled up as a \halign, each preceded by
  % a left connector and a \Fil, and followed by a \Fil and the right
  % connector.  The midlines of the vertical wires are right on the edges of
  % the \halign.
  
  % The height+depth of the upper alternatives, (and the height of the middle
  % one) are added and stored in a local variable.
  % The result of the \halign is \vtop ped and raised by that amount.  

  % The \halign is needed since each row has to be expanded to the maximum
  % width of everything in the middle. It also implies that empty alternatives
  % have their height or depth (which is usually \Eps) augmented by an extra
  % \Unit, to accomodate the connectors.

  % To get the right vertical spacing, some entries may have an extra
  % are preceded by a strut whose height is 
  % \AltSpacing plus the `natural' height of the current entry,
  % and minus the amount of blank space already inserted at the bottom
  % of the previous one. The latter is nonero only if the previous alternative
  % has a `natural depth' that is too small to accomodate the side connectors,
  % as for example in \Upper{\Empty},
  % \Upper{\Repeat{\Upper{...}\Middle{\Empty}}}, or omitted \Middle.
  % is inserted between consecultive alternatives. Also, between 
  % every two rows of the \halign we insert a \kern -\Wid, to
  % compensate for the thickness of lines and ensure seamless connections.

  % Since each row of an \halign is a separate group, some tricks are
  % required to propagate information from one row to the next and
  % yet to prevent nested \Alternatives from interacting in undesired
  % ways. Those valueas are normally stored in the global registers
  % \AltCtr, \ChartHeight, and \PrevSpace. 
  % While typesetting one branch (which may involve
  % recursive calls to % \Alternatives) those values are locally assigned
  % to scratch registers, and then re-assigned to the global 
  % ones after the stuff in that branch has been made into 
  % a box.

  % These reagisters should be assigned with \global:

  \newcount\AltCtr       % Alternative counter
  \newdimen\ChartHeight  % Height of current chart above its baseline
  \newdimen\PrevSpace    % `Actual' minus `natural' depth of previous alt.

  % These should be defined only locally:
  
  \newcount\AltNum     % Number of alternatives in current chart

  \newcount\SaveAltCtr
  \newdimen\SaveChartHeight
  \newdimen\SavePrevSpace
  
  \def\SaveData{
      \SaveAltCtr=\AltCtr
      \SaveChartHeight=\ChartHeight
      \SavePrevSpace=\PrevSpace
    }
  
  \def\RestoreData{
      \global\AltCtr=\SaveAltCtr
      \global\ChartHeight=\SaveChartHeight
      \global\PrevSpace=\SavePrevSpace
    }

  \newif\ifupper  % Flags that tell the type of previous
  \newif\iflower  %   alternative
  
  \def\Alternatives#1{
      \begingroup

          \groundlevelfalse
          
          % First, count alternatives:
          \AltNum=0 \upperfalse \lowerfalse
          \def\Upper##1{\iflower \BadUseOfUpper \fi
                        \inc\AltNum \uppertrue \lowerfalse}
          \def\Middle##1{\iflower \BadUseOfMiddle \fi
                         \inc\AltNum\upperfalse \lowertrue}
          \def\Lower##1{\inc\AltNum\upperfalse \lowertrue}
          #1

          % Now typeset them:
          \global\AltCtr=\AltNum
          \global\ChartHeight=0pt
          \global\PrevSpace=\AltSpacing
          \upperfalse \lowerfalse

          \def\Upper{\UpperAlternative}
          \def\Middle{\MiddleAlternative}
          \def\Lower{\MaybeOmittedMiddleAlternative\LowerAlternative}

          \setbox0\vtop{\null\halign{##\cr
              \ignorespaces#1
              \MaybeOmittedMiddleAlternative
              \Empty\cr
            }}
          \hbox{\ForwU\raise\ChartHeight \box0\ForwU}

      \endgroup
    \ignorespaces}

\macrosection{ repeat...}

  \def\Repeat#1{
      \begingroup

          \groundlevelfalse
          
          % First count the alternatives:
          \AltNum=0 \upperfalse \lowerfalse
          \def\Upper##1{\iflower \BadUseOfUpper \fi
                  \inc\AltNum\uppertrue \lowerfalse}
          \def\Middle##1{\iflower \BadUseOfMiddle \fi
                  \inc\AltNum\upperfalse \lowertrue}
          \def\Lower##1{\iflower \else \BadUseOfLower \fi
                  \inc\AltNum\upperfalse \lowertrue}
          #1
          \iflower \else \NoMiddle \fi
          
          % Now typeset them:
          \global\AltCtr=\AltNum
          \global\ChartHeight=0pt
          \global\PrevSpace=\AltSpacing
          \upperfalse \lowerfalse
          
          \def\Upper{\SwitchDirection\UpperAlternative}
          \def\Middle{\RepeatBody}
          \def\Lower{\SwitchDirection\LowerAlternative}
          
          \setbox0\vtop{\null\halign{##\cr
              \ignorespaces#1
              \Empty\cr
            }}
          \hbox{\raise\ChartHeight \box0}

      \endgroup
    \ignorespaces}

\macrosection{ internals of the above...}

  \def\AltStrut{
      % Assumes \box0 tightly contains the current alternative,
      % minus its side connectors, and \PrevSpace contains
      % the actual minus natural depth of the previous alternative.

      % Produces a vertical strut that ensures \AltSpacing-\Wid
      % whitespace between this and the previous alternative.

      \ifdim \PrevSpace<\AltSpacing \then
        \dimen0=\AltSpacing \advance\dimen0 by -\PrevSpace
        \advance\dimen0 by \ht0
        \vrule height \dimen0 depth 0pt width 0pt
      \fi
    }
      
  \def\UpperAlternative#1{
      \global\dec\AltCtr
      
      % Typeset the argument into \box0, without connectors and vert space.
      % Beware of recursive calls.
      \SaveData \setbox0\hbox{\Empty\ignorespaces#1} \RestoreData
      
      % Now add top spacer, add connecting arcs, and compute bottom space 
      % added because of them:
      \dimen2=\dp0   % Save `natural' depth
      \setbox0\hbox{\II\DefaultFil \AltStrut \unhbox0\relax\DefaultFil\I}
      \global\PrevSpace=\dp0 
      \global\advance\PrevSpace by -\dimen2
      
      % Update total chart height:
      \global\advance \ChartHeight by \ht0
      \global\advance \ChartHeight by \dp0

      % Now add side rails, and pass it to the \halign:
      \ifupper \FilVertLine \else \LinetoBot \fi
      \unhbox0\relax
      \ifupper \FilVertLine \else \LinetoBot \fi
      \cr
      
      % Backspace to account for line thickness:
      \noalign{\kern -\Wid}
      \global\advance \ChartHeight by -\Wid
      
      \uppertrue\lowerfalse\ignorespaces
    }

  \def\MiddleAlternative#1{
      \global\dec\AltCtr
      
      % Typeset the argument into \box0, without connectors and vert space:
      \SaveData \setbox0\hbox{\Empty\ignorespaces#1} \RestoreData
      
      % Now add top spacer, add connecting arcs, and compute bottom space 
      % added because of them:
      \dimen2=\dp0   % Save `natural' depth
      \setbox0\hbox{
          \ifupper \BackU\IV \fi
          \ifnum \AltCtr>0 \then \BackU\I \fi
          \BackU\UULine
          \DefaultFil \AltStrut \unhbox0\relax \DefaultFil
          \UULine\BackU
          \ifupper \III\BackU \fi
          \ifnum \AltCtr>0 \then \II\BackU \fi
        }

      \global\PrevSpace=\dp0 
      \global\advance\PrevSpace by -\dimen2
      
      % Update total chart height:
      \global\advance\ChartHeight by \ht0

      % Now add side rails, and pass it to the \halign:
      \ifupper \LinetoTop \fi
      \ifnum \AltCtr>0 \then \LinetoBot \fi
      \unhbox0 \relax
      \ifupper \LinetoTop \fi
      \ifnum \AltCtr>0 \then \LinetoBot \fi
      \cr
      
      % Backspace to account for line thickness:
      \noalign{\kern -\Wid}
      \upperfalse\lowertrue\ignorespaces
    }

\def\MaybeOmittedMiddleAlternative{
    \let\next=\relax
    \iflower \else \let\next=\OmittedMiddleAlternative \fi
    \next
  }

\def\OmittedMiddleAlternative{

      \setbox0\hbox{
          \vrule height \Eps depth\Eps width 0pt
          \ifupper \BackU\IV\LinetoTop \fi
          \ifnum \AltCtr>0 \then \BackU\I\LinetoBot \fi
          \ForwU
          
          \ifupper
            % Put a strut to ensure correct spacing relative to previous
            % alternative.  Try to distribute the extra space equally on both
            % sides of the baseline: 
            \dimen0=\AltSpacing 
            \advance\dimen0 by -\PrevSpace 
            \advance\dimen0 by -2\Unit      % height+depth of self
            \dimen0 =0.5\dimen0
            \ifdim \dimen0<0pt \then \dimen0=0pt \fi
            % Round \dimen0 to a multiple of \Unit:
            \advance\dimen0 by 0.5\Unit 
              \divide\dimen0 by \Unit \multiply \dimen0 by \Unit
            % Now put the qppropriate strut:
            \advance\dimen0 by \Unit
            \vrule height \dimen0 depth 0pt width 0pt
          \fi

          \hfil
          
          \ForwU
          \ifupper \LinetoTop\III\BackU \fi
          \ifnum \AltCtr>0 \then \LinetoBot\II\BackU \fi
        }

      % Update white space at bottom of previous alternative:
      \global\advance\PrevSpace by \ht0
      \global\advance\PrevSpace by \dp0
      \global\advance\PrevSpace by -\Wid
      
      % Update total chart height
      \global\advance\ChartHeight by \ht0

      % Now pass the stuff to the \halign:
      \unhbox0\relax
      \cr
      % Backspace to compensate for line thickness:
      \noalign{\kern -\Wid}
    }

  \def\RepeatBody#1{
      \global\dec\AltCtr

      % Typeset the argument into \box0, without connectors and vert space:
      \SaveData \setbox0\hbox{\Empty\ignorespaces#1} \RestoreData 
      
      % Now add top spacer, add connecting arcs, and compute bottom space 
      % added because of them:
      \dimen2=\dp0   % Save `natural' depth
      \setbox0\hbox{
          \ifupper \III\BackU \fi
          \ifnum \AltCtr>0 \then \II\BackU\fi
          \ULine \DefaultFil \AltStrut \unhbox0\relax \DefaultFil \ULine
          \ifupper \BackU\IV \fi
          \ifnum \AltCtr>0 \then \BackU\I \fi
        }

      \global\PrevSpace=\dp0 
      \global\advance\PrevSpace by -\dimen2
      
      % Update total chart height:
      \global\advance\ChartHeight by \ht0

      % Now add side rails, and pass it to the \halign:
      \ifupper \LinetoTop \fi
      \ifnum \AltCtr>0 \then \LinetoBot \fi
      \unhbox0
      \ifupper \LinetoTop  \fi
      \ifnum \AltCtr>0 \then \LinetoBot \fi
      \cr
      % Backspace to compensate for line thickness:
      \noalign{\kern -\Wid}
      \upperfalse\lowertrue\ignorespaces
    }

  \def\LowerAlternative#1{
      \global\dec\AltCtr

      % Typeset the argument into \box0, without connectors and vert space:
      \SaveData \setbox0\hbox{\Empty\ignorespaces#1} \RestoreData 
      
      % Now add top spacer, add connecting arcs, and compute bottom space 
      % added because of them:
      \dimen2=\dp0   % Save `natural' depth
      \setbox0\hbox{\III\DefaultFil\AltStrut \unhbox0\relax \DefaultFil\IV}
      \global\PrevSpace=\dp0 
      \global\advance\PrevSpace by -\dimen2
      
      % Now add side rails, and pass it to the \halign:
      \ifnum \AltCtr=0 \then \LinetoTop \else \FilVertLine \fi
      \unhbox0 \relax
      \ifnum \AltCtr=0 \then \LinetoTop \else \FilVertLine \fi
      \cr
      % Backspace to compensate for line thickness:
      \noalign{\kern -\Wid}
      \upperfalse\lowertrue\ignorespaces
    }

\macrosection{ horz alternatives...}

  % Horizontal alternatives work in much the same way as the others, but is a
  % little simple because we don't have to worry about a \Middle.
  % We use a \halign with three rows (one for the stuff and
  % two for the top and bottom connections), and keep track of the maximum
  % height among the branches. The result of the \halign is \vtop ped and
  % then raised by that amount (plus the height of the top connectors).

  % The same tricks used to handle \AltCtr and \ChartHeight in the vertical
  % case are also used here.

  \def\HorzAlternatives#1{
      \begingroup

          \groundlevelfalse
          
          % First, count the alternatives:
          \AltNum=0
          \def\Alternative##1{\inc\AltNum}
          #1 
          \ifnum \AltNum<2 \then \NeedTwoOrMoreAlternatives \fi

          % Now typeset them:
          \global\AltCtr=\AltNum
          \global\ChartHeight=0pt
          \def\Alternative{\DoHorzAlternative}
          \setbox0\vtop{\null
            \halign{##&&##\cr
              % Now generate the top row of the \halign
              \global\count1=\AltNum
              \TopHorzAlternativeConnectors 
              \cr \noalign{\kern -\Wid}
              % Now the middle row:
              \ignorespaces#1\cr\noalign{\kern -\Wid}
              % Now the bottom connectors:
              \global\count1=\AltNum
              \BotHorzAlternativeConnectors\cr
            }}
          % Account for top connector height:
          \global\advance\ChartHeight by \Unit
          \hbox{\ForwU\raise\ChartHeight\box0 \ForwU}
      \endgroup
    \ignorespaces}

  \def\TopHorzAlternativeConnectors{
      & \ifnum \count1=\AltNum \then \II \fi 
        \ifnum \count1>1 \then \Fil \fi
      & 
      \ifnum \count1>1 \then 
        \I \ifnum \count1>2 \then \BackU\ULine \fi
        \let\next=\TopHorzAlternativeConnectors
      \else
        \let\next=\relax
      \fi
      \global\advance\count1 by -1\relax \next
     }      
            
  \def\HorzAltStrut{
      % Assumes \box0 is a tight box enclosing the alternative, without
      % its connecting arcs. Produces a strut that ensures \AltSpacing
      % whitespace between the alternative and the top and bottom railing.
      % Assumes the top and bottom connectors already include one unit
      % worth of whitespace, to accomaodate the circular arcs.

      \dimen0=\ht0 \advance\dimen0 by \AltSpacing 
      \advance\dimen0 by -\Unit
      \dimen2=\dp0 \advance\dimen2 by \AltSpacing 
      \advance\dimen2 by -\Unit 
      \vrule height \dimen0 depth \dimen2 width 0pt
    }
  
  \def\DoHorzAlternative#1{
      & 
     
      % First, typeset the alternative without connectors and struts into 
      % \box0. Beware of recursive calls:
      \SaveAltCtr=\AltCtr \SaveChartHeight=\ChartHeight
      \setbox0\hbox{\Empty\ignorespaces#1} 
      \global\AltCtr=\SaveAltCtr \global\ChartHeight=\SaveChartHeight
     
      % Now add connectors and vertical spacers:
      \setbox0\hbox{
          \ifnum \AltCtr=\AltNum \then \BackU\IV\BackU\UULine \else \III \fi
          \HorzAltStrut \unhbox0
          \ifnum \AltCtr>1 \then \I \else \UULine\BackU\II\BackU \fi
        }
      
      % Now update the maximum height:
      \ifdim \ht0>\ChartHeight \then \global\ChartHeight=\ht0 \fi

      % Finally, add vertical connections and pass it to the \halign:
      \LinetoTop \unhbox0 \LinetoBot
      &
      \hfil
      \global\dec\AltCtr
    }

  \def\BotHorzAlternativeConnectors{
      & 
      \ifnum \count1=\AltNum \then 
        \hfil
      \else 
        \Fil \ifnum \count1=1 \then \IV \fi
      \fi
      &
      \ifnum \count1>1 \then 
        \III \let\next=\BotHorzAlternativeConnectors
        \ifnum \count1<\AltNum \then\BackU\ULine \fi
      \else
        \let\next=\relax
      \fi
      \global\advance\count1 by -1\relax \next
     }      

\macrosection{ syntax chart...}

  % Usage: \SyntaxChart
  
  \def\SyntaxChart{
      \par
      \lineskip0pt
      \baselineskip-0.232ex
      \font\CIRCF=lcirclew10 \relax  % A circle font

      % Defaults
      \MinLineWidth=0pt
      \DontOmitArrows
      \SmallCircles
      \AltSpacing=2\Unit\relax
      \DontIgnoreWhiteSpace % so the user can space significantly

      \parfillskip 0pt plus 10000pt % stiffer than the \Define-\EndDef glue
      \LeftToRight
      \IgnoreLinebreaks
    }

\macrosection{ done.}
  
\makeatother \endlinechar=`\^^M

% END OF FILE

