%\filename{SynChart.tex} %\edited{by stolfi on Fri Jan 10 15:29:46 1986} % TeX macros for syntax charts % Adapted from Mike Plass's SAIL-TEX macros (SynChart.TEX) % New version - using full power of \halign \def\macrosection{\begingroup\catcode`\ =12\XXmacrosection} \def\XXmacrosection#1{\message{#1}\endgroup} \macrosection{ fonts...} \font\CIRCF=circlew10 \relax % A circle font \font\ARRF=amtt10 \relax \font\BARRF=amtt10 scaled \magstep2 \relax % 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 \newcount\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.75pt \Eps=0.5\Wid \setbox0\hbox{\CIRCF \Ichar} \Unit=0.5\wd0 \UPlus=\Unit \advance \UPlus by \Eps } % Useful spacing macros: \def\BackU{\kern -\Unit} \def\BackUU{\kern -2\Unit} \def\ForwU{\kern \Unit} \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*\Unit-\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{ \BackU\ForwE \hbox to\Unit{\ForwU\BackE\CIRCF \Ichar \hss} \vss \hrule height -\Unit depth \UPlus width 0pt }} \def\II{\vbox to \Eps{ \BackU\ForwE \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{\BackE\ForwU\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 \BackU\ForwE \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 \BackU\ForwE \hbox to \Unit{\ForwU\BackE\Ichar\hss}\nointerlineskip \vss \hbox to \Unit{\ForwU\BackE\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=6.5pt \def\LeftGoingArrow{\MakeArrow{\ARRF\lower3.1pt\hbox{<}\hss}} \def\RightGoingArrow{\MakeArrow{\hss\ARRF\lower3.17pt\hbox{>}\kern-1pt}} \def\BigLeftGoingArrow{\MakeArrow{\kern-1pt\BARRF\lower4.25pt\hbox{<}\hss}} \def\BigRightGoingArrow{\MakeArrow{\hss\BARRF\lower4.3pt\hbox{>}\kern-2pt}} \def\DownGoingArrow{\hbox to 0pt{\hss\BARRF\char'024\kern-1pt\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}} \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\BackE\ForwU \vbox to 0pt{\vss \halign{\hfil##\hfil\cr\null\cr\hbox{#1}\cr} \vss\null } \ForwU\BackE \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\Unit \vskip \AltSpacing\Unit \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 } } % \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\Unit \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\Unit \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)*\Unit 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\Unit \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\Unit \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\Unit-\Wid % whitespace between this and the previous alternative. \ifdim \PrevSpace<\AltSpacing\Unit \then \dimen0=\AltSpacing\Unit \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\Unit \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\Unit % 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\Unit \advance\dimen0 by -\Unit \dimen2=\dp0 \advance\dimen2 by \AltSpacing\Unit \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...} \def\SyntaxChart{ \par \lineskip0pt \baselineskip-1pt \parfillskip 0pt plus 10000pt % stiffer than the \Define-\EndDef glue \LeftToRight \IgnoreLinebreaks } \macrosection{ defaults...} \AltSpacing=2 \MinLineWidth=0pt \DontOmitArrows \SmallCircles \DontIgnoreWhiteSpace % so the user can space significantly \macrosection{ done.} % END OF FILE