\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,
% 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\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 }}
% \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.
% 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:
% 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).
% \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).
% 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:
% \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
% 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
\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
% 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
\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 }
\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
% 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.
% 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\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