diff options
author | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
commit | 487ca4806cc046076293cf6cc5fbba0db282bac7 (patch) | |
tree | 847b412ab5158dd7bdd7ed7e5a4cc3fbca94be32 /Build/source/texk/web2c/mp.web | |
parent | a3d3111bfe26b8e5f5bc6049dfb2a4ca2edc7881 (diff) |
texk 1
git-svn-id: svn://tug.org/texlive/trunk@1485 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/web2c/mp.web')
-rw-r--r-- | Build/source/texk/web2c/mp.web | 23247 |
1 files changed, 23247 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/mp.web b/Build/source/texk/web2c/mp.web new file mode 100644 index 00000000000..93481bc1598 --- /dev/null +++ b/Build/source/texk/web2c/mp.web @@ -0,0 +1,23247 @@ +% $Id: mp.web,v 1.5 2005/06/22 17:06:28 olaf Exp $ +% MetaPost, by John Hobby. Public domain. + +% Much of this program was copied with permission from MF.web Version 1.9 +% It interprets a language very similar to D.E. Knuth's METAFONT, but with +% changes designed to make it more suitable for PostScript output. + +% TeX is a trademark of the American Mathematical Society. +% METAFONT is a trademark of Addison-Wesley Publishing Company. +% PostScript is a trademark of Adobe Systems Incorporated. + +% Here is TeX material that gets inserted after \input webmac +\def\hang{\hangindent 3em\noindent\ignorespaces} +\def\textindent#1{\hangindent2.5em\noindent\hbox to2.5em{\hss#1 }\ignorespaces} +\def\PASCAL{Pascal} +\def\ps{PostScript} +\def\ph{\hbox{Pascal-H}} +\def\psqrt#1{\sqrt{\mathstrut#1}} +\def\k{_{k+1}} +\def\pct!{{\char`\%}} % percent sign in ordinary text +\font\tenlogo=logo10 % font used for the METAFONT logo +\font\logos=logosl10 +\def\MF{{\tenlogo META}\-{\tenlogo FONT}} +\def\MP{{\tenlogo META}\-{\tenlogo POST}} +\def\<#1>{$\langle#1\rangle$} +\def\section{\mathhexbox278} +\let\swap=\leftrightarrow +\def\round{\mathop{\rm round}\nolimits} +\mathchardef\vb="026A % synonym for `\|' + +\def\(#1){} % this is used to make section names sort themselves better +\def\9#1{} % this is used for sort keys in the index via @@:sort key}{entry@@> + +\outer\def\N#1. \[#2]#3.{\MN#1.\vfil\eject % begin starred section + \def\rhead{PART #2:\uppercase{#3}} % define running headline + \message{*\modno} % progress report + \edef\next{\write\cont{\Z{\?#2]#3}{\modno}{\the\pageno}}}\next + \ifon\startsection{\bf\ignorespaces#3.\quad}\ignorespaces} +\let\?=\relax % we want to be able to \write a \? + +\def\title{MetaPost} +\def\topofcontents{\hsize 5.5in + \vglue -30pt plus 1fil minus 1.5in + \def\?##1]{\hbox to 1in{\hfil##1.\ }} + } +\def\botofcontents{\vskip 0pt plus 1fil minus 1.5in} +\pageno=3 +\def\glob{13} % this should be the section number of "<Global...>" +\def\gglob{20, 26} % this should be the next two sections of "<Global...>" + +@* \[1] Introduction. +This is \MP, a graphics-language processor based on D. E. Knuth's \MF. +The \PASCAL\ program that follows defines a standard version +@:PASCAL}{\PASCAL@> +of \MP\ that is designed to be highly portable so that identical output +will be obtainable on a great variety of computers. + +The main purpose of the following program is to explain the algorithms of \MP\ +as clearly as possible. As a result, the program will not necessarily be very +efficient when a particular \PASCAL\ compiler has translated it into a +particular machine language. However, the program has been written so that it +can be tuned to run efficiently in a wide variety of operating environments +by making comparatively few changes. Such flexibility is possible because +the documentation that follows is written in the \.{WEB} language, which is +at a higher level than \PASCAL; the preprocessing step that converts \.{WEB} +to \PASCAL\ is able to introduce most of the necessary refinements. +Semi-automatic translation to other languages is also feasible, because the +program below does not make extensive use of features that are peculiar to +\PASCAL. + +A large piece of software like \MP\ has inherent complexity that cannot +be reduced below a certain level of difficulty, although each individual +part is fairly simple by itself. The \.{WEB} language is intended to make +the algorithms as readable as possible, by reflecting the way the +individual program pieces fit together and by providing the +cross-references that connect different parts. Detailed comments about +what is going on, and about why things were done in certain ways, have +been liberally sprinkled throughout the program. These comments explain +features of the implementation, but they rarely attempt to explain the +\MP\ language itself, since the reader is supposed to be familiar with +{\sl The {\logos METAFONT\/}book} as well as the manual +@.WEB@> +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +{\sl A User's Manual for MetaPost}, Computing Science Technical Report 162, +AT\AM T Bell Laboratories. + +@ The present implementation is a preliminary version, but the possibilities +for new features are limited by the desire to remain as nearly compatible +with \MF\ as possible. + +On the other hand, the \.{WEB} description can be extended without changing +the core of the program, and it has been designed so that such +extensions are not extremely difficult to make. +The |banner| string defined here should be changed whenever \MP\ +undergoes any modifications, so that it will be clear which version of +\MP\ might be the guilty party when a problem arises. +@^extensions to \MP@> +@^system dependencies@> + +@d banner=='This is MetaPost, Version 0.901' {printed when \MP\ starts} +@d metapost_version=="0.901" + +@ Different \PASCAL s have slightly different conventions, and the present +@!@:PASCAL H}{\ph@> +program is expressed in a version of \PASCAL\ that D. E. Knuth used for \MF. +Constructions that apply to +this particular compiler, which we shall call \ph, should help the +reader see how to make an appropriate interface for other systems +if necessary. (\ph\ is Charles Hedrick's modification of a compiler +@^Hedrick, Charles Locke@> +for the DECsystem-10 that was originally developed at the University of +Hamburg; cf.\ {\sl SOFTWARE---Practice \AM\ Experience \bf6} (1976), +29--42. The \MP\ program below is intended to be adaptable, without +extensive changes, to most other versions of \PASCAL\ and commonly used +\PASCAL-to-C translators, so it does not fully +@!@:C@> +use the admirable features of \ph. Indeed, a conscious effort has been +made here to avoid using several idiosyncratic features of standard +\PASCAL\ itself, so that most of the code can be translated mechanically +into other high-level languages. For example, the `\&{with}' and `\\{new}' +features are not used, nor are pointer types, set types, or enumerated +scalar types; there are no `\&{var}' parameters, except in the case of files; +there are no tag fields on variant records; there are no |real| variables; +no procedures are declared local to other procedures.) + +The portions of this program that involve system-dependent code, where +changes might be necessary because of differences between \PASCAL\ compilers +and/or differences between +operating systems, can be identified by looking at the sections whose +numbers are listed under `system dependencies' in the index. Furthermore, +the index entries for `dirty \PASCAL' list all places where the restrictions +of \PASCAL\ have not been followed perfectly, for one reason or another. +@!@^system dependencies@> +@!@^dirty \PASCAL@> + +@ The program begins with a normal \PASCAL\ program heading, whose +components will be filled in later, using the conventions of \.{WEB}. +@.WEB@> +For example, the portion of the program called `\X\glob:Global +variables\X' below will be replaced by a sequence of variable declarations +that starts in $\section\glob$ of this documentation. In this way, we are able +to define each individual global variable when we are prepared to +understand what it means; we do not have to define all of the globals at +once. Cross references in $\section\glob$, where it says ``See also +sections \gglob, \dots,'' also make it possible to look at the set of +all global variables, if desired. Similar remarks apply to the other +portions of the program heading. + +Actually the heading shown here is not quite normal: The |program| line +does not mention any |output| file, because \ph\ would ask the \MP\ user +to specify a file name if |output| were specified here. +@^system dependencies@> + +@d mtype==t@&y@&p@&e {this is a \.{WEB} coding trick:} +@f mtype==type {`\&{mtype}' will be equivalent to `\&{type}'} +@f type==true {but `|type|' will not be treated as a reserved word} + +@p @t\4@>@<Compiler directives@>@/ +program MP; {all file names are defined dynamically} +label @<Labels in the outer block@>@/ +const @<Constants in the outer block@>@/ +mtype @<Types in the outer block@>@/ +var @<Global variables@>@/ +@# +procedure initialize; {this procedure gets things started properly} + var @<Local variables for initialization@>@/ + begin @<Set initial values of key variables@>@/ + end;@# +@t\4@>@<Basic printing procedures@>@/ +@t\4@>@<Error handling procedures@>@/ + +@ The overall \MP\ program begins with the heading just shown, after which +comes a bunch of procedure declarations and function declarations. +Finally we will get to the main program, which begins with the +comment `|start_here|'. If you want to skip down to the +main program now, you can look up `|start_here|' in the index. +But the author suggests that the best way to understand this program +is to follow pretty much the order of \MP's components as they appear in the +\.{WEB} description you are now reading, since the present ordering is +intended to combine the advantages of the ``bottom up'' and ``top down'' +approaches to the problem of understanding a somewhat complicated system. + +@ Three labels must be declared in the main program, so we give them +symbolic names. + +@d start_of_MP=1 {go here when \MP's variables are initialized} +@d end_of_MP=9998 {go here to close files and terminate gracefully} +@d final_end=9999 {this label marks the ending of the program} + +@<Labels in the out...@>= +start_of_MP@t\hskip-2pt@>, end_of_MP@t\hskip-2pt@>,@,final_end; + {key control points} + +@ Some of the code below is intended to be used only when diagnosing the +strange behavior that sometimes occurs when \MP\ is being installed or +when system wizards are fooling around with \MP\ without quite knowing +what they are doing. Such code will not normally be compiled; it is +delimited by the codewords `$|debug|\ldots|gubed|$', with apologies +to people who wish to preserve the purity of English. + +Similarly, there is some conditional code delimited by +`$|stat|\ldots|tats|$' that is intended for use when statistics are to be +kept about \MP's memory usage. +@^debugging@> + +@d debug==@{ {change this to `$\\{debug}\equiv\null$' when debugging} +@d gubed==@t@>@} {change this to `$\\{gubed}\equiv\null$' when debugging} +@f debug==begin +@f gubed==end +@# +@d stat==@{ {change this to `$\\{stat}\equiv\null$' when gathering + usage statistics} +@d tats==@t@>@} {change this to `$\\{tats}\equiv\null$' when gathering + usage statistics} +@f stat==begin +@f tats==end + +@ This program has two important variations: (1) There is a long and slow +version called \.{INIMP}, which does the extra calculations needed to +@.INIMP@> +initialize \MP's internal tables; and (2)~there is a shorter and faster +production version, which cuts the initialization to a bare minimum. +Parts of the program that are needed in (1) but not in (2) are delimited by +the codewords `$|init|\ldots|tini|$'. + +@d init== {change this to `$\\{init}\equiv\.{@@\{}$' in the production version} +@d tini== {change this to `$\\{tini}\equiv\.{@@\}}$' in the production version} +@f init==begin +@f tini==end + +@ If the first character of a \PASCAL\ comment is a dollar sign, +\ph\ treats the comment as a list of ``compiler directives'' that will +affect the translation of this program into machine language. The +directives shown below specify full checking and inclusion of the \PASCAL\ +debugger when \MP\ is being debugged, but they cause range checking and other +redundant code to be eliminated when the production system is being generated. +Arithmetic overflow will be detected in all cases. +@^system dependencies@> +@^Overflow in arithmetic@> + +@<Compiler directives@>= +@{@&$C-,A+,D-@} {no range check, catch arithmetic overflow, no debug overhead} +@!debug @{@&$C+,D+@}@+ gubed {but turn everything on when debugging} + +@ This \MP\ implementation conforms to the rules of the {\sl Pascal User +@:PASCAL}{\PASCAL@> +@^system dependencies@> +Manual} published by Jensen and Wirth in 1975, except where system-dependent +@^Wirth, Niklaus@> +@^Jensen, Kathleen@> +code is necessary to make a useful system program, and except in another +respect where such conformity would unnecessarily obscure the meaning +and clutter up the code: We assume that |case| statements may include a +default case that applies if no matching label is found. Thus, we shall use +constructions like +$$\vbox{\halign{\ignorespaces#\hfil\cr +|case x of|\cr +1: $\langle\,$code for $x=1\,\rangle$;\cr +3: $\langle\,$code for $x=3\,\rangle$;\cr +|othercases| $\langle\,$code for |x<>1| and |x<>3|$\,\rangle$\cr +|endcases|\cr}}$$ +since most \PASCAL\ compilers have plugged this hole in the language by +incorporating some sort of default mechanism. For example, the \ph\ +compiler allows `|others|:' as a default label, and other \PASCAL s allow +syntaxes like `\&{else}' or `\&{otherwise}' or `\\{otherwise}:', etc. The +definitions of |othercases| and |endcases| should be changed to agree with +local conventions. Note that no semicolon appears before |endcases| in +this program, so the definition of |endcases| should include a semicolon +if the compiler wants one. (Of course, if no default mechanism is +available, the |case| statements of \MP\ will have to be laboriously +extended by listing all remaining cases. People who are stuck with such +\PASCAL s have, in fact, done this, successfully but not happily!) + +@d othercases == others: {default for cases not listed explicitly} +@d endcases == @+end {follows the default case in an extended |case| statement} +@f othercases == else +@f endcases == end + +@ The following parameters can be changed at compile time to extend or +reduce \MP's capacity. They may have different values in \.{INIMP} and +in production versions of \MP. +@.INIMP@> +@^system dependencies@> + +@<Constants...@>= +@!mem_max=30000; {greatest index in \MP's internal |mem| array; + must be strictly less than |max_halfword|; + must be equal to |mem_top| in \.{INIMP}, otherwise |>=mem_top|} +@!max_internal=100; {maximum number of internal quantities} +@!buf_size=500; {maximum number of characters simultaneously present in + current lines of open files; must not exceed |max_halfword|} +@!error_line=72; {width of context lines on terminal error messages} +@!half_error_line=42; {width of first lines of contexts in terminal + error messages; should be between 30 and |error_line-15|} +@!max_print_line=79; {width of longest text lines output; should be at least 60} +@!emergency_line_length=255; + {\ps\ output lines can be this long in unusual circumstances} +@!stack_size=30; {maximum number of simultaneous input sources} +@!max_read_files=4; {maximum number of simultaneously open \&{readfrom} files} +@!max_strings=2500; {maximum number of strings; must not exceed |max_halfword|} +@!string_vacancies=9000; {the minimum number of characters that should be + available for the user's identifier names and strings, + after \MP's own error messages are stored} +@!strings_vacant=1000; {the minimum number of strings that should be available} +@!pool_size=32000; {maximum number of characters in strings, including all + error messages and help texts, and the names of all identifiers; + must exceed |string_vacancies| by the total + length of \MP's own strings, which is currently about 22000} +@!font_max=50; {maximum font number for included text fonts} +@!font_mem_size=10000; {number of words for \.{TFM} information for text fonts} +@!file_name_size=40; {file names shouldn't be longer than this} +@!pool_name='MPlib:MP.POOL '; + {string of length |file_name_size|; tells where the string pool appears} +@.MPlib@> +@!ps_tab_name='MPlib:PSFONTS.MAP '; + {string of length |file_name_size|; locates font name translation table} +@!path_size=300; {maximum number of knots between breakpoints of a path} +@!bistack_size=785; {size of stack for bisection algorithms; + should probably be left at this value} +@!header_size=100; {maximum number of \.{TFM} header words, times~4} +@!lig_table_size=5000; {maximum number of ligature/kern steps, must be + at least 255 and at most 32510} +@!max_kerns=500; {maximum number of distinct kern amounts} +@!max_font_dimen=50; {maximum number of \&{fontdimen} parameters} + +@ Like the preceding parameters, the following quantities can be changed +at compile time to extend or reduce \MP's capacity. But if they are changed, +it is necessary to rerun the initialization program \.{INIMP} +@.INIMP@> +to generate new tables for the production \MP\ program. +One can't simply make helter-skelter changes to the following constants, +since certain rather complex initialization +numbers are computed from them. They are defined here using +\.{WEB} macros, instead of being put into \PASCAL's |const| list, in order to +emphasize this distinction. + +@d mem_min=0 {smallest index in the |mem| array, must not be less + than |min_halfword|} +@d mem_top==30000 {largest index in the |mem| array dumped by \.{INIMP}; + must be substantially larger than |mem_min| + and not greater than |mem_max|} +@d hash_size=2100 {maximum number of symbolic tokens, + must be less than |max_halfword-3*param_size|} +@d hash_prime=1777 {a prime number equal to about 85\pct! of |hash_size|} +@d max_in_open=6 {maximum number of input files and error insertions that + can be going on simultaneously} +@d param_size=150 {maximum number of simultaneous macro parameters} +@d max_write_files=4 {maximum number of simultaneously open \&{write} files} +@^system dependencies@> + +@ In case somebody has inadvertently made bad settings of the ``constants,'' +\MP\ checks them using a global variable called |bad|. + +This is the first of many sections of \MP\ where global variables are +defined. + +@<Glob...@>= +@!bad:integer; {is some ``constant'' wrong?} + +@ Later on we will say `\ignorespaces|if mem_max>=max_halfword then bad:=10|', +or something similar. (We can't do that until |max_halfword| has been defined.) + +@<Check the ``constant'' values for consistency@>= +bad:=0; +if (half_error_line<30)or(half_error_line>error_line-15) then bad:=1; +if max_print_line<60 then bad:=2; +if emergency_line_length<max_print_line then bad:=3; +if mem_min+1100>mem_top then bad:=4; +if hash_prime>hash_size then bad:=5; +if header_size mod 4 <> 0 then bad:=6; +if(lig_table_size<255)or(lig_table_size>32510)then bad:=7; + +@ Labels are given symbolic names by the following definitions, so that +occasional |goto| statements will be meaningful. We insert the label +`|exit|:' just before the `\ignorespaces|end|\unskip' of a procedure in +which we have used the `|return|' statement defined below; the label +`|restart|' is occasionally used at the very beginning of a procedure; and +the label `|reswitch|' is occasionally used just prior to a |case| +statement in which some cases change the conditions and we wish to branch +to the newly applicable case. Loops that are set up with the |loop| +construction defined below are commonly exited by going to `|done|' or to +`|found|' or to `|not_found|', and they are sometimes repeated by going to +`|continue|'. If two or more parts of a subroutine start differently but +end up the same, the shared code may be gathered together at +`|common_ending|'. + +Incidentally, this program never declares a label that isn't actually used, +because some fussy \PASCAL\ compilers will complain about redundant labels. + +@d exit=10 {go here to leave a procedure} +@d restart=20 {go here to start a procedure again} +@d reswitch=21 {go here to start a case statement again} +@d continue=22 {go here to resume a loop} +@d done=30 {go here to exit a loop} +@d done1=31 {like |done|, when there is more than one loop} +@d done2=32 {for exiting the second loop in a long block} +@d done3=33 {for exiting the third loop in a very long block} +@d done4=34 {for exiting the fourth loop in an extremely long block} +@d done5=35 {for exiting the fifth loop in an immense block} +@d done6=36 {for exiting the sixth loop in a block} +@d found=40 {go here when you've found it} +@d found1=41 {like |found|, when there's more than one per routine} +@d found2=42 {like |found|, when there's more than two per routine} +@d not_found=45 {go here when you've found nothing} +@d common_ending=50 {go here when you want to merge with another branch} + +@ Here are some macros for common programming idioms. + +@d incr(#) == #:=#+1 {increase a variable by unity} +@d decr(#) == #:=#-1 {decrease a variable by unity} +@d negate(#) == #:=-# {change the sign of a variable} +@d double(#) == #:=#+# {multiply a variable by two} +@d loop == @+ while true do@+ {repeat over and over until a |goto| happens} +@f loop == xclause + {\.{WEB}'s |xclause| acts like `\ignorespaces|while true do|\unskip'} +@d do_nothing == {empty statement} +@d return == goto exit {terminate a procedure call} +@f return == nil {\.{WEB} will henceforth say |return| instead of \\{return}} + +@* \[2] The character set. +In order to make \MP\ readily portable to a wide variety of +computers, all of its input text is converted to an internal eight-bit +code that includes standard ASCII, the ``American Standard Code for +Information Interchange.'' This conversion is done immediately when each +character is read in. Conversely, characters are converted from ASCII to +the user's external representation just before they are output to a +text file. +@^ASCII code@> + +Such an internal code is relevant to users of \MP\ only with respect to +the \&{char} and \&{ASCII} operations, and the comparison of strings. + +@ Characters of text that have been converted to \MP's internal form +are said to be of type |ASCII_code|, which is a subrange of the integers. + +@<Types...@>= +@!ASCII_code=0..255; {eight-bit numbers} + +@ The original \PASCAL\ compiler was designed in the late 60s, when six-bit +character sets were common, so it did not make provision for lowercase +letters. Nowadays, of course, we need to deal with both capital and small +letters in a convenient way, especially in a program for font design; +so the present specification of \MP\ has been written under the assumption +that the \PASCAL\ compiler and run-time system permit the use of text files +with more than 64 distinguishable characters. More precisely, we assume that +the character set contains at least the letters and symbols associated +with ASCII codes @'40 through @'176; all of these characters are now +available on most computer terminals. + +Since we are dealing with more characters than were present in the first +\PASCAL\ compilers, we have to decide what to call the associated data +type. Some \PASCAL s use the original name |char| for the +characters in text files, even though there now are more than 64 such +characters, while other \PASCAL s consider |char| to be a 64-element +subrange of a larger data type that has some other name. + +In order to accommodate this difference, we shall use the name |text_char| +to stand for the data type of the characters that are converted to and +from |ASCII_code| when they are input and output. We shall also assume +that |text_char| consists of the elements |chr(first_text_char)| through +|chr(last_text_char)|, inclusive. The following definitions should be +adjusted if necessary. +@^system dependencies@> + +@d text_char == char {the data type of characters in text files} +@d first_text_char=0 {ordinal number of the smallest element of |text_char|} +@d last_text_char=255 {ordinal number of the largest element of |text_char|} + +@<Local variables for init...@>= +@!i:integer; + +@ The \MP\ processor converts between ASCII code and +the user's external character set by means of arrays |xord| and |xchr| +that are analogous to \PASCAL's |ord| and |chr| functions. + +@<Glob...@>= +@!xord: array [text_char] of ASCII_code; + {specifies conversion of input characters} +@!xchr: array [ASCII_code] of text_char; + {specifies conversion of output characters} + +@ Since we are assuming that our \PASCAL\ system is able to read and +write the visible characters of standard ASCII (although not +necessarily using the ASCII codes to represent them), the following +assignment statements initialize the standard part of the |xchr| array +properly, without needing any system-dependent changes. On the other +hand, it is possible to implement \MP\ with less complete character +sets, and in such cases it will be necessary to change something here. +@^system dependencies@> + +@<Set init...@>= +xchr[@'40]:=' '; +xchr[@'41]:='!'; +xchr[@'42]:='"'; +xchr[@'43]:='#'; +xchr[@'44]:='$'; +xchr[@'45]:='%'; +xchr[@'46]:='&'; +xchr[@'47]:='''';@/ +xchr[@'50]:='('; +xchr[@'51]:=')'; +xchr[@'52]:='*'; +xchr[@'53]:='+'; +xchr[@'54]:=','; +xchr[@'55]:='-'; +xchr[@'56]:='.'; +xchr[@'57]:='/';@/ +xchr[@'60]:='0'; +xchr[@'61]:='1'; +xchr[@'62]:='2'; +xchr[@'63]:='3'; +xchr[@'64]:='4'; +xchr[@'65]:='5'; +xchr[@'66]:='6'; +xchr[@'67]:='7';@/ +xchr[@'70]:='8'; +xchr[@'71]:='9'; +xchr[@'72]:=':'; +xchr[@'73]:=';'; +xchr[@'74]:='<'; +xchr[@'75]:='='; +xchr[@'76]:='>'; +xchr[@'77]:='?';@/ +xchr[@'100]:='@@'; +xchr[@'101]:='A'; +xchr[@'102]:='B'; +xchr[@'103]:='C'; +xchr[@'104]:='D'; +xchr[@'105]:='E'; +xchr[@'106]:='F'; +xchr[@'107]:='G';@/ +xchr[@'110]:='H'; +xchr[@'111]:='I'; +xchr[@'112]:='J'; +xchr[@'113]:='K'; +xchr[@'114]:='L'; +xchr[@'115]:='M'; +xchr[@'116]:='N'; +xchr[@'117]:='O';@/ +xchr[@'120]:='P'; +xchr[@'121]:='Q'; +xchr[@'122]:='R'; +xchr[@'123]:='S'; +xchr[@'124]:='T'; +xchr[@'125]:='U'; +xchr[@'126]:='V'; +xchr[@'127]:='W';@/ +xchr[@'130]:='X'; +xchr[@'131]:='Y'; +xchr[@'132]:='Z'; +xchr[@'133]:='['; +xchr[@'134]:='\'; +xchr[@'135]:=']'; +xchr[@'136]:='^'; +xchr[@'137]:='_';@/ +xchr[@'140]:='`'; +xchr[@'141]:='a'; +xchr[@'142]:='b'; +xchr[@'143]:='c'; +xchr[@'144]:='d'; +xchr[@'145]:='e'; +xchr[@'146]:='f'; +xchr[@'147]:='g';@/ +xchr[@'150]:='h'; +xchr[@'151]:='i'; +xchr[@'152]:='j'; +xchr[@'153]:='k'; +xchr[@'154]:='l'; +xchr[@'155]:='m'; +xchr[@'156]:='n'; +xchr[@'157]:='o';@/ +xchr[@'160]:='p'; +xchr[@'161]:='q'; +xchr[@'162]:='r'; +xchr[@'163]:='s'; +xchr[@'164]:='t'; +xchr[@'165]:='u'; +xchr[@'166]:='v'; +xchr[@'167]:='w';@/ +xchr[@'170]:='x'; +xchr[@'171]:='y'; +xchr[@'172]:='z'; +xchr[@'173]:='{'; +xchr[@'174]:='|'; +xchr[@'175]:='}'; +xchr[@'176]:='~';@/ + +@ The ASCII code is ``standard'' only to a certain extent, since many +computer installations have found it advantageous to have ready access +to more than 94 printing characters. If \MP\ is being used +on a garden-variety \PASCAL\ for which only standard ASCII +codes will appear in the input and output files, it doesn't really matter +what codes are specified in |xchr[0..@'37]|, but the safest policy is to +blank everything out by using the code shown below. + +However, other settings of |xchr| will make \MP\ more friendly on +computers that have an extended character set, so that users can type things +like `\.^^Z' instead of `\.{<>}'. +People with extended character sets can +assign codes arbitrarily, giving an |xchr| equivalent to whatever +characters the users of \MP\ are allowed to have in their input files. +Appropriate changes to \MP's |char_class| table should then be made. +(Unlike \TeX, each installation of \MP\ has a fixed assignment of category +codes, called the |char_class|.) Such changes make portability of programs +more difficult, so they should be introduced cautiously if at all. +@^character set dependencies@> +@^system dependencies@> + +@<Set init...@>= +for i:=0 to @'37 do xchr[i]:=' '; +for i:=@'177 to @'377 do xchr[i]:=' '; + +@ The following system-independent code makes the |xord| array contain a +suitable inverse to the information in |xchr|. Note that if |xchr[i]=xchr[j]| +where |i<j<@'177|, the value of |xord[xchr[i]]| will turn out to be +|j| or more; hence, standard ASCII code numbers will be used instead of +codes below @'40 in case there is a coincidence. + +@<Set init...@>= +for i:=first_text_char to last_text_char do xord[chr(i)]:=@'177; +for i:=@'200 to @'377 do xord[xchr[i]]:=i; +for i:=0 to @'176 do xord[xchr[i]]:=i; + +@* \[3] Input and output. +The bane of portability is the fact that different operating systems treat +input and output quite differently, perhaps because computer scientists +have not given sufficient attention to this problem. People have felt somehow +that input and output are not part of ``real'' programming. Well, it is true +that some kinds of programming are more fun than others. With existing +input/output conventions being so diverse and so messy, the only sources of +joy in such parts of the code are the rare occasions when one can find a +way to make the program a little less bad than it might have been. We have +two choices, either to attack I/O now and get it over with, or to postpone +I/O until near the end. Neither prospect is very attractive, so let's +get it over with. + +The basic operations we need to do are (1)~inputting and outputting of +text, to or from a file or the user's terminal; (2)~inputting and +outputting of eight-bit bytes, to or from a file; (3)~instructing the +operating system to initiate (``open'') or to terminate (``close'') input or +output from a specified file; (4)~testing whether the end of an input +file has been reached; (5)~display of bits on the user's screen. +The bit-display operation will be discussed in a later section; we shall +deal here only with more traditional kinds of I/O. + +\MP\ needs to deal with two kinds of files. +We shall use the term |alpha_file| for a file that contains textual data, +and the term |byte_file| for a file that contains eight-bit binary information. +These two types turn out to be the same on many computers, but +sometimes there is a significant distinction, so we shall be careful to +distinguish between them. Standard protocols for transferring +such files from computer to computer, via high-speed networks, are +now becoming available to more and more communities of users. + +The program actually makes use also of a third kind of file, called a +|word_file|, when dumping and reloading mem information for its own +initialization. We shall define a word file later; but it will be possible +for us to specify simple operations on word files before they are defined. + +@<Types...@>= +@!eight_bits=0..255; {unsigned one-byte quantity} +@!alpha_file=packed file of text_char; {files that contain textual data} +@!byte_file=packed file of eight_bits; {files that contain binary data} + +@ Most of what we need to do with respect to input and output can be handled +by the I/O facilities that are standard in \PASCAL, i.e., the routines +called |get|, |put|, |eof|, and so on. But +standard \PASCAL\ does not allow file variables to be associated with file +names that are determined at run time, so it cannot be used to implement +\MP; some sort of extension to \PASCAL's ordinary |reset| and |rewrite| +is crucial for our purposes. We shall assume that |name_of_file| is a variable +of an appropriate type such that the \PASCAL\ run-time system being used to +implement \MP\ can open a file whose external name is specified by +|name_of_file|. +@^system dependencies@> + +@<Glob...@>= +@!name_of_file:packed array[1..file_name_size] of char;@;@/ + {on some systems this may be a \&{record} variable} +@!name_length:0..file_name_size;@/{this many characters are actually + relevant in |name_of_file| (the rest are blank)} + +@ The \ph\ compiler with which the original version of \MF\ was prepared +extends the rules of \PASCAL\ in a very convenient way. To open file~|f|, +we can write +$$\vbox{\halign{#\hfil\qquad&#\hfil\cr +|reset(f,@t\\{name}@>,'/O')|&for input;\cr +|rewrite(f,@t\\{name}@>,'/O')|&for output.\cr}}$$ +The `\\{name}' parameter, which is of type `\ignorespaces|packed +array[@t\<\\{any}>@>] of text_char|', stands for the name of +the external file that is being opened for input or output. +Blank spaces that might appear in \\{name} are ignored. + +The `\.{/O}' parameter tells the operating system not to issue its own +error messages if something goes wrong. If a file of the specified name +cannot be found, or if such a file cannot be opened for some other reason +(e.g., someone may already be trying to write the same file), we will have +|@!erstat(f)<>0| after an unsuccessful |reset| or |rewrite|. This allows +\MP\ to undertake appropriate corrective action. +@:PASCAL H}{\ph@> +@^system dependencies@> + +\MP's file-opening procedures return |false| if no file identified by +|name_of_file| could be opened. + +@d reset_OK(#)==erstat(#)=0 +@d rewrite_OK(#)==erstat(#)=0 + +@p function a_open_in(var @!f:alpha_file):boolean; + {open a text file for input} +begin reset(f,name_of_file,'/O'); a_open_in:=reset_OK(f); +end; +@# +function a_open_out(var @!f:alpha_file):boolean; + {open a text file for output} +begin rewrite(f,name_of_file,'/O'); a_open_out:=rewrite_OK(f); +end; +@# +function b_open_in(var @!f:byte_file):boolean; + {open a binary file for input} +begin reset(f,name_of_file,'/O'); b_open_in:=reset_OK(f); +end; +@# +function b_open_out(var @!f:byte_file):boolean; + {open a binary file for output} +begin rewrite(f,name_of_file,'/O'); b_open_out:=rewrite_OK(f); +end; +@# +function w_open_in(var @!f:word_file):boolean; + {open a word file for input} +begin reset(f,name_of_file,'/O'); w_open_in:=reset_OK(f); +end; +@# +function w_open_out(var @!f:word_file):boolean; + {open a word file for output} +begin rewrite(f,name_of_file,'/O'); w_open_out:=rewrite_OK(f); +end; + +@ Files can be closed with the \ph\ routine `|close(f)|', which +@^system dependencies@> +should be used when all input or output with respect to |f| has been completed. +This makes |f| available to be opened again, if desired; and if |f| was used for +output, the |close| operation makes the corresponding external file appear +on the user's area, ready to be read. + +@p procedure a_close(var @!f:alpha_file); {close a text file} +begin close(f); +end; +@# +procedure b_close(var @!f:byte_file); {close a binary file} +begin close(f); +end; +@# +procedure w_close(var @!f:word_file); {close a word file} +begin close(f); +end; + +@ Binary input and output are done with \PASCAL's ordinary |get| and |put| +procedures, so we don't have to make any other special arrangements for +binary~I/O. Text output is also easy to do with standard \PASCAL\ routines. +The treatment of text input is more difficult, however, because +of the necessary translation to |ASCII_code| values. +\MP's conventions should be efficient, and they should +blend nicely with the user's operating environment. + +@ Input from text files is read one line at a time, using a routine called +|input_ln|. This function is defined in terms of global variables called +|buffer|, |first|, and |last| that will be described in detail later; for +now, it suffices for us to know that |buffer| is an array of |ASCII_code| +values, and that |first| and |last| are indices into this array +representing the beginning and ending of a line of text. + +@<Glob...@>= +@!buffer:array[0..buf_size] of ASCII_code; {lines of characters being read} +@!first:0..buf_size; {the first unused position in |buffer|} +@!last:0..buf_size; {end of the line just input to |buffer|} +@!max_buf_stack:0..buf_size; {largest index used in |buffer|} + +@ The |input_ln| function brings the next line of input from the specified +field into available positions of the buffer array and returns the value +|true|, unless the file has already been entirely read, in which case it +returns |false| and sets |last:=first|. In general, the |ASCII_code| +numbers that represent the next line of the file are input into +|buffer[first]|, |buffer[first+1]|, \dots, |buffer[last-1]|; and the +global variable |last| is set equal to |first| plus the length of the +line. Trailing blanks are removed from the line; thus, either |last=first| +(in which case the line was entirely blank) or |buffer[last-1]<>" "|. +@^inner loop@> + +An overflow error is given, however, if the normal actions of |input_ln| +would make |last>=buf_size|; this is done so that other parts of \MP\ +can safely look at the contents of |buffer[last+1]| without overstepping +the bounds of the |buffer| array. Upon entry to |input_ln|, the condition +|first<buf_size| will always hold, so that there is always room for an +``empty'' line. + +The variable |max_buf_stack|, which is used to keep track of how large +the |buf_size| parameter must be to accommodate the present job, is +also kept up to date by |input_ln|. + +If the |bypass_eoln| parameter is |true|, |input_ln| will do a |get| +before looking at the first character of the line; this skips over +an |eoln| that was in |f^|. The procedure does not do a |get| when it +reaches the end of the line; therefore it can be used to acquire input +from the user's terminal as well as from ordinary text files. + +Standard \PASCAL\ says that a file should have |eoln| immediately +before |eof|, but \MP\ needs only a weaker restriction: If |eof| +occurs in the middle of a line, the system function |eoln| should return +a |true| result (even though |f^| will be undefined). + +@p function input_ln(var @!f:alpha_file;@!bypass_eoln:boolean):boolean; + {inputs the next line or returns |false|} +var @!last_nonblank:0..buf_size; {|last| with trailing blanks removed} +begin if bypass_eoln then if not eof(f) then get(f); + {input the first character of the line into |f^|} +last:=first; {cf.\ Matthew 19\thinspace:\thinspace30} +if eof(f) then input_ln:=false +else begin last_nonblank:=first; + while not eoln(f) do + begin if last>=max_buf_stack then + begin max_buf_stack:=last+1; + if max_buf_stack=buf_size then + @<Report overflow of the input buffer, and abort@>; + end; + buffer[last]:=xord[f^]; get(f); incr(last); + if buffer[last-1]<>" " then last_nonblank:=last; + end; + last:=last_nonblank; input_ln:=true; + end; +end; + +@ The user's terminal acts essentially like other files of text, except +that it is used both for input and for output. When the terminal is +considered an input file, the file variable is called |term_in|, and when it +is considered an output file the file variable is |term_out|. +@^system dependencies@> + +@<Glob...@>= +@!term_in:alpha_file; {the terminal as an input file} +@!term_out:alpha_file; {the terminal as an output file} + +@ Here is how to open the terminal files +in \ph. The `\.{/I}' switch suppresses the first |get|. +@^system dependencies@> + +@d t_open_in==reset(term_in,'TTY:','/O/I') {open the terminal for text input} +@d t_open_out==rewrite(term_out,'TTY:','/O') {open the terminal for text output} + +@ Sometimes it is necessary to synchronize the input/output mixture that +happens on the user's terminal, and three system-dependent +procedures are used for this +purpose. The first of these, |update_terminal|, is called when we want +to make sure that everything we have output to the terminal so far has +actually left the computer's internal buffers and been sent. +The second, |clear_terminal|, is called when we wish to cancel any +input that the user may have typed ahead (since we are about to +issue an unexpected error message). The third, |wake_up_terminal|, +is supposed to revive the terminal if the user has disabled it by +some instruction to the operating system. The following macros show how +these operations can be specified in \ph: +@^system dependencies@> + +@d update_terminal == break(term_out) {empty the terminal output buffer} +@d clear_terminal == break_in(term_in,true) {clear the terminal input buffer} +@d wake_up_terminal == do_nothing {cancel the user's cancellation of output} + +@ We need a special routine to read the first line of \MP\ input from +the user's terminal. This line is different because it is read before we +have opened the transcript file; there is sort of a ``chicken and +egg'' problem here. If the user types `\.{input cmr10}' on the first +line, or if some macro invoked by that line does such an \.{input}, +the transcript file will be named `\.{cmr10.log}'; but if no \.{input} +commands are performed during the first line of terminal input, the transcript +file will acquire its default name `\.{mpout.log}'. (The transcript file +will not contain error messages generated by the first line before the +first \.{input} command.) + +The first line is even more special if we are lucky enough to have an operating +system that treats \MP\ differently from a run-of-the-mill \PASCAL\ object +program. It's nice to let the user start running a \MP\ job by typing +a command line like `\.{MP cmr10}'; in such a case, \MP\ will operate +as if the first line of input were `\.{cmr10}', i.e., the first line will +consist of the remainder of the command line, after the part that invoked \MP. + +The first line is special also because it may be read before \MP\ has +input a mem file. In such cases, normal error messages cannot yet +be given. The following code uses concepts that will be explained later. + +@<Report overflow of the input buffer, and abort@>= +if mem_ident=0 then + begin write_ln(term_out,'Buffer size exceeded!'); goto final_end; +@.Buffer size exceeded@> + end +else begin cur_input.loc_field:=first; cur_input.limit_field:=last-1; + overflow("buffer size",buf_size); +@:MetaPost capacity exceeded buffer size}{\quad buffer size@> + end + +@ Different systems have different ways to get started. But regardless of +what conventions are adopted, the routine that initializes the terminal +should satisfy the following specifications: + +\yskip\textindent{1)}It should open file |term_in| for input from the + terminal. (The file |term_out| will already be open for output to the + terminal.) + +\textindent{2)}If the user has given a command line, this line should be + considered the first line of terminal input. Otherwise the + user should be prompted with `\.{**}', and the first line of input + should be whatever is typed in response. + +\textindent{3)}The first line of input, which might or might not be a + command line, should appear in locations |first| to |last-1| of the + |buffer| array. + +\textindent{4)}The global variable |loc| should be set so that the + character to be read next by \MP\ is in |buffer[loc]|. This + character should not be blank, and we should have |loc<last|. + +\yskip\noindent(It may be necessary to prompt the user several times +before a non-blank line comes in. The prompt is `\.{**}' instead of the +later `\.*' because the meaning is slightly different: `\.{input}' need +not be typed immediately after~`\.{**}'.) + +@d loc==cur_input.loc_field {location of first unread character in |buffer|} + +@ The following program does the required initialization +without retrieving a possible command line. +It should be clear how to modify this routine to deal with command lines, +if the system permits them. +@^system dependencies@> + +@p function init_terminal:boolean; {gets the terminal input started} +label exit; +begin t_open_in; +loop@+begin wake_up_terminal; write(term_out,'**'); update_terminal; +@.**@> + if not input_ln(term_in,true) then {this shouldn't happen} + begin write_ln(term_out); + write(term_out,'! End of file on the terminal... why?'); +@.End of file on the terminal@> + init_terminal:=false; return; + end; + loc:=first; + while (loc<last)and(buffer[loc]=" ") do incr(loc); + if loc<last then + begin init_terminal:=true; + return; {return unless the line was all blank} + end; + write_ln(term_out,'Please type the name of your input file.'); + end; +exit:end; + +@* \[4] String handling. +Symbolic token names and diagnostic messages are variable-length strings +of eight-bit characters. Since \PASCAL\ does not have a well-developed string +mechanism, \MP\ does all of its string processing by homegrown methods. + +\MP\ uses strings more extensively than \MF\ does, but the necessary +operations can still be handled with a fairly simple data structure. +The array |str_pool| contains all of the (eight-bit) ASCII codes in all +of the strings, and the array |str_start| contains indices of the starting +points of each string. Strings are referred to by integer numbers, so that +string number |s| comprises the characters |str_pool[j]| for +|str_start[s]<=j<str_start[ss]| where |ss=next_str[s]|. The string pool +is allocated sequentially and |str_pool[pool_ptr]| is the next unused +location. The first string number not currently in use is |str_ptr| +and |next_str[str_ptr]| begins a list of free string numbers. String +pool entries |str_start[str_ptr]| up to |pool_ptr| are reserved for a +string currently being constructed. + +String numbers 0 to 255 are reserved for strings that correspond to single +ASCII characters. This is in accordance with the conventions of \.{WEB}, +@.WEB@> +which converts single-character strings into the ASCII code number of the +single character involved, while it converts other strings into integers +and builds a string pool file. Thus, when the string constant \.{"."} appears +in the program below, \.{WEB} converts it into the integer 46, which is the +ASCII code for a period, while \.{WEB} will convert a string like \.{"hello"} +into some integer greater than~255. String number 46 will presumably be the +single character `\..'\thinspace; but some ASCII codes have no standard visible +representation, and \MP\ may need to be able to print an arbitrary +ASCII character, so the first 256 strings are used to specify exactly what +should be printed for each of the 256 possibilities. + +Elements of the |str_pool| array must be ASCII codes that can actually be +printed; i.e., they must have an |xchr| equivalent in the local +character set. (This restriction applies only to preloaded strings, +not to those generated dynamically by the user.) + +Some \PASCAL\ compilers won't pack integers into a single byte unless the +integers lie in the range |-128..127|. To accommodate such systems +we access the string pool via macros that can easily be redefined. +When accessing character dimensions for the \&{infont} operator, an explicit +offset is used to convert from |pool_ASCII_code| to |ASCII_code|. + +@d si(#) == # {convert from |ASCII_code| to |pool_ASCII_code|} +@d so(#) == # {convert from |pool_ASCII_code| to |ASCII_code|} +@d min_pool_ASCII=0 {added to an |ASCII_code| to make a |pool_ASCII_code|} + +@<Types...@>= +@!pool_pointer = 0..pool_size; {for variables that point into |str_pool|} +@!str_number = 0..max_strings; {for variables that point into |str_start|} +@!pool_ASCII_code = 0..255; {elements of |str_pool| array} + +@ @<Glob...@>= +@!str_pool:packed array[pool_pointer] of pool_ASCII_code; {the characters} +@!str_start : array[str_number] of pool_pointer; {the starting pointers} +@!next_str : array[str_number] of str_number; {for linking strings in order} +@!pool_ptr : pool_pointer; {first unused position in |str_pool|} +@!str_ptr : str_number; {number of the current string being created} +@!init_pool_ptr : pool_pointer; {the starting value of |pool_ptr|} +@!init_str_use : str_number; {the initial number of strings in use} +@!max_pool_ptr : pool_pointer; {the maximum so far of |pool_ptr|} +@!max_str_ptr : str_number; {the maximum so far of |str_ptr|} + +@ Except for |strs_used_up|, the following string statistics are only +maintained when code between |stat| $\ldots$ |tats| delimiters is not +commented out: + +@<Glob...@>= +@!strs_used_up:integer; {strings in use or unused but not reclaimed} +@!pool_in_use:integer; {total number of cells of |str_pool| actually in use} +@!strs_in_use:integer; {total number of strings actually in use} +@!max_pl_used:integer; {maximum |pool_in_use| so far} +@!max_strs_used:integer; {maximum |strs_in_use| so far} + +@ Several of the elementary string operations are performed using \.{WEB} +macros instead of \PASCAL\ procedures, because many of the +operations are done quite frequently and we want to avoid the +overhead of procedure calls. For example, here is +a simple macro that computes the length of a string. +@.WEB@> + +@d str_stop(#)==str_start[next_str[#]] {one cell past the end of string + number \#} +@d length(#)==(str_stop(#)-str_start[#]) {the number of characters in string \#} + +@ The length of the current string is called |cur_length|. If we decide that +the current string is not needed, |flush_cur_string| resets |pool_ptr| so that +|cur_length| becomes zero. + +@d cur_length == (pool_ptr - str_start[str_ptr]) +@d flush_cur_string == pool_ptr:=str_start[str_ptr] + +@ Strings are created by appending character codes to |str_pool|. +The |append_char| macro, defined here, does not check to see if the +value of |pool_ptr| has gotten too high; this test is supposed to be +made before |append_char| is used. + +To test if there is room to append |l| more characters to |str_pool|, +we shall write |str_room(l)|, which tries to make sure there is enough room +by compacting the string pool if necessary. If this does not work, +|do_compaction| aborts \MP\ and gives an apologetic error message. + +@d append_char(#) == {put |ASCII_code| \# at the end of |str_pool|} +begin str_pool[pool_ptr]:=si(#); incr(pool_ptr); +end +@d str_room(#) == {make sure that the pool hasn't overflowed} + begin if pool_ptr+# > max_pool_ptr then + if pool_ptr+# > pool_size then do_compaction(#) + else max_pool_ptr:=pool_ptr+#; + end + +@ The following routine is similar to |str_room(1)| but it uses the +argument |pool_size| to prevent |do_compaction| from aborting when +string space is exhausted. + +@<Declare the procedure called |unit_str_room|@>= +procedure unit_str_room; +begin if pool_ptr>=pool_size then do_compaction(pool_size); +if pool_ptr>=max_pool_ptr then max_pool_ptr:=pool_ptr+1; +end; + +@ \MP's string expressions are implemented in a brute-force way: Every +new string or substring that is needed is simply copied into the string pool. +Space is eventually reclaimed by a procedure called |do_compaction| with +the aid of a simple system system of reference counts. +@^reference counts@> + +The number of references to string number |s| will be |str_ref[s]|. The +special value |str_ref[s]=max_str_ref=127| is used to denote an unknown +positive number of references; such strings will never be recycled. If +a string is ever referred to more than 126 times, simultaneously, we +put it in this category. Hence a single byte suffices to store each |str_ref|. + +@d max_str_ref=127 {``infinite'' number of references} +@d add_str_ref(#)==begin if str_ref[#]<max_str_ref then incr(str_ref[#]); + end + +@<Glob...@>= +@!str_ref:array[str_number] of 0..max_str_ref; + +@ Here's what we do when a string reference disappears: + +@d delete_str_ref(#)== begin if str_ref[#]<max_str_ref then + if str_ref[#]>1 then decr(str_ref[#])@+else flush_string(#); + end + +@<Declare the procedure called |flush_string|@>= +procedure flush_string(@!s:str_number); +begin stat pool_in_use:=pool_in_use-length(s); + decr(strs_in_use); + tats@; +if next_str[s]<>str_ptr then str_ref[s]:=0 +else begin str_ptr:=s; + decr(strs_used_up); + end; +pool_ptr:=str_start[str_ptr]; +end; + +@ Once a sequence of characters has been appended to |str_pool|, it +officially becomes a string when the function |make_string| is called. +This function returns the identification number of the new string as its +value. + +When getting the next unused string number from the linked list, we pretend +that +$$ \hbox{|max_str_ptr+1|, |max_str_ptr+2|, $\ldots$, |max_strings|} $$ +are linked sequentially even though the |next_str| entries have not been +initialized yet. We never allow |str_ptr| to reach |max_strings|; +|do_compaction| is responsible for making sure of this. + +@p @t\4@>@<Declare the procedure called |do_compaction|@>@; +@t\4@>@<Declare the procedure called |unit_str_room|@>@; +function make_string : str_number; {current string enters the pool} +label restart; +var @!s:str_number; {the new string} +begin restart: s:=str_ptr; +str_ptr:=next_str[s]; +if str_ptr>max_str_ptr then + if str_ptr=max_strings then + begin str_ptr:=s; + do_compaction(0); + goto restart; + end + else begin debug if strs_used_up<>max_str_ptr then confusion("s");@+gubed@/ +@:this can't happen s}{\quad \.s@> + max_str_ptr:=str_ptr; + next_str[str_ptr]:=max_str_ptr+1; + end; +str_ref[s]:=1; +str_start[str_ptr]:=pool_ptr; +incr(strs_used_up); +stat incr(strs_in_use); + pool_in_use:=pool_in_use+length(s); + if pool_in_use>max_pl_used then max_pl_used:=pool_in_use; + if strs_in_use>max_strs_used then max_strs_used:=strs_in_use; +tats@; +make_string:=s; +end; + +@ On rare occasions, we might decide after calling |make_string| that some +characters should be removed from the end of the last string and transferred +to the beginning of a string under construction. This basically a matter of +resetting |str_start[str_ptr]|. It is not practical to ensure that the new +value for this pointer is in range, so this procedure should be used carefully. + +@p procedure chop_last_string(@!p:pool_pointer); +begin stat pool_in_use:=pool_in_use-(str_start[str_ptr]-p); @+tats; +str_start[str_ptr]:=p; +end; + +@ The most interesting string operation is string pool compaction. The idea +is to recover unused space in the |str_pool| array by recopying the strings +to close the gaps created when some strings become unused. All string +numbers~$k$ where |str_ref[k]=0| are to be linked into the list of free string +numbers after |str_ptr|. If this fails to free enough pool space we issue an +|overflow| error unless |needed=pool_size|. Calling |do_compaction| +with |needed=pool_size| supresses all overflow tests. + +The compaction process starts with |last_fixed_str| because all lower numbered +strings are permanently allocated with |max_str_ref| in their |str_ref| entries. + +@<Glob...@>= +@!last_fixed_str:str_number; {last permanently allocated string} +@!fixed_str_use:str_number; {number of permanently allocated strings} + +@ @<Declare the procedure called |do_compaction|@>= +procedure do_compaction(@!needed:pool_pointer); +label done; +var @!str_use:str_number; {a count of strings in use} +@!r,@!s,@!t:str_number; {strings being manipulated} +@!p,@!q:pool_pointer; {destination and source for copying string characters} +begin @<Advance |last_fixed_str| as far as possible and set |str_use|@>; +r:=last_fixed_str; +s:=next_str[r]; +p:=str_start[s]; +while s<>str_ptr do + begin while str_ref[s]=0 do + @<Advance |s| and add the old |s| to the list of free string numbers; + then |goto done| if |s=str_ptr|@>; + r:=s; s:=next_str[s]; + incr(str_use); + @<Move string |r| back so that |str_start[r]=p|; make |p| the location + after the end of the string@>; + end; +done: @<Move the current string back so that it starts at |p|@>; +if needed<pool_size then + @<Make sure that there is room for another string with |needed| characters@>; +stat @<Account for the compaction and make sure the statistics agree with the + global versions@>; +tats@; +strs_used_up:=str_use; +end; + +@ @<Advance |last_fixed_str| as far as possible and set |str_use|@>= +t:=next_str[last_fixed_str]; +while (str_ref[t]=max_str_ref)and(t<>str_ptr) do + begin incr(fixed_str_use); + last_fixed_str:=t; + t:=next_str[t]; + end; +str_use:=fixed_str_use + +@ Because of the way |flush_string| has been written, it should never be +necessary to |goto done| here. The extra line of code seems worthwhile to +preserve the generality of |do_compaction|. + +@<Advance |s| and add the old |s| to the list of free string numbers;...@>= +begin t:=s; +s:=next_str[s]; +next_str[r]:=s; +next_str[t]:=next_str[str_ptr]; +next_str[str_ptr]:=t; +if s=str_ptr then goto done; +end + +@ The string currently starts at |str_start[r]| and ends just before +|str_start[s]|. We don't change |str_start[s]| because it might be needed +to locate the next string. + +@<Move string |r| back so that |str_start[r]=p|; make |p| the location...@>= +q:=str_start[r]; +str_start[r]:=p; +while q<str_start[s] do + begin str_pool[p]:=str_pool[q]; + incr(p); incr(q); + end + +@ Pointers |str_start[str_ptr]| and |pool_ptr| have not been updated. When +we do this, anything between them should be moved. + +@ @<Move the current string back so that it starts at |p|@>= +q:=str_start[str_ptr]; +str_start[str_ptr]:=p; +while q<pool_ptr do + begin str_pool[p]:=str_pool[q]; + incr(p); incr(q); + end; +pool_ptr:=p + +@ We must remember that |str_ptr| is not allowed to reach |max_strings|. + +@<Make sure that there is room for another string with |needed| char...@>= +begin if str_use>=max_strings-1 then + begin str_overflowed:=true; + overflow("number of strings", max_strings-1-init_str_use); +@:MetaPost capacity exceeded number of strings}{\quad number of strings@> + end; +if pool_ptr+needed>max_pool_ptr then + if pool_ptr+needed>pool_size then + begin str_overflowed:=true; + overflow("pool size", pool_size-init_pool_ptr); +@:MetaPost capacity exceeded pool size}{\quad pool size@> + end + else max_pool_ptr:=pool_ptr+needed; +end + +@ Routines that can be called after string overflow need a way of checking +whether it is safe to use |str_room|, |make_string|, or |do_compaction|. + +@<Glob...@>= +@!str_overflowed:boolean; {is \MP\ aborting due to pool size of number of + strings?} + +@ @<Account for the compaction and make sure the statistics agree with...@>= +if (str_start[str_ptr]<>pool_in_use)or(str_use<>strs_in_use) then + confusion("string"); +@:this can't happen string}{\quad string@> +incr(pact_count); +pact_chars:=pact_chars+pool_ptr-str_stop(last_fixed_str); +pact_strs:=pact_strs+str_use-fixed_str_use; +debug s:=str_ptr; t:=str_use; + while s<=max_str_ptr do + begin if t>max_str_ptr then confusion(""""); + incr(t); s:=next_str[s]; + end; + if t<=max_str_ptr then confusion(""""); +gubed + +@ A few more global variables are needed to keep track of statistics when +|stat| $\ldots$ |tats| blocks are not commented out. + +@<Glob...@>= +@!pact_count:integer; {number of string pool compactions so far} +@!pact_chars:integer; {total number of characters moved during compactions} +@!pact_strs:integer; {total number of strings moved during compactions} + +@ @<Initialize compaction statistics@>= +pact_count:=0; +pact_chars:=0; +pact_strs:=0@; + +@ The following subroutine compares string |s| with another string of the +same length that appears in |buffer| starting at position |k|; +the result is |true| if and only if the strings are equal. + +@p function str_eq_buf(@!s:str_number;@!k:integer):boolean; + {test equality of strings} +label not_found; {loop exit} +var @!j: pool_pointer; {running index} +@!result: boolean; {result of comparison} +begin j:=str_start[s]; +while j<str_stop(s) do + begin if so(str_pool[j])<>buffer[k] then + begin result:=false; goto not_found; + end; + incr(j); incr(k); + end; +result:=true; +not_found: str_eq_buf:=result; +end; + +@ Here is a similar routine, but it compares two strings in the string pool, +and it does not assume that they have the same length. If the first string +is lexicographically greater than, less than, or equal to the second, +the result is respectively positive, negative, or zero. + +@p function str_vs_str(@!s,@!t:str_number):integer; + {test equality of strings} +label exit; +var @!j,@!k: pool_pointer; {running indices} +@!ls,@!lt:integer; {lengths} +@!l:integer; {length remaining to test} +begin ls:=length(s); lt:=length(t); +if ls<=lt then l:=ls@+else l:=lt; +j:=str_start[s]; k:=str_start[t]; +while l>0 do + begin if str_pool[j]<>str_pool[k] then + begin str_vs_str:=str_pool[j]-str_pool[k]; return; + end; + incr(j); incr(k); decr(l); + end; +str_vs_str:=ls-lt; +exit:end; + +@ The initial values of |str_pool|, |str_start|, |pool_ptr|, +and |str_ptr| are computed by the \.{INIMP} program, based in part +on the information that \.{WEB} has output while processing \MP. +@.INIMP@> +@^string pool@> + +@p @!init function get_strings_started:boolean; {initializes the string pool, + but returns |false| if something goes wrong} +label done,exit; +var @!k,@!l:0..255; {small indices or counters} +@!m,@!n:text_char; {characters input from |pool_file|} +@!g:str_number; {garbage} +@!a:integer; {accumulator for check sum} +@!c:boolean; {check sum has been checked} +begin pool_ptr:=0; str_ptr:=0; max_pool_ptr:=0; max_str_ptr:=0; +str_start[0]:=0; +next_str[0]:=1; +str_overflowed:=false; +stat pool_in_use:=0; strs_in_use:=0; + max_pl_used:=0; max_strs_used:=0; + @<Initialize compaction statistics@>; +tats@; +strs_used_up:=0; +@<Make the first 256 strings@>; +@<Read the other strings from the \.{MP.POOL} file and return |true|, + or give an error message and return |false|@>; +last_fixed_str:=str_ptr-1; +fixed_str_use:=str_ptr; +exit:end; +tini + +@ The first 256 strings will consist of a single character only. + +@<Make the first 256...@>= +for k:=0 to 255 do + begin append_char(k); + g:=make_string; str_ref[g]:=max_str_ref; + end; + +@ The first 128 strings will contain 95 standard ASCII characters, and the +other 33 characters will be printed in three-symbol form like `\.{\^\^A}' +unless a system-dependent change is made here. Installations that have +an extended character set, where for example |xchr[@'32]=@t\.{\'^^Z\'}@>|, +would like string @'32 to be printed as the single character @'32 instead +of the three characters @'136, @'136, @'132 (\.{\^\^Z}). On the other hand, +even people with an extended character set will want to represent string +@'15 by \.{\^\^M}, since @'15 is ASCII's ``carriage return'' code; the idea is +to produce visible strings instead of tabs or line-feeds or carriage-returns +or bell-rings or characters that are treated anomalously in text files. + +Unprintable characters of codes 128--255 are, similarly, rendered +\.{\^\^80}--\.{\^\^ff}. + +The boolean expression defined here should be |true| unless \MP\ internal +code number~|k| corresponds to a non-troublesome visible symbol in the +local character set. +If character |k| cannot be printed, and |k<@'200|, then character |k+@'100| or +|k-@'100| must be printable; moreover, ASCII codes |[@'60..@'71, @'141..@'146]| +must be printable. +@^character set dependencies@> +@^system dependencies@> + +@<Character |k| cannot be printed@>= + (k<" ")or(k>"~") + +@ When the \.{WEB} system program called \.{TANGLE} processes the \.{MP.WEB} +description that you are now reading, it outputs the \PASCAL\ program +\.{MP.PAS} and also a string pool file called \.{MP.POOL}. The \.{INIMP} +@.WEB@>@.INIMP@> +program reads the latter file, where each string appears as a two-digit decimal +length followed by the string itself, and the information is recorded in +\MP's string memory. + +@<Glob...@>= +@!init @!pool_file:alpha_file; {the string-pool file output by \.{TANGLE}} +tini + +@ @d bad_pool(#)==begin wake_up_terminal; write_ln(term_out,#); + a_close(pool_file); get_strings_started:=false; return; + end +@<Read the other strings...@>= +name_of_file:=pool_name; {we needn't set |name_length|} +if a_open_in(pool_file) then + begin c:=false; + repeat @<Read one string, but return |false| if the + string memory space is getting too tight for comfort@>; + until c; + a_close(pool_file); get_strings_started:=true; + end +else bad_pool('! I can''t read MP.POOL.') +@.I can't read MP.POOL@> + +@ @<Read one string...@>= +begin if eof(pool_file) then bad_pool('! MP.POOL has no check sum.'); +@.MP.POOL has no check sum@> +read(pool_file,m,n); {read two digits of string length} +if m='*' then @<Check the pool check sum@> +else begin if (xord[m]<"0")or(xord[m]>"9")or@| + (xord[n]<"0")or(xord[n]>"9") then + bad_pool('! MP.POOL line doesn''t begin with two digits.'); +@.MP.POOL line doesn't...@> + l:=xord[m]*10+xord[n]-"0"*11; {compute the length} + if pool_ptr+l+string_vacancies>pool_size then + bad_pool('! You have to increase POOLSIZE.'); +@.You have to increase POOLSIZE@> + if str_ptr+strings_vacant>=max_strings then + bad_pool('! You have to increase MAXSTRINGS.'); +@.You have to increase MAXSTRINGS@> + for k:=1 to l do + begin if eoln(pool_file) then m:=' '@+else read(pool_file,m); + append_char(xord[m]); + end; + read_ln(pool_file); g:=make_string; str_ref[g]:=max_str_ref; + end; +end + +@ The \.{WEB} operation \.{@@\$} denotes the value that should be at the +end of this \.{MP.POOL} file; any other value means that the wrong pool +file has been loaded. +@^check sum@> + +@<Check the pool check sum@>= +begin a:=0; k:=1; +loop@+ begin if (xord[n]<"0")or(xord[n]>"9") then + bad_pool('! MP.POOL check sum doesn''t have nine digits.'); +@.MP.POOL check sum...@> + a:=10*a+xord[n]-"0"; + if k=9 then goto done; + incr(k); read(pool_file,n); + end; +done: if a<>@$ then bad_pool('! MP.POOL doesn''t match; TANGLE me again.'); +@.MP.POOL doesn't match@> +c:=true; +end + +@* \[5] On-line and off-line printing. +Messages that are sent to a user's terminal and to the transcript-log file +are produced by several `|print|' procedures. These procedures will +direct their output to a variety of places, based on the setting of +the global variable |selector|, which has the following possible +values: + +\yskip +\hang |term_and_log|, the normal setting, prints on the terminal and on the + transcript file. + +\hang |log_only|, prints only on the transcript file. + +\hang |term_only|, prints only on the terminal. + +\hang |no_print|, doesn't print at all. This is used only in rare cases + before the transcript file is open. + +\hang |ps_file_only| prints only on the \ps\ output file. + +\hang |pseudo|, puts output into a cyclic buffer that is used + by the |show_context| routine; when we get to that routine we shall discuss + the reasoning behind this curious mode. + +\hang |new_string|, appends the output to the current string in the + string pool. + +\hang |0..max_write_files-1| prints on one of the files used for the \&{write} +@:write_}{\&{write} primitive@> + command. + +\yskip +\noindent The symbolic names `|term_and_log|', etc., have been assigned +numeric codes that satisfy the convenient relations |no_print+1=term_only|, +|no_print+2=log_only|, |term_only+2=log_only+1=term_and_log|. These +relations are not used when |selector| could be |pseudo|, |new_string|, +or |ps_file_only|. We need not check for unprintable characters when +|selector<pseudo|. + +Four additional global variables, |tally|, |term_offset|, |file_offset|, +and |ps_offset| record the number of characters that have been printed +since they were most recently cleared to zero. We use |tally| to record +the length of (possibly very long) stretches of printing; |term_offset|, +|file_offset|, and |ps_offset|, on the other hand, keep track of how many +characters have appeared so far on the current line that has been output +to the terminal, the transcript file, or the \ps\ output file, respectively. + +@d new_string=max_write_files {printing is deflected to the string pool} +@d ps_file_only=new_string+1 {printing goes to the \ps\ output file} +@d pseudo=new_string+2 {special |selector| setting for |show_context|} +@d no_print=new_string+3 {|selector| setting that makes data disappear} +@d term_only=new_string+4 {printing is destined for the terminal only} +@d log_only=new_string+5 {printing is destined for the transcript file only} +@d term_and_log=new_string+6 {normal |selector| setting} +@d max_selector=term_and_log {highest selector setting} + +@<Glob...@>= +@!log_file : alpha_file; {transcript of \MP\ session} +@!ps_file: alpha_file; {the generic font output goes here} +@!selector : 0..max_selector; {where to print a message} +@!dig : array[0..22] of 0..15; {digits in a number being output} +@!tally : integer; {the number of characters recently printed} +@!term_offset : 0..max_print_line; + {the number of characters on the current terminal line} +@!file_offset : 0..max_print_line; + {the number of characters on the current file line} +@!ps_offset : integer; + {the number of characters on the current \ps\ file line} +@!trick_buf:array[0..error_line] of ASCII_code; {circular buffer for + pseudoprinting} +@!trick_count: integer; {threshold for pseudoprinting, explained later} +@!first_count: integer; {another variable for pseudoprinting} + +@ @<Initialize the output routines@>= +selector:=term_only; tally:=0; term_offset:=0; file_offset:=0; ps_offset:=0; + +@ Macro abbreviations for output to the terminal and to the log file are +defined here for convenience. Some systems need special conventions +for terminal output, and it is possible to adhere to those conventions +by changing |wterm|, |wterm_ln|, and |wterm_cr| here. +@^system dependencies@> + +@d wterm(#)==write(term_out,#) +@d wterm_ln(#)==write_ln(term_out,#) +@d wterm_cr==write_ln(term_out) +@d wlog(#)==write(log_file,#) +@d wlog_ln(#)==write_ln(log_file,#) +@d wlog_cr==write_ln(log_file) +@d wps(#)==write(ps_file,#) +@d wps_ln(#)==write_ln(ps_file,#) +@d wps_cr==write_ln(ps_file) + +@ To end a line of text output, we call |print_ln|. Cases |0..max_write_files| +use an array |wr_file| that will be declared later. + +@<Basic print...@>= +procedure print_ln; {prints an end-of-line} +begin case selector of +term_and_log: begin wterm_cr; wlog_cr; + term_offset:=0; file_offset:=0; + end; +log_only: begin wlog_cr; file_offset:=0; + end; +term_only: begin wterm_cr; term_offset:=0; + end; +ps_file_only: begin wps_cr; ps_offset:=0; + end; +no_print,pseudo,new_string: do_nothing; +othercases write_ln(wr_file[selector]) +endcases; +end; {note that |tally| is not affected} + +@ The |print_visible_char| procedure sends one character to the desired +destination, using the |xchr| array to map it into an external character +compatible with |input_ln|. (It assumes that it is always called with +a visible ASCII character.) All printing comes through |print_ln| or +|print_char|, which ultimately calls |print_visible_char|, hence these +routines are the ones that limit lines to at most |max_print_line| characters. +But we must make an exception for the \ps\ output file since it is not safe +to cut up lines arbitrarily in \ps. + +Procedure |unit_str_room| needs to be declared |forward| here because it calls +|do_compaction| and |do_compaction| can call the error routines. Actually, +|unit_str_room| avoids |overflow| errors but it can call |confusion|. + +@<Basic printing...@>= +procedure@?unit_str_room; forward;@t\2@>@/ +procedure print_visible_char(@!s:ASCII_code); {prints a single character} +label done; +begin case selector of +term_and_log: begin wterm(xchr[s]); wlog(xchr[s]); + incr(term_offset); incr(file_offset); + if term_offset=max_print_line then + begin wterm_cr; term_offset:=0; + end; + if file_offset=max_print_line then + begin wlog_cr; file_offset:=0; + end; + end; +log_only: begin wlog(xchr[s]); incr(file_offset); + if file_offset=max_print_line then print_ln; + end; +term_only: begin wterm(xchr[s]); incr(term_offset); + if term_offset=max_print_line then print_ln; + end; +ps_file_only: begin wps(xchr[s]); incr(ps_offset); + end; +no_print: do_nothing; +pseudo: if tally<trick_count then trick_buf[tally mod error_line]:=s; +new_string: begin if pool_ptr>=max_pool_ptr then + begin unit_str_room; + if pool_ptr>=pool_size then goto done; + {drop characters if string space is full} + end; + append_char(s); + end; +othercases write(wr_file[selector],xchr[s]) +endcases; +done:incr(tally); +end; + +@ The |print_char| procedure sends one character to the desired destination. +File names and string expressions might contain |ASCII_code| values that +can't be printed using |print_visible_char|. These characters will be +printed in three- or four-symbol form like `\.{\^\^A}' or `\.{\^\^e4}'. +(This procedure assumes that it is safe to bypass all checks for unprintable +characters when |selector| is in the range |0..max_write_files-1| or when +|selector=ps_file_only|. In the former case the user might want to write +unprintable characters, and in the latter case the \ps\ printing routines +check their arguments themselves before calling |print_char| or |print|.) + +@d print_lc_hex(#)==l:=#; + if l<10 then print_visible_char(l+"0")@+else print_visible_char(l-10+"a") + +@<Basic printing...@>= +procedure print_char(@!k:ASCII_code); {prints a single character} +var l:0..255; {small index or counter} +begin if selector<pseudo then print_visible_char(k) +else if @<Character |k| cannot be printed@> then + begin print_visible_char("^"); print_visible_char("^"); + if k<@'100 then print_visible_char(k+@'100) + else if k<@'200 then print_visible_char(k-@'100) + else begin print_lc_hex(k div 16); print_lc_hex(k mod 16); + end; + end +else print_visible_char(k); +end; + +@ An entire string is output by calling |print|. Note that if we are outputting +the single standard ASCII character \.c, we could call |print("c")|, since +|"c"=99| is the number of a single-character string, as explained above. But +|print_char("c")| is quicker, so \MP\ goes directly to the |print_char| +routine when it knows that this is safe. (The present implementation +assumes that it is always safe to print a visible ASCII character.) +@^system dependencies@> + +@<Basic print...@>= +procedure print(@!s:integer); {prints string |s|} +var @!j:pool_pointer; {current character code position} +begin if (s<0)or(s>max_str_ptr) then s:="???"; {this can't happen} +@.???@> +j:=str_start[s]; +while j<str_stop(s) do + begin print_char(so(str_pool[j])); incr(j); + end; +end; + +@ By popular demand, \MP\ prints the banner line only on the transcript file. +Thus there is nothing special to be printed here. + +@<Initialize the output...@>= +update_terminal; + +@ The procedure |print_nl| is like |print|, but it makes sure that the +string appears at the beginning of a new line. + +@<Basic print...@>= +procedure print_nl(@!s:str_number); {prints string |s| at beginning of line} +begin case selector of +term_and_log: if (term_offset>0)or(file_offset>0) then print_ln; +log_only: if file_offset>0 then print_ln; +term_only: if term_offset>0 then print_ln; +ps_file_only: if ps_offset>0 then print_ln; +no_print,pseudo,new_string: do_nothing; +end; {there are no other cases} +print(s); +end; + +@ An array of digits in the range |0..9| is printed by |print_the_digs|. + +@<Basic print...@>= +procedure print_the_digs(@!k:eight_bits); + {prints |dig[k-1]|$\,\ldots\,$|dig[0]|} +begin while k>0 do + begin decr(k); print_char("0"+dig[k]); + end; +end; + +@ The following procedure, which prints out the decimal representation of a +given integer |n|, has been written carefully so that it works properly +if |n=0| or if |(-n)| would cause overflow. It does not apply |mod| or |div| +to negative arguments, since such operations are not implemented consistently +by all \PASCAL\ compilers. + +@<Basic print...@>= +procedure print_int(@!n:integer); {prints an integer in decimal form} +var k:0..23; {index to current digit; we assume that $|n|<10^{23}$} +@!m:integer; {used to negate |n| in possibly dangerous cases} +begin k:=0; +if n<0 then + begin print_char("-"); + if n>-100000000 then negate(n) + else begin m:=-1-n; n:=m div 10; m:=(m mod 10)+1; k:=1; + if m<10 then dig[0]:=m + else begin dig[0]:=0; incr(n); + end; + end; + end; +repeat dig[k]:=n mod 10; n:=n div 10; incr(k); +until n=0; +print_the_digs(k); +end; + +@ \MP\ also makes use of a trivial procedure to print two digits. The +following subroutine is usually called with a parameter in the range |0<=n<=99|. + +@p procedure print_dd(@!n:integer); {prints two least significant digits} +begin n:=abs(n) mod 100; print_char("0"+(n div 10)); +print_char("0"+(n mod 10)); +end; + +@ Here is a procedure that asks the user to type a line of input, +assuming that the |selector| setting is either |term_only| or |term_and_log|. +The input is placed into locations |first| through |last-1| of the +|buffer| array, and echoed on the transcript file if appropriate. + +This procedure is never called when |interaction<scroll_mode|. + +@d prompt_input(#)==begin wake_up_terminal; print(#); term_input; + end {prints a string and gets a line of input} + +@p procedure term_input; {gets a line from the terminal} +var @!k:0..buf_size; {index into |buffer|} +begin update_terminal; {Now the user sees the prompt for sure} +if not input_ln(term_in,true) then fatal_error("End of file on the terminal!"); +@.End of file on the terminal@> +term_offset:=0; {the user's line ended with \<\rm return>} +decr(selector); {prepare to echo the input} +if last<>first then for k:=first to last-1 do print(buffer[k]); +print_ln; buffer[last]:="%"; incr(selector); {restore previous status} +end; + +@* \[6] Reporting errors. +When something anomalous is detected, \MP\ typically does something like this: +$$\vbox{\halign{#\hfil\cr +|print_err("Something anomalous has been detected");|\cr +|help3("This is the first line of my offer to help.")|\cr +|("This is the second line. I'm trying to")|\cr +|("explain the best way for you to proceed.");|\cr +|error;|\cr}}$$ +A two-line help message would be given using |help2|, etc.; these informal +helps should use simple vocabulary that complements the words used in the +official error message that was printed. (Outside the U.S.A., the help +messages should preferably be translated into the local vernacular. Each +line of help is at most 60 characters long, in the present implementation, +so that |max_print_line| will not be exceeded.) + +The |print_err| procedure supplies a `\.!' before the official message, +and makes sure that the terminal is awake if a stop is going to occur. +The |error| procedure supplies a `\..' after the official message, then it +shows the location of the error; and if |interaction=error_stop_mode|, +it also enters into a dialog with the user, during which time the help +message may be printed. +@^system dependencies@> + +@ The global variable |interaction| has four settings, representing increasing +amounts of user interaction: + +@d batch_mode=0 {omits all stops and omits terminal output} +@d nonstop_mode=1 {omits all stops} +@d scroll_mode=2 {omits error stops} +@d error_stop_mode=3 {stops at every opportunity to interact} +@d print_err(#)==begin if interaction=error_stop_mode then wake_up_terminal; + print_nl("! "); print(#); +@.!\relax@> + end + +@<Glob...@>= +@!interaction:batch_mode..error_stop_mode; {current level of interaction} + +@ @<Set init...@>=interaction:=error_stop_mode; + +@ \MP\ is careful not to call |error| when the print |selector| setting +might be unusual. The only possible values of |selector| at the time of +error messages are + +\yskip\hang|no_print| (when |interaction=batch_mode| + and |log_file| not yet open); + +\hang|term_only| (when |interaction>batch_mode| and |log_file| not yet open); + +\hang|log_only| (when |interaction=batch_mode| and |log_file| is open); + +\hang|term_and_log| (when |interaction>batch_mode| and |log_file| is open). + +@<Initialize the print |selector| based on |interaction|@>= +if interaction=batch_mode then selector:=no_print@+else selector:=term_only + +@ A global variable |deletions_allowed| is set |false| if the |get_next| +routine is active when |error| is called; this ensures that |get_next| +will never be called recursively. +@^recursion@> + +The global variable |history| records the worst level of error that +has been detected. It has four possible values: |spotless|, |warning_issued|, +|error_message_issued|, and |fatal_error_stop|. + +Another global variable, |error_count|, is increased by one when an +|error| occurs without an interactive dialog, and it is reset to zero at +the end of every statement. If |error_count| reaches 100, \MP\ decides +that there is no point in continuing further. + +@d spotless=0 {|history| value when nothing has been amiss yet} +@d warning_issued=1 {|history| value when |begin_diagnostic| has been called} +@d error_message_issued=2 {|history| value when |error| has been called} +@d fatal_error_stop=3 {|history| value when termination was premature} + +@<Glob...@>= +@!deletions_allowed:boolean; {is it safe for |error| to call |get_next|?} +@!history:spotless..fatal_error_stop; {has the source input been clean so far?} +@!error_count:-1..100; {the number of scrolled errors since the + last statement ended} + +@ The value of |history| is initially |fatal_error_stop|, but it will +be changed to |spotless| if \MP\ survives the initialization process. + +@<Set init...@>= +deletions_allowed:=true; error_count:=0; {|history| is initialized elsewhere} + +@ Since errors can be detected almost anywhere in \MP, we want to declare the +error procedures near the beginning of the program. But the error procedures +in turn use some other procedures, which need to be declared |forward| +before we get to |error| itself. + +It is possible for |error| to be called recursively if some error arises +when |get_next| is being used to delete a token, and/or if some fatal error +occurs while \MP\ is trying to fix a non-fatal one. But such recursion +@^recursion@> +is never more than two levels deep. + +@<Error handling...@>= +procedure@?normalize_selector; forward;@t\2@>@/ +procedure@?get_next; forward;@t\2@>@/ +procedure@?term_input; forward;@t\2@>@/ +procedure@?show_context; forward;@t\2@>@/ +procedure@?begin_file_reading; forward;@t\2@>@/ +procedure@?open_log_file; forward;@t\2@>@/ +procedure@?close_files_and_terminate; forward;@t\2@>@/ +procedure@?clear_for_error_prompt; forward;@t\2@>@/ +@t\4\hskip-\fontdimen2\font@>@;@+@!debug@+procedure@?debug_help; + forward;@;@+gubed@;@/ +@t\4@>@<Declare the procedure called |flush_string|@> + +@ Individual lines of help are recorded in the array |help_line|, which +contains entries in positions |0..(help_ptr-1)|. They should be printed +in reverse order, i.e., with |help_line[0]| appearing last. + +@d hlp1(#)==help_line[0]:=#;@+end +@d hlp2(#)==help_line[1]:=#; hlp1 +@d hlp3(#)==help_line[2]:=#; hlp2 +@d hlp4(#)==help_line[3]:=#; hlp3 +@d hlp5(#)==help_line[4]:=#; hlp4 +@d hlp6(#)==help_line[5]:=#; hlp5 +@d help0==help_ptr:=0 {sometimes there might be no help} +@d help1==@+begin help_ptr:=1; hlp1 {use this with one help line} +@d help2==@+begin help_ptr:=2; hlp2 {use this with two help lines} +@d help3==@+begin help_ptr:=3; hlp3 {use this with three help lines} +@d help4==@+begin help_ptr:=4; hlp4 {use this with four help lines} +@d help5==@+begin help_ptr:=5; hlp5 {use this with five help lines} +@d help6==@+begin help_ptr:=6; hlp6 {use this with six help lines} + +@<Glob...@>= +@!help_line:array[0..5] of str_number; {helps for the next |error|} +@!help_ptr:0..6; {the number of help lines present} +@!use_err_help:boolean; {should the |err_help| string be shown?} +@!err_help:str_number; {a string set up by \&{errhelp}} + +@ @<Set init...@>= +help_ptr:=0; use_err_help:=false; err_help:=0; + +@ The |jump_out| procedure just cuts across all active procedure levels and +goes to |end_of_MP|. This is the only nonlocal |@!goto| statement in the +whole program. It is used when there is no recovery from a particular error. + +Some \PASCAL\ compilers do not implement non-local |goto| statements. +@^system dependencies@> +In such cases the body of |jump_out| should simply be +`|close_files_and_terminate|;\thinspace' followed by a call on some system +procedure that quietly terminates the program. + +@<Error hand...@>= +procedure jump_out; +begin goto end_of_MP; +end; + +@ Here now is the general |error| routine. + +@<Error hand...@>= +procedure error; {completes the job of error reporting} +label continue,exit; +var @!c:ASCII_code; {what the user types} +@!s1,@!s2,@!s3:integer; {used to save global variables when deleting tokens} +@!j:pool_pointer; {character position being printed} +begin if history<error_message_issued then history:=error_message_issued; +print_char("."); show_context; +if interaction=error_stop_mode then @<Get user's advice and |return|@>; +incr(error_count); +if error_count=100 then + begin print_nl("(That makes 100 errors; please try again.)"); +@.That makes 100 errors...@> + history:=fatal_error_stop; jump_out; + end; +@<Put help message on the transcript file@>; +exit:end; + +@ @<Get user's advice...@>= +loop@+begin continue: clear_for_error_prompt; prompt_input("? "); +@.?\relax@> + if last=first then return; + c:=buffer[first]; + if c>="a" then c:=c+"A"-"a"; {convert to uppercase} + @<Interpret code |c| and |return| if done@>; + end + +@ It is desirable to provide an `\.E' option here that gives the user +an easy way to return from \MP\ to the system editor, with the offending +line ready to be edited. But such an extension requires some system +wizardry, so the present implementation simply types out the name of the +file that should be +edited and the relevant line number. +@^system dependencies@> + +There is a secret `\.D' option available when the debugging routines haven't +been commented~out. +@^debugging@> + +@<Interpret code |c| and |return| if done@>= +case c of +"0","1","2","3","4","5","6","7","8","9": if deletions_allowed then + @<Delete |c-"0"| tokens and |goto continue|@>; +@t\4\4@>@;@+@!debug "D":begin debug_help;goto continue;@+end;@+gubed@/ +"E": if file_ptr>0 then + begin print_nl("You want to edit file "); +@.You want to edit file x@> + print(input_stack[file_ptr].name_field); + print(" at line "); print_int(true_line);@/ + interaction:=scroll_mode; jump_out; + end; +"H": @<Print the help information and |goto continue|@>; +"I":@<Introduce new material from the terminal and |return|@>; +"Q","R","S":@<Change the interaction level and |return|@>; +"X":begin interaction:=scroll_mode; jump_out; + end; +othercases do_nothing +endcases;@/ +@<Print the menu of available options@> + +@ @<Print the menu...@>= +begin print("Type <return> to proceed, S to scroll future error messages,");@/ +@.Type <return> to proceed...@> +print_nl("R to run without stopping, Q to run quietly,");@/ +print_nl("I to insert something, "); +if file_ptr>0 then print("E to edit your file,"); +if deletions_allowed then + print_nl("1 or ... or 9 to ignore the next 1 to 9 tokens of input,"); +print_nl("H for help, X to quit."); +end + +@ Here the author of \MP\ apologizes for making use of the numerical +relation between |"Q"|, |"R"|, |"S"|, and the desired interaction settings +|batch_mode|, |nonstop_mode|, |scroll_mode|. +@^Knuth, Donald Ervin@> + +@<Change the interaction...@>= +begin error_count:=0; interaction:=batch_mode+c-"Q"; +print("OK, entering "); +case c of +"Q":begin print("batchmode"); decr(selector); + end; +"R":print("nonstopmode"); +"S":print("scrollmode"); +end; {there are no other cases} +print("..."); print_ln; update_terminal; return; +end + +@ When the following code is executed, |buffer[(first+1)..(last-1)]| may +contain the material inserted by the user; otherwise another prompt will +be given. In order to understand this part of the program fully, you need +to be familiar with \MP's input stacks. + +@<Introduce new material...@>= +begin begin_file_reading; {enter a new syntactic level for terminal input} +if last>first+1 then + begin loc:=first+1; buffer[first]:=" "; + end +else begin prompt_input("insert>"); loc:=first; +@.insert>@> + end; +first:=last+1; cur_input.limit_field:=last; return; +end + +@ We allow deletion of up to 99 tokens at a time. + +@<Delete |c-"0"| tokens...@>= +begin s1:=cur_cmd; s2:=cur_mod; s3:=cur_sym; OK_to_interrupt:=false; +if (last>first+1) and (buffer[first+1]>="0")and(buffer[first+1]<="9") then + c:=c*10+buffer[first+1]-"0"*11 +else c:=c-"0"; +while c>0 do + begin get_next; {one-level recursive call of |error| is possible} + @<Decrease the string reference count, if the current token is a string@>; + decr(c); + end; +cur_cmd:=s1; cur_mod:=s2; cur_sym:=s3; OK_to_interrupt:=true; +help2("I have just deleted some text, as you asked.")@/ +("You can now delete more, or insert, or whatever."); +show_context; goto continue; +end + +@ @<Print the help info...@>= +begin if use_err_help then + begin @<Print the string |err_help|, possibly on several lines@>; + use_err_help:=false; + end +else begin if help_ptr=0 then + help2("Sorry, I don't know how to help in this situation.")@/ + @t\kern1em@>("Maybe you should try asking a human?"); + repeat decr(help_ptr); print(help_line[help_ptr]); print_ln; + until help_ptr=0; + end; +help4("Sorry, I already gave what help I could...")@/ + ("Maybe you should try asking a human?")@/ + ("An error might have occurred before I noticed any problems.")@/ + ("``If all else fails, read the instructions.''");@/ +goto continue; +end + +@ @<Print the string |err_help|, possibly on several lines@>= +j:=str_start[err_help]; +while j<str_stop(err_help) do + begin if str_pool[j]<>si("%") then print(so(str_pool[j])) + else if j+1=str_stop(err_help) then print_ln + else if str_pool[j+1]<>si("%") then print_ln + else begin incr(j); print_char("%"); + end; + incr(j); + end + +@ @<Put help message on the transcript file@>= +if interaction>batch_mode then decr(selector); {avoid terminal output} +if use_err_help then + begin print_nl(""); + @<Print the string |err_help|, possibly on several lines@>; + end +else while help_ptr>0 do + begin decr(help_ptr); print_nl(help_line[help_ptr]); + end; +print_ln; +if interaction>batch_mode then incr(selector); {re-enable terminal output} +print_ln + +@ In anomalous cases, the print selector might be in an unknown state; +the following subroutine is called to fix things just enough to keep +running a bit longer. + +@p procedure normalize_selector; +begin if log_opened then selector:=term_and_log +else selector:=term_only; +if job_name=0 then open_log_file; +if interaction=batch_mode then decr(selector); +end; + +@ The following procedure prints \MP's last words before dying. + +@d succumb==begin if interaction=error_stop_mode then + interaction:=scroll_mode; {no more interaction} + if log_opened then error; + @!debug if interaction>batch_mode then debug_help;@;@+gubed@;@/ + history:=fatal_error_stop; jump_out; {irrecoverable error} + end + +@<Error hand...@>= +procedure fatal_error(@!s:str_number); {prints |s|, and that's it} +begin normalize_selector;@/ +print_err("Emergency stop"); help1(s); succumb; +@.Emergency stop@> +end; + +@ Here is the most dreaded error message. + +@<Error hand...@>= +procedure overflow(@!s:str_number;@!n:integer); {stop due to finiteness} +begin normalize_selector; +print_err("MetaPost capacity exceeded, sorry ["); +@.MetaPost capacity exceeded ...@> +print(s); print_char("="); print_int(n); print_char("]"); +help2("If you really absolutely need more capacity,")@/ + ("you can ask a wizard to enlarge me."); +succumb; +end; + +@ The program might sometime run completely amok, at which point there is +no choice but to stop. If no previous error has been detected, that's bad +news; a message is printed that is really intended for the \MP\ +maintenance person instead of the user (unless the user has been +particularly diabolical). The index entries for `this can't happen' may +help to pinpoint the problem. +@^dry rot@> + +@<Error hand...@>= +procedure confusion(@!s:str_number); + {consistency check violated; |s| tells where} +begin normalize_selector; +if history<error_message_issued then + begin print_err("This can't happen ("); print(s); print_char(")"); +@.This can't happen@> + help1("I'm broken. Please show this to someone who can fix can fix"); + end +else begin print_err("I can't go on meeting you like this"); +@.I can't go on...@> + help2("One of your faux pas seems to have wounded me deeply...")@/ + ("in fact, I'm barely conscious. Please fix it and try again."); + end; +succumb; +end; + +@ Users occasionally want to interrupt \MP\ while it's running. +If the \PASCAL\ runtime system allows this, one can implement +a routine that sets the global variable |interrupt| to some nonzero value +when such an interrupt is signaled. Otherwise there is probably at least +a way to make |interrupt| nonzero using the \PASCAL\ debugger. +@^system dependencies@> +@^debugging@> + +@d check_interrupt==begin if interrupt<>0 then pause_for_instructions; + end + +@<Global...@>= +@!interrupt:integer; {should \MP\ pause for instructions?} +@!OK_to_interrupt:boolean; {should interrupts be observed?} + +@ @<Set init...@>= +interrupt:=0; OK_to_interrupt:=true; + +@ When an interrupt has been detected, the program goes into its +highest interaction level and lets the user have the full flexibility of +the |error| routine. \MP\ checks for interrupts only at times when it is +safe to do this. + +@p procedure pause_for_instructions; +begin if OK_to_interrupt then + begin interaction:=error_stop_mode; + if (selector=log_only)or(selector=no_print) then + incr(selector); + print_err("Interruption"); +@.Interruption@> + help3("You rang?")@/ + ("Try to insert some instructions for me (e.g.,`I show x'),")@/ + ("unless you just want to quit by typing `X'."); + deletions_allowed:=false; error; deletions_allowed:=true; + interrupt:=0; + end; +end; + +@ Many of \MP's error messages state that a missing token has been +inserted behind the scenes. We can save string space and program space +by putting this common code into a subroutine. + +@p procedure missing_err(@!s:str_number); +begin print_err("Missing `"); print(s); print("' has been inserted"); +@.Missing...inserted@> +end; + +@* \[7] Arithmetic with scaled numbers. +The principal computations performed by \MP\ are done entirely in terms of +integers less than $2^{31}$ in magnitude; thus, the arithmetic specified in this +program can be carried out in exactly the same way on a wide variety of +computers, including some small ones. +@^small computers@> + +But \PASCAL\ does not define the @!|div| +operation in the case of negative dividends; for example, the result of +|(-2*n-1) div 2| is |-(n+1)| on some computers and |-n| on others. +There are two principal types of arithmetic: ``translation-preserving,'' +in which the identity |(a+q*b)div b=(a div b)+q| is valid; and +``negation-preserving,'' in which |(-a)div b=-(a div b)|. This leads to +two \MP s, which can produce different results, although the differences +should be negligible when the language is being used properly. +The \TeX\ processor has been defined carefully so that both varieties +of arithmetic will produce identical output, but it would be too +inefficient to constrain \MP\ in a similar way. + +@d el_gordo == @'17777777777 {$2^{31}-1$, the largest value that \MP\ likes} + +@ One of \MP's most common operations is the calculation of +$\lfloor{a+b\over2}\rfloor$, +the midpoint of two given integers |a| and~|b|. The only decent way to do +this in \PASCAL\ is to write `|(a+b) div 2|'; but on most machines it is +far more efficient to calculate `|(a+b)| right shifted one bit'. + +Therefore the midpoint operation will always be denoted by `|half(a+b)|' +in this program. If \MP\ is being implemented with languages that permit +binary shifting, the |half| macro should be changed to make this operation +as efficient as possible. Since some languages have shift operators that can +only be trusted to work on positive numbers, there is also a macro |halfp| +that is used only when the quantity being halved is known to be positive +or zero. + +@d half(#)==(#) div 2 +@d halfp(#)==(#) div 2 + +@ A single computation might use several subroutine calls, and it is +desirable to avoid producing multiple error messages in case of arithmetic +overflow. So the routines below set the global variable |arith_error| to |true| +instead of reporting errors directly to the user. + +@<Glob...@>= +@!arith_error:boolean; {has arithmetic overflow occurred recently?} + +@ @<Set init...@>= +arith_error:=false; + +@ At crucial points the program will say |check_arith|, to test if +an arithmetic error has been detected. + +@d check_arith==begin if arith_error then clear_arith;@+end + +@p procedure clear_arith; +begin print_err("Arithmetic overflow"); +@.Arithmetic overflow@> +help4("Uh, oh. A little while ago one of the quantities that I was")@/ + ("computing got too large, so I'm afraid your answers will be")@/ + ("somewhat askew. You'll probably have to adopt different")@/ + ("tactics next time. But I shall try to carry on anyway."); +error; arith_error:=false; +end; + +@ Addition is not always checked to make sure that it doesn't overflow, +but in places where overflow isn't too unlikely the |slow_add| routine +is used. + +@p function slow_add(@!x,@!y:integer):integer; +begin if x>=0 then + if y<=el_gordo-x then slow_add:=x+y + else begin arith_error:=true; slow_add:=el_gordo; + end +else if -y<=el_gordo+x then slow_add:=x+y + else begin arith_error:=true; slow_add:=-el_gordo; + end; +end; + +@ Fixed-point arithmetic is done on {\sl scaled integers\/} that are multiples +of $2^{-16}$. In other words, a binary point is assumed to be sixteen bit +positions from the right end of a binary computer word. + +@d quarter_unit == @'40000 {$2^{14}$, represents 0.250000} +@d half_unit == @'100000 {$2^{15}$, represents 0.50000} +@d three_quarter_unit == @'140000 {$3\cdot2^{14}$, represents 0.75000} +@d unity == @'200000 {$2^{16}$, represents 1.00000} +@d two == @'400000 {$2^{17}$, represents 2.00000} +@d three == @'600000 {$2^{17}+2^{16}$, represents 3.00000} + +@<Types...@>= +@!scaled = integer; {this type is used for scaled integers} +@!small_number=0..63; {this type is self-explanatory} + +@ The following function is used to create a scaled integer from a given decimal +fraction $(.d_0d_1\ldots d_{k-1})$, where |0<=k<=17|. The digit $d_i$ is +given in |dig[i]|, and the calculation produces a correctly rounded result. + +@p function round_decimals(@!k:small_number) : scaled; + {converts a decimal fraction} +var @!a:integer; {the accumulator} +begin a:=0; +while k>0 do + begin decr(k); a:=(a+dig[k]*two) div 10; + end; +round_decimals:=halfp(a+1); +end; + +@ Conversely, here is a procedure analogous to |print_int|. If the output +of this procedure is subsequently read by \MP\ and converted by the +|round_decimals| routine above, it turns out that the original value will +be reproduced exactly. A decimal point is printed only if the value is +not an integer. If there is more than one way to print the result with +the optimum number of digits following the decimal point, the closest +possible value is given. + +The invariant relation in the \&{repeat} loop is that a sequence of +decimal digits yet to be printed will yield the original number if and only if +they form a fraction~$f$ in the range $s-\delta\L10\cdot2^{16}f<s$. +We can stop if and only if $f=0$ satisfies this condition; the loop will +terminate before $s$ can possibly become zero. + +@<Basic printing...@>= +procedure print_scaled(@!s:scaled); {prints scaled real, rounded to five + digits} +var @!delta:scaled; {amount of allowable inaccuracy} +begin if s<0 then + begin print_char("-"); negate(s); {print the sign, if negative} + end; +print_int(s div unity); {print the integer part} +s:=10*(s mod unity)+5; +if s<>5 then + begin delta:=10; print_char("."); + repeat if delta>unity then + s:=s+@'100000-(delta div 2); {round the final digit} + print_char("0"+(s div unity)); s:=10*(s mod unity); delta:=delta*10; + until s<=delta; + end; +end; + +@ We often want to print two scaled quantities in parentheses, +separated by a comma. + +@<Basic printing...@>= +procedure print_two(@!x,@!y:scaled); {prints `|(x,y)|'} +begin print_char("("); print_scaled(x); print_char(","); print_scaled(y); +print_char(")"); +end; + +@ The |scaled| quantities in \MP\ programs are generally supposed to be +less than $2^{12}$ in absolute value, so \MP\ does much of its internal +arithmetic with 28~significant bits of precision. A |fraction| denotes +a scaled integer whose binary point is assumed to be 28 bit positions +from the right. + +@d fraction_half==@'1000000000 {$2^{27}$, represents 0.50000000} +@d fraction_one==@'2000000000 {$2^{28}$, represents 1.00000000} +@d fraction_two==@'4000000000 {$2^{29}$, represents 2.00000000} +@d fraction_three==@'6000000000 {$3\cdot2^{28}$, represents 3.00000000} +@d fraction_four==@'10000000000 {$2^{30}$, represents 4.00000000} + +@<Types...@>= +@!fraction=integer; {this type is used for scaled fractions} + +@ In fact, the two sorts of scaling discussed above aren't quite +sufficient; \MP\ has yet another, used internally to keep track of angles +in units of $2^{-20}$ degrees. + +@d forty_five_deg==@'264000000 {$45\cdot2^{20}$, represents $45^\circ$} +@d ninety_deg==@'550000000 {$90\cdot2^{20}$, represents $90^\circ$} +@d one_eighty_deg==@'1320000000 {$180\cdot2^{20}$, represents $180^\circ$} +@d three_sixty_deg==@'2640000000 {$360\cdot2^{20}$, represents $360^\circ$} + +@<Types...@>= +@!angle=integer; {this type is used for scaled angles} + +@ The |make_fraction| routine produces the |fraction| equivalent of +|p/q|, given integers |p| and~|q|; it computes the integer +$f=\lfloor2^{28}p/q+{1\over2}\rfloor$, when $p$ and $q$ are +positive. If |p| and |q| are both of the same scaled type |t|, +the ``type relation'' |make_fraction(t,t)=fraction| is valid; +and it's also possible to use the subroutine ``backwards,'' using +the relation |make_fraction(t,fraction)=t| between scaled types. + +If the result would have magnitude $2^{31}$ or more, |make_fraction| +sets |arith_error:=true|. Most of \MP's internal computations have +been designed to avoid this sort of error. + +If this subroutine were programmed in assembly language on a typical +machine, we could simply compute |(@t$2^{28}$@>*p)div q|, since a +double-precision product can often be input to a fixed-point division +instruction. But when we are restricted to \PASCAL\ arithmetic it +is necessary either to resort to multiple-precision maneuvering +or to use a simple but slow iteration. The multiple-precision technique +would be about three times faster than the code adopted here, but it +would be comparatively long and tricky, involving about sixteen +additional multiplications and divisions. + +This operation is part of \MP's ``inner loop''; indeed, it will +consume nearly 10\pct! of the running time (exclusive of input and output) +if the code below is left unchanged. A machine-dependent recoding +will therefore make \MP\ run faster. The present implementation +is highly portable, but slow; it avoids multiplication and division +except in the initial stage. System wizards should be careful to +replace it with a routine that is guaranteed to produce identical +results in all cases. +@^system dependencies@> + +As noted below, a few more routines should also be replaced by machine-dependent +code, for efficiency. But when a procedure is not part of the ``inner loop,'' +such changes aren't advisable; simplicity and robustness are +preferable to trickery, unless the cost is too high. +@^inner loop@> + +@p function make_fraction(@!p,@!q:integer):fraction; +var @!f:integer; {the fraction bits, with a leading 1 bit} +@!n:integer; {the integer part of $\vert p/q\vert$} +@!negative:boolean; {should the result be negated?} +@!be_careful:integer; {disables certain compiler optimizations} +begin if p>=0 then negative:=false +else begin negate(p); negative:=true; + end; +if q<=0 then + begin debug if q=0 then confusion("/");@;@+gubed@;@/ +@:this can't happen /}{\quad \./@> + negate(q); negative:=not negative; + end; +n:=p div q; p:=p mod q; +if n>=8 then + begin arith_error:=true; + if negative then make_fraction:=-el_gordo@+else make_fraction:=el_gordo; + end +else begin n:=(n-1)*fraction_one; + @<Compute $f=\lfloor 2^{28}(1+p/q)+{1\over2}\rfloor$@>; + if negative then make_fraction:=-(f+n)@+else make_fraction:=f+n; + end; +end; + +@ The |repeat| loop here preserves the following invariant relations +between |f|, |p|, and~|q|: +(i)~|0<=p<q|; (ii)~$fq+p=2^k(q+p_0)$, where $k$ is an integer and +$p_0$ is the original value of~$p$. + +Notice that the computation specifies +|(p-q)+p| instead of |(p+p)-q|, because the latter could overflow. +Let us hope that optimizing compilers do not miss this point; a +special variable |be_careful| is used to emphasize the necessary +order of computation. Optimizing compilers should keep |be_careful| +in a register, not store it in memory. +@^inner loop@> + +@<Compute $f=\lfloor 2^{28}(1+p/q)+{1\over2}\rfloor$@>= +f:=1; +repeat be_careful:=p-q; p:=be_careful+p; +if p>=0 then f:=f+f+1 +else begin double(f); p:=p+q; + end; +until f>=fraction_one; +be_careful:=p-q; +if be_careful+p>=0 then incr(f) + +@ The dual of |make_fraction| is |take_fraction|, which multiplies a +given integer~|q| by a fraction~|f|. When the operands are positive, it +computes $p=\lfloor qf/2^{28}+{1\over2}\rfloor$, a symmetric function +of |q| and~|f|. + +This routine is even more ``inner loopy'' than |make_fraction|; +the present implementation consumes almost 20\pct! of \MP's computation +time during typical jobs, so a machine-language substitute is advisable. +@^inner loop@> @^system dependencies@> + +@p function take_fraction(@!q:integer;@!f:fraction):integer; +var @!p:integer; {the fraction so far} +@!negative:boolean; {should the result be negated?} +@!n:integer; {additional multiple of $q$} +@!be_careful:integer; {disables certain compiler optimizations} +begin @<Reduce to the case that |f>=0| and |q>0|@>; +if f<fraction_one then n:=0 +else begin n:=f div fraction_one; f:=f mod fraction_one; + if q<=el_gordo div n then n:=n*q + else begin arith_error:=true; n:=el_gordo; + end; + end; +f:=f+fraction_one; +@<Compute $p=\lfloor qf/2^{28}+{1\over2}\rfloor-q$@>; +be_careful:=n-el_gordo; +if be_careful+p>0 then + begin arith_error:=true; n:=el_gordo-p; + end; +if negative then take_fraction:=-(n+p) +else take_fraction:=n+p; +end; + +@ @<Reduce to the case that |f>=0| and |q>0|@>= +if f>=0 then negative:=false +else begin negate(f); negative:=true; + end; +if q<0 then + begin negate(q); negative:=not negative; + end; + +@ The invariant relations in this case are (i)~$\lfloor(qf+p)/2^k\rfloor +=\lfloor qf_0/2^{28}+{1\over2}\rfloor$, where $k$ is an integer and +$f_0$ is the original value of~$f$; (ii)~$2^k\L f<2^{k+1}$. +@^inner loop@> + +@<Compute $p=\lfloor qf/2^{28}+{1\over2}\rfloor-q$@>= +p:=fraction_half; {that's $2^{27}$; the invariants hold now with $k=28$} +if q<fraction_four then + repeat if odd(f) then p:=halfp(p+q)@+else p:=halfp(p); + f:=halfp(f); + until f=1 +else repeat if odd(f) then p:=p+halfp(q-p)@+else p:=halfp(p); + f:=halfp(f); + until f=1 + + +@ When we want to multiply something by a |scaled| quantity, we use a scheme +analogous to |take_fraction| but with a different scaling. +Given positive operands, |take_scaled| +computes the quantity $p=\lfloor qf/2^{16}+{1\over2}\rfloor$. + +Once again it is a good idea to use a machine-language replacement if +possible; otherwise |take_scaled| will use more than 2\pct! of the running time +when the Computer Modern fonts are being generated. +@^inner loop@> + +@p function take_scaled(@!q:integer;@!f:scaled):integer; +var @!p:integer; {the fraction so far} +@!negative:boolean; {should the result be negated?} +@!n:integer; {additional multiple of $q$} +@!be_careful:integer; {disables certain compiler optimizations} +begin @<Reduce to the case that |f>=0| and |q>0|@>; +if f<unity then n:=0 +else begin n:=f div unity; f:=f mod unity; + if q<=el_gordo div n then n:=n*q + else begin arith_error:=true; n:=el_gordo; + end; + end; +f:=f+unity; +@<Compute $p=\lfloor qf/2^{16}+{1\over2}\rfloor-q$@>; +be_careful:=n-el_gordo; +if be_careful+p>0 then + begin arith_error:=true; n:=el_gordo-p; + end; +if negative then take_scaled:=-(n+p) +else take_scaled:=n+p; +end; + +@ @<Compute $p=\lfloor qf/2^{16}+{1\over2}\rfloor-q$@>= +p:=half_unit; {that's $2^{15}$; the invariants hold now with $k=16$} +@^inner loop@> +if q<fraction_four then + repeat if odd(f) then p:=halfp(p+q)@+else p:=halfp(p); + f:=halfp(f); + until f=1 +else repeat if odd(f) then p:=p+halfp(q-p)@+else p:=halfp(p); + f:=halfp(f); + until f=1 + +@ For completeness, there's also |make_scaled|, which computes a +quotient as a |scaled| number instead of as a |fraction|. +In other words, the result is $\lfloor2^{16}p/q+{1\over2}\rfloor$, if the +operands are positive. \ (This procedure is not used especially often, +so it is not part of \MP's inner loop.) + +@p function make_scaled(@!p,@!q:integer):scaled; +var @!f:integer; {the fraction bits, with a leading 1 bit} +@!n:integer; {the integer part of $\vert p/q\vert$} +@!negative:boolean; {should the result be negated?} +@!be_careful:integer; {disables certain compiler optimizations} +begin if p>=0 then negative:=false +else begin negate(p); negative:=true; + end; +if q<=0 then + begin debug if q=0 then confusion("/");@+gubed@;@/ +@:this can't happen /}{\quad \./@> + negate(q); negative:=not negative; + end; +n:=p div q; p:=p mod q; +if n>=@'100000 then + begin arith_error:=true; + if negative then make_scaled:=-el_gordo@+else make_scaled:=el_gordo; + end +else begin n:=(n-1)*unity; + @<Compute $f=\lfloor 2^{16}(1+p/q)+{1\over2}\rfloor$@>; + if negative then make_scaled:=-(f+n)@+else make_scaled:=f+n; + end; +end; + +@ @<Compute $f=\lfloor 2^{16}(1+p/q)+{1\over2}\rfloor$@>= +f:=1; +repeat be_careful:=p-q; p:=be_careful+p; +if p>=0 then f:=f+f+1 +else begin double(f); p:=p+q; + end; +until f>=unity; +be_careful:=p-q; +if be_careful+p>=0 then incr(f) + +@ Here is a typical example of how the routines above can be used. +It computes the function +$${1\over3\tau}f(\theta,\phi)= +{\tau^{-1}\bigl(2+\sqrt2\,(\sin\theta-{1\over16}\sin\phi) + (\sin\phi-{1\over16}\sin\theta)(\cos\theta-\cos\phi)\bigr)\over +3\,\bigl(1+{1\over2}(\sqrt5-1)\cos\theta+{1\over2}(3-\sqrt5\,)\cos\phi\bigr)},$$ +where $\tau$ is a |scaled| ``tension'' parameter. This is \MP's magic +fudge factor for placing the first control point of a curve that starts +at an angle $\theta$ and ends at an angle $\phi$ from the straight path. +(Actually, if the stated quantity exceeds 4, \MP\ reduces it to~4.) + +The trigonometric quantity to be multiplied by $\sqrt2$ is less than $\sqrt2$. +(It's a sum of eight terms whose absolute values can be bounded using +relations such as $\sin\theta\cos\theta\L{1\over2}$.) Thus the numerator +is positive; and since the tension $\tau$ is constrained to be at least +$3\over4$, the numerator is less than $16\over3$. The denominator is +nonnegative and at most~6. Hence the fixed-point calculations below +are guaranteed to stay within the bounds of a 32-bit computer word. + +The angles $\theta$ and $\phi$ are given implicitly in terms of |fraction| +arguments |st|, |ct|, |sf|, and |cf|, representing $\sin\theta$, $\cos\theta$, +$\sin\phi$, and $\cos\phi$, respectively. + +@p function velocity(@!st,@!ct,@!sf,@!cf:fraction;@!t:scaled):fraction; +var @!acc,@!num,@!denom:integer; {registers for intermediate calculations} +begin acc:=take_fraction(st-(sf div 16), sf-(st div 16)); +acc:=take_fraction(acc,ct-cf); +num:=fraction_two+take_fraction(acc,379625062); + {$2^{28}\sqrt2\approx379625062.497$} +denom:=fraction_three+take_fraction(ct,497706707)+take_fraction(cf,307599661); + {$3\cdot2^{27}\cdot(\sqrt5-1)\approx497706706.78$ and + $3\cdot2^{27}\cdot(3-\sqrt5\,)\approx307599661.22$} +if t<>unity then num:=make_scaled(num,t); + {|make_scaled(fraction,scaled)=fraction|} +if num div 4>=denom then velocity:=fraction_four +else velocity:=make_fraction(num,denom); +end; + +@ The following somewhat different subroutine tests rigorously if $ab$ is +greater than, equal to, or less than~$cd$, +given integers $(a,b,c,d)$. In most cases a quick decision is reached. +The result is $+1$, 0, or~$-1$ in the three respective cases. + +@d return_sign(#)==begin ab_vs_cd:=#; return; + end + +@p function ab_vs_cd(@!a,b,c,d:integer):integer; +label exit; +var @!q,@!r:integer; {temporary registers} +begin @<Reduce to the case that |a,c>=0|, |b,d>0|@>; +loop@+ begin q := a div d; r := c div b; + if q<>r then + if q>r then return_sign(1)@+else return_sign(-1); + q := a mod d; r := c mod b; + if r=0 then + if q=0 then return_sign(0)@+else return_sign(1); + if q=0 then return_sign(-1); + a:=b; b:=q; c:=d; d:=r; + end; {now |a>d>0| and |c>b>0|} +exit:end; + +@ @<Reduce to the case that |a...@>= +if a<0 then + begin negate(a); negate(b); + end; +if c<0 then + begin negate(c); negate(d); + end; +if d<=0 then + begin if b>=0 then + if ((a=0)or(b=0))and((c=0)or(d=0)) then return_sign(0) + else return_sign(1); + if d=0 then + if a=0 then return_sign(0)@+else return_sign(-1); + q:=a; a:=c; c:=q; q:=-b; b:=-d; d:=q; + end +else if b<=0 then + begin if b<0 then if a>0 then return_sign(-1); + if c=0 then return_sign(0) else return_sign(-1); + end + +@ We conclude this set of elementary routines with some simple rounding +and truncation operations that are coded in a machine-independent fashion. +The routines are slightly complicated because we want them to work +without overflow whenever $-2^{31}\L x<2^{31}$. + +@p function floor_scaled(@!x:scaled):scaled; + {$2^{16}\lfloor x/2^{16}\rfloor$} +var @!be_careful:integer; {temporary register} +begin if x>=0 then floor_scaled:=x-(x mod unity) +else begin be_careful:=x+1; + floor_scaled:=x+((-be_careful) mod unity)+1-unity; + end; +end; +@# +function round_unscaled(@!x:scaled):integer; + {$\lfloor x/2^{16}+.5\rfloor$} +var @!be_careful:integer; {temporary register} +begin if x>=half_unit then round_unscaled:=1+((x-half_unit) div unity) +else if x>=-half_unit then round_unscaled:=0 +else begin be_careful:=x+1; + round_unscaled:=-(1+((-be_careful-half_unit) div unity)); + end; +end; +@# +function round_fraction(@!x:fraction):scaled; + {$\lfloor x/2^{12}+.5\rfloor$} +var @!be_careful:integer; {temporary register} +begin if x>=2048 then round_fraction:=1+((x-2048) div 4096) +else if x>=-2048 then round_fraction:=0 +else begin be_careful:=x+1; + round_fraction:=-(1+((-be_careful-2048) div 4096)); + end; +end; + +@* \[8] Algebraic and transcendental functions. +\MP\ computes all of the necessary special functions from scratch, without +relying on |real| arithmetic or system subroutines for sines, cosines, etc. + +@ To get the square root of a |scaled| number |x|, we want to calculate +$s=\lfloor 2^8\!\sqrt x +{1\over2}\rfloor$. If $x>0$, this is the unique +integer such that $2^{16}x-s\L s^2<2^{16}x+s$. The following subroutine +determines $s$ by an iterative method that maintains the invariant +relations $x=2^{46-2k}x_0\bmod 2^{30}$, $0<y=\lfloor 2^{16-2k}x_0\rfloor +-s^2+s\L q=2s$, where $x_0$ is the initial value of $x$. The value of~$y$ +might, however, be zero at the start of the first iteration. + +@p function square_rt(@!x:scaled):scaled; +var @!k:small_number; {iteration control counter} +@!y,@!q:integer; {registers for intermediate calculations} +begin if x<=0 then @<Handle square root of zero or negative argument@> +else begin k:=23; q:=2; + while x<fraction_two do {i.e., |while x<@t$2^{29}$@>|\unskip} + begin decr(k); x:=x+x+x+x; + end; + if x<fraction_four then y:=0 + else begin x:=x-fraction_four; y:=1; + end; + repeat @<Decrease |k| by 1, maintaining the invariant + relations between |x|, |y|, and~|q|@>; + until k=0; + square_rt:=halfp(q); + end; +end; + +@ @<Handle square root of zero...@>= +begin if x<0 then + begin print_err("Square root of "); +@.Square root...replaced by 0@> + print_scaled(x); print(" has been replaced by 0"); + help2("Since I don't take square roots of negative numbers,")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); + error; + end; +square_rt:=0; +end + +@ @<Decrease |k| by 1, maintaining...@>= +double(x); double(y); +if x>=fraction_four then {note that |fraction_four=@t$2^{30}$@>|} + begin x:=x-fraction_four; incr(y); + end; +double(x); y:=y+y-q; double(q); +if x>=fraction_four then + begin x:=x-fraction_four; incr(y); + end; +if y>q then + begin y:=y-q; q:=q+2; + end +else if y<=0 then + begin q:=q-2; y:=y+q; + end; +decr(k) + +@ Pythagorean addition $\psqrt{a^2+b^2}$ is implemented by an elegant +iterative scheme due to Cleve Moler and Donald Morrison [{\sl IBM Journal +@^Moler, Cleve Barry@> +@^Morrison, Donald Ross@> +of Research and Development\/ \bf27} (1983), 577--581]. It modifies |a| and~|b| +in such a way that their Pythagorean sum remains invariant, while the +smaller argument decreases. + +@p function pyth_add(@!a,@!b:integer):integer; +label done; +var @!r:fraction; {register used to transform |a| and |b|} +@!big:boolean; {is the result dangerously near $2^{31}$?} +begin a:=abs(a); b:=abs(b); +if a<b then + begin r:=b; b:=a; a:=r; + end; {now |0<=b<=a|} +if b>0 then + begin if a<fraction_two then big:=false + else begin a:=a div 4; b:=b div 4; big:=true; + end; {we reduced the precision to avoid arithmetic overflow} + @<Replace |a| by an approximation to $\psqrt{a^2+b^2}$@>; + if big then + if a<fraction_two then a:=a+a+a+a + else begin arith_error:=true; a:=el_gordo; + end; + end; +pyth_add:=a; +end; + +@ The key idea here is to reflect the vector $(a,b)$ about the +line through $(a,b/2)$. + +@<Replace |a| by an approximation to $\psqrt{a^2+b^2}$@>= +loop@+ begin r:=make_fraction(b,a); + r:=take_fraction(r,r); {now $r\approx b^2/a^2$} + if r=0 then goto done; + r:=make_fraction(r,fraction_four+r); + a:=a+take_fraction(a+a,r); b:=take_fraction(b,r); + end; +done: + +@ Here is a similar algorithm for $\psqrt{a^2-b^2}$. +It converges slowly when $b$ is near $a$, but otherwise it works fine. + +@p function pyth_sub(@!a,@!b:integer):integer; +label done; +var @!r:fraction; {register used to transform |a| and |b|} +@!big:boolean; {is the input dangerously near $2^{31}$?} +begin a:=abs(a); b:=abs(b); +if a<=b then @<Handle erroneous |pyth_sub| and set |a:=0|@> +else begin if a<fraction_four then big:=false + else begin a:=halfp(a); b:=halfp(b); big:=true; + end; + @<Replace |a| by an approximation to $\psqrt{a^2-b^2}$@>; + if big then a:=a+a; + end; +pyth_sub:=a; +end; + +@ @<Replace |a| by an approximation to $\psqrt{a^2-b^2}$@>= +loop@+ begin r:=make_fraction(b,a); + r:=take_fraction(r,r); {now $r\approx b^2/a^2$} + if r=0 then goto done; + r:=make_fraction(r,fraction_four-r); + a:=a-take_fraction(a+a,r); b:=take_fraction(b,r); + end; +done: + +@ @<Handle erroneous |pyth_sub| and set |a:=0|@>= +begin if a<b then + begin print_err("Pythagorean subtraction "); print_scaled(a); + print("+-+"); print_scaled(b); print(" has been replaced by 0"); +@.Pythagorean...@> + help2("Since I don't take square roots of negative numbers,")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); + error; + end; +a:=0; +end + +@ The subroutines for logarithm and exponential involve two tables. +The first is simple: |two_to_the[k]| equals $2^k$. The second involves +a bit more calculation, which the author claims to have done correctly: +|spec_log[k]| is $2^{27}$ times $\ln\bigl(1/(1-2^{-k})\bigr)= +2^{-k}+{1\over2}2^{-2k}+{1\over3}2^{-3k}+\cdots\,$, rounded to the +nearest integer. + +@<Glob...@>= +@!two_to_the:array[0..30] of integer; {powers of two} +@!spec_log:array[1..28] of integer; {special logarithms} + +@ @<Local variables for initialization@>= +@!k:integer; {all-purpose loop index} + +@ @<Set init...@>= +two_to_the[0]:=1; +for k:=1 to 30 do two_to_the[k]:=2*two_to_the[k-1]; +spec_log[1]:=93032640; +spec_log[2]:=38612034; +spec_log[3]:=17922280; +spec_log[4]:=8662214; +spec_log[5]:=4261238; +spec_log[6]:=2113709; +spec_log[7]:=1052693; +spec_log[8]:=525315; +spec_log[9]:=262400; +spec_log[10]:=131136; +spec_log[11]:=65552; +spec_log[12]:=32772; +spec_log[13]:=16385; +for k:=14 to 27 do spec_log[k]:=two_to_the[27-k]; +spec_log[28]:=1; + +@ Here is the routine that calculates $2^8$ times the natural logarithm +of a |scaled| quantity; it is an integer approximation to $2^{24}\ln(x/2^{16})$, +when |x| is a given positive integer. + +The method is based on exercise 1.2.2--25 in {\sl The Art of Computer +Programming\/}: During the main iteration we have $1\L 2^{-30}x<1/(1-2^{1-k})$, +and the logarithm of $2^{30}x$ remains to be added to an accumulator +register called~$y$. Three auxiliary bits of accuracy are retained in~$y$ +during the calculation, and sixteen auxiliary bits to extend |y| are +kept in~|z| during the initial argument reduction. (We add +$100\cdot2^{16}=6553600$ to~|z| and subtract 100 from~|y| so that |z| will +not become negative; also, the actual amount subtracted from~|y| is~96, +not~100, because we want to add~4 for rounding before the final division by~8.) + +@p function m_log(@!x:scaled):scaled; +var @!y,@!z:integer; {auxiliary registers} +@!k:integer; {iteration counter} +begin if x<=0 then @<Handle non-positive logarithm@> +else begin y:=1302456956+4-100; {$14\times2^{27}\ln2\approx1302456956.421063$} + z:=27595+6553600; {and $2^{16}\times .421063\approx 27595$} + while x<fraction_four do + begin double(x); y:=y-93032639; z:=z-48782; + end; {$2^{27}\ln2\approx 93032639.74436163$ + and $2^{16}\times.74436163\approx 48782$} + y:=y+(z div unity); k:=2; + while x>fraction_four+4 do + @<Increase |k| until |x| can be multiplied by a + factor of $2^{-k}$, and adjust $y$ accordingly@>; + m_log:=y div 8; + end; +end; + +@ @<Increase |k| until |x| can...@>= +begin z:=((x-1) div two_to_the[k])+1; {$z=\lceil x/2^k\rceil$} +while x<fraction_four+z do + begin z:=halfp(z+1); k:=k+1; + end; +y:=y+spec_log[k]; x:=x-z; +end + +@ @<Handle non-positive logarithm@>= +begin print_err("Logarithm of "); +@.Logarithm...replaced by 0@> +print_scaled(x); print(" has been replaced by 0"); +help2("Since I don't take logs of non-positive numbers,")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); +error; m_log:=0; +end + +@ Conversely, the exponential routine calculates $\exp(x/2^8)$, +when |x| is |scaled|. The result is an integer approximation to +$2^{16}\exp(x/2^{24})$, when |x| is regarded as an integer. + +@p function m_exp(@!x:scaled):scaled; +var @!k:small_number; {loop control index} +@!y,@!z:integer; {auxiliary registers} +begin if x>174436200 then + {$2^{24}\ln((2^{31}-1)/2^{16})\approx 174436199.51$} + begin arith_error:=true; m_exp:=el_gordo; + end +else if x<-197694359 then m_exp:=0 + {$2^{24}\ln(2^{-1}/2^{16})\approx-197694359.45$} +else begin if x<=0 then + begin z:=-8*x; y:=@'4000000; {$y=2^{20}$} + end + else begin if x<=127919879 then z:=1023359037-8*x + {$2^{27}\ln((2^{31}-1)/2^{20})\approx 1023359037.125$} + else z:=8*(174436200-x); {|z| is always nonnegative} + y:=el_gordo; + end; + @<Multiply |y| by $\exp(-z/2^{27})$@>; + if x<=127919879 then m_exp:=(y+8) div 16@+else m_exp:=y; + end; +end; + +@ The idea here is that subtracting |spec_log[k]| from |z| corresponds +to multiplying |y| by $1-2^{-k}$. + +A subtle point (which had to be checked) was that if $x=127919879$, the +value of~|y| will decrease so that |y+8| doesn't overflow. In fact, +$z$ will be 5 in this case, and |y| will decrease by~64 when |k=25| +and by~16 when |k=27|. + +@<Multiply |y| by...@>= +k:=1; +while z>0 do + begin while z>=spec_log[k] do + begin z:=z-spec_log[k]; + y:=y-1-((y-two_to_the[k-1]) div two_to_the[k]); + end; + incr(k); + end + +@ The trigonometric subroutines use an auxiliary table such that +|spec_atan[k]| contains an approximation to the |angle| whose tangent +is~$1/2^k$. + +@<Glob...@>= +@!spec_atan:array[1..26] of angle; {$\arctan2^{-k}$ times $2^{20}\cdot180/\pi$} + +@ @<Set init...@>= +spec_atan[1]:=27855475; +spec_atan[2]:=14718068; +spec_atan[3]:=7471121; +spec_atan[4]:=3750058; +spec_atan[5]:=1876857; +spec_atan[6]:=938658; +spec_atan[7]:=469357; +spec_atan[8]:=234682; +spec_atan[9]:=117342; +spec_atan[10]:=58671; +spec_atan[11]:=29335; +spec_atan[12]:=14668; +spec_atan[13]:=7334; +spec_atan[14]:=3667; +spec_atan[15]:=1833; +spec_atan[16]:=917; +spec_atan[17]:=458; +spec_atan[18]:=229; +spec_atan[19]:=115; +spec_atan[20]:=57; +spec_atan[21]:=29; +spec_atan[22]:=14; +spec_atan[23]:=7; +spec_atan[24]:=4; +spec_atan[25]:=2; +spec_atan[26]:=1; + +@ Given integers |x| and |y|, not both zero, the |n_arg| function +returns the |angle| whose tangent points in the direction $(x,y)$. +This subroutine first determines the correct octant, then solves the +problem for |0<=y<=x|, then converts the result appropriately to +return an answer in the range |-one_eighty_deg<=@t$\theta$@><=one_eighty_deg|. +(The answer is |+one_eighty_deg| if |y=0| and |x<0|, but an answer of +|-one_eighty_deg| is possible if, for example, |y=-1| and $x=-2^{30}$.) + +The octants are represented in a ``Gray code,'' since that turns out +to be computationally simplest. + +@d negate_x=1 +@d negate_y=2 +@d switch_x_and_y=4 +@d first_octant=1 +@d second_octant=first_octant+switch_x_and_y +@d third_octant=first_octant+switch_x_and_y+negate_x +@d fourth_octant=first_octant+negate_x +@d fifth_octant=first_octant+negate_x+negate_y +@d sixth_octant=first_octant+switch_x_and_y+negate_x+negate_y +@d seventh_octant=first_octant+switch_x_and_y+negate_y +@d eighth_octant=first_octant+negate_y + +@p function n_arg(@!x,@!y:integer):angle; +var @!z:angle; {auxiliary register} +@!t:integer; {temporary storage} +@!k:small_number; {loop counter} +@!octant:first_octant..sixth_octant; {octant code} +begin if x>=0 then octant:=first_octant +else begin negate(x); octant:=first_octant+negate_x; + end; +if y<0 then + begin negate(y); octant:=octant+negate_y; + end; +if x<y then + begin t:=y; y:=x; x:=t; octant:=octant+switch_x_and_y; + end; +if x=0 then @<Handle undefined arg@> +else begin @<Set variable |z| to the arg of $(x,y)$@>; + @<Return an appropriate answer based on |z| and |octant|@>; + end; +end; + +@ @<Handle undefined arg@>= +begin print_err("angle(0,0) is taken as zero"); +@.angle(0,0)...zero@> +help2("The `angle' between two identical points is undefined.")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); +error; n_arg:=0; +end + +@ @<Return an appropriate answer...@>= +case octant of +first_octant:n_arg:=z; +second_octant:n_arg:=ninety_deg-z; +third_octant:n_arg:=ninety_deg+z; +fourth_octant:n_arg:=one_eighty_deg-z; +fifth_octant:n_arg:=z-one_eighty_deg; +sixth_octant:n_arg:=-z-ninety_deg; +seventh_octant:n_arg:=z-ninety_deg; +eighth_octant:n_arg:=-z; +end {there are no other cases} + +@ At this point we have |x>=y>=0|, and |x>0|. The numbers are scaled up +or down until $2^{28}\L x<2^{29}$, so that accurate fixed-point calculations +will be made. + +@<Set variable |z| to the arg...@>= +while x>=fraction_two do + begin x:=halfp(x); y:=halfp(y); + end; +z:=0; +if y>0 then + begin while x<fraction_one do + begin double(x); double(y); + end; + @<Increase |z| to the arg of $(x,y)$@>; + end + +@ During the calculations of this section, variables |x| and~|y| +represent actual coordinates $(x,2^{-k}y)$. We will maintain the +condition |x>=y|, so that the tangent will be at most $2^{-k}$. +If $x<2y$, the tangent is greater than $2^{-k-1}$. The transformation +$(a,b)\mapsto(a+b\tan\phi,b-a\tan\phi)$ replaces $(a,b)$ by +coordinates whose angle has decreased by~$\phi$; in the special case +$a=x$, $b=2^{-k}y$, and $\tan\phi=2^{-k-1}$, this operation reduces +to the particularly simple iteration shown here. [Cf.~John E. Meggitt, +@^Meggitt, John E.@> +{\sl IBM Journal of Research and Development\/ \bf6} (1962), 210--226.] + +The initial value of |x| will be multiplied by at most +$(1+{1\over2})(1+{1\over8})(1+{1\over32})\cdots\approx 1.7584$; hence +there is no chance of integer overflow. + +@<Increase |z|...@>= +k:=0; +repeat double(y); incr(k); +if y>x then + begin z:=z+spec_atan[k]; t:=x; x:=x+(y div two_to_the[k+k]); y:=y-t; + end; +until k=15; +repeat double(y); incr(k); +if y>x then + begin z:=z+spec_atan[k]; y:=y-x; + end; +until k=26 + +@ Conversely, the |n_sin_cos| routine takes an |angle| and produces the sine +and cosine of that angle. The results of this routine are +stored in global integer variables |n_sin| and |n_cos|. + +@<Glob...@>= +@!n_sin,@!n_cos:fraction; {results computed by |n_sin_cos|} + +@ Given an integer |z| that is $2^{20}$ times an angle $\theta$ in degrees, +the purpose of |n_sin_cos(z)| is to set +|x=@t$r\cos\theta$@>| and |y=@t$r\sin\theta$@>| (approximately), +for some rather large number~|r|. The maximum of |x| and |y| +will be between $2^{28}$ and $2^{30}$, so that there will be hardly +any loss of accuracy. Then |x| and~|y| are divided by~|r|. + +@p procedure n_sin_cos(@!z:angle); {computes a multiple of the sine and cosine} +var @!k:small_number; {loop control variable} +@!q:0..7; {specifies the quadrant} +@!r:fraction; {magnitude of |(x,y)|} +@!x,@!y,@!t:integer; {temporary registers} +begin while z<0 do z:=z+three_sixty_deg; +z:=z mod three_sixty_deg; {now |0<=z<three_sixty_deg|} +q:=z div forty_five_deg; z:=z mod forty_five_deg; +x:=fraction_one; y:=x; +if not odd(q) then z:=forty_five_deg-z; +@<Subtract angle |z| from |(x,y)|@>; +@<Convert |(x,y)| to the octant determined by~|q|@>; +r:=pyth_add(x,y); n_cos:=make_fraction(x,r); n_sin:=make_fraction(y,r); +end; + +@ In this case the octants are numbered sequentially. + +@<Convert |(x,...@>= +case q of +0:do_nothing; +1:begin t:=x; x:=y; y:=t; + end; +2:begin t:=x; x:=-y; y:=t; + end; +3:negate(x); +4:begin negate(x); negate(y); + end; +5:begin t:=x; x:=-y; y:=-t; + end; +6:begin t:=x; x:=y; y:=-t; + end; +7:negate(y); +end {there are no other cases} + +@ The main iteration of |n_sin_cos| is similar to that of |n_arg| but +applied in reverse. The values of |spec_atan[k]| decrease slowly enough +that this loop is guaranteed to terminate before the (nonexistent) value +|spec_atan[27]| would be required. + +@<Subtract angle |z|...@>= +k:=1; +while z>0 do + begin if z>=spec_atan[k] then + begin z:=z-spec_atan[k]; t:=x;@/ + x:=t+y div two_to_the[k]; + y:=y-t div two_to_the[k]; + end; + incr(k); + end; +if y<0 then y:=0 {this precaution may never be needed} + +@ And now let's complete our collection of numeric utility routines +by considering random number generation. +\MP\ generates pseudo-random numbers with the additive scheme recommended +in Section 3.6 of {\sl The Art of Computer Programming}; however, the +results are random fractions between 0 and |fraction_one-1|, inclusive. + +There's an auxiliary array |randoms| that contains 55 pseudo-random +fractions. Using the recurrence $x_n=(x_{n-55}-x_{n-31})\bmod 2^{28}$, +we generate batches of 55 new $x_n$'s at a time by calling |new_randoms|. +The global variable |j_random| tells which element has most recently +been consumed. +The global variable |sys_random_seed| was introduced in version 0.9, +for the sole reason of stressing the fact that the initial value of the +random seed is system-dependant. The pascal code below will initialize +this variable to |(internal[time] div unity)+internal[day]|, but this is +not good enough on modern fast machines that are capable of running +multiple MetaPost processes within the same second. +@^system dependencies@> + +@<Glob...@>= +@!randoms:array[0..54] of fraction; {the last 55 random values generated} +@!j_random:0..54; {the number of unused |randoms|} +@!sys_random_seed:scaled; {the default random seed} + +@ To consume a random fraction, the program below will say `|next_random|' +and then it will fetch |randoms[j_random]|. + +@d next_random==if j_random=0 then new_randoms + else decr(j_random) + +@p procedure new_randoms; +var @!k:0..54; {index into |randoms|} +@!x:fraction; {accumulator} +begin for k:=0 to 23 do + begin x:=randoms[k]-randoms[k+31]; + if x<0 then x:=x+fraction_one; + randoms[k]:=x; + end; +for k:=24 to 54 do + begin x:=randoms[k]-randoms[k-24]; + if x<0 then x:=x+fraction_one; + randoms[k]:=x; + end; +j_random:=54; +end; + +@ To initialize the |randoms| table, we call the following routine. + +@p procedure init_randoms(@!seed:scaled); +var @!j,@!jj,@!k:fraction; {more or less random integers} +@!i:0..54; {index into |randoms|} +begin j:=abs(seed); +while j>=fraction_one do j:=halfp(j); +k:=1; +for i:=0 to 54 do + begin jj:=k; k:=j-k; j:=jj; + if k<0 then k:=k+fraction_one; + randoms[(i*21)mod 55]:=j; + end; +new_randoms; new_randoms; new_randoms; {``warm up'' the array} +end; + +@ To produce a uniform random number in the range |0<=u<x| or |0>=u>x| +or |0=u=x|, given a |scaled| value~|x|, we proceed as shown here. + +Note that the call of |take_fraction| will produce the values 0 and~|x| +with about half the probability that it will produce any other particular +values between 0 and~|x|, because it rounds its answers. + +@p function unif_rand(@!x:scaled):scaled; +var @!y:scaled; {trial value} +begin next_random; y:=take_fraction(abs(x),randoms[j_random]); +if y=abs(x) then unif_rand:=0 +else if x>0 then unif_rand:=y +else unif_rand:=-y; +end; + +@ Finally, a normal deviate with mean zero and unit standard deviation +can readily be obtained with the ratio method (Algorithm 3.4.1R in +{\sl The Art of Computer Programming\/}). + +@p function norm_rand:scaled; +var @!x,@!u,@!l:integer; {what the book would call $2^{16}X$, $2^{28}U$, + and $-2^{24}\ln U$} +begin repeat + repeat next_random; + x:=take_fraction(112429,randoms[j_random]-fraction_half); + {$2^{16}\sqrt{8/e}\approx 112428.82793$} + next_random; u:=randoms[j_random]; + until abs(x)<u; +x:=make_fraction(x,u); +l:=139548960-m_log(u); {$2^{24}\cdot12\ln2\approx139548959.6165$} +until ab_vs_cd(1024,l,x,x)>=0; +norm_rand:=x; +end; + +@* \[9] Packed data. +In order to make efficient use of storage space, \MP\ bases its major data +structures on a |memory_word|, which contains either a (signed) integer, +possibly scaled, or a small number of fields that are one half or one +quarter of the size used for storing integers. + +If |x| is a variable of type |memory_word|, it contains up to four +fields that can be referred to as follows: +$$\vbox{\halign{\hfil#&#\hfil&#\hfil\cr +|x|&.|int|&(an |integer|)\cr +|x|&.|sc|\qquad&(a |scaled| integer)\cr +|x.hh.lh|, |x.hh|&.|rh|&(two halfword fields)\cr +|x.hh.b0|, |x.hh.b1|, |x.hh|&.|rh|&(two quarterword fields, one halfword + field)\cr +|x.qqqq.b0|, |x.qqqq.b1|, |x.qqqq|&.|b2|, |x.qqqq.b3|\hskip-100pt + &\qquad\qquad\qquad(four quarterword fields)\cr}}$$ +This is somewhat cumbersome to write, and not very readable either, but +macros will be used to make the notation shorter and more transparent. +The \PASCAL\ code below gives a formal definition of |memory_word| and +its subsidiary types, using packed variant records. \MP\ makes no +assumptions about the relative positions of the fields within a word. + +Since we are assuming 32-bit integers, a halfword must contain at least +16 bits, and a quarterword must contain at least 8 bits. +@^system dependencies@> +But it doesn't hurt to have more bits; for example, with enough 36-bit +words you might be able to have |mem_max| as large as 262142. + +N.B.: Valuable memory space will be dreadfully wasted unless \MP\ is compiled +by a \PASCAL\ that packs all of the |memory_word| variants into +the space of a single integer. Some \PASCAL\ compilers will pack an +integer whose subrange is `|0..255|' into an eight-bit field, but others +insist on allocating space for an additional sign bit; on such systems you +can get 256 values into a quarterword only if the subrange is `|-128..127|'. + +The present implementation tries to accommodate as many variations as possible, +so it makes few assumptions. If integers having the subrange +`|min_quarterword..max_quarterword|' can be packed into a quarterword, +and if integers having the subrange `|min_halfword..max_halfword|' +can be packed into a halfword, everything should work satisfactorily. + +It is usually most efficient to have |min_quarterword=min_halfword=0|, +so one should try to achieve this unless it causes a severe problem. +The values defined here are recommended for most 32-bit computers. + +@d min_quarterword=0 {smallest allowable value in a |quarterword|} +@d max_quarterword=255 {largest allowable value in a |quarterword|} +@d min_halfword==0 {smallest allowable value in a |halfword|} +@d max_halfword==65535 {largest allowable value in a |halfword|} + +@ Here are the inequalities that the quarterword and halfword values +must satisfy (or rather, the inequalities that they mustn't satisfy): + +@<Check the ``constant''...@>= +init if mem_max<>mem_top then bad:=8;@+tini@;@/ +if mem_max<mem_top then bad:=8; +if (min_quarterword>0)or(max_quarterword<127) then bad:=9; +if (min_halfword>0)or(max_halfword<32767) then bad:=10; +if (min_quarterword<min_halfword)or@| + (max_quarterword>max_halfword) then bad:=11; +if (mem_min<min_halfword)or(mem_max>=max_halfword) then bad:=12; +if max_strings>max_halfword then bad:=13; +if buf_size>max_halfword then bad:=14; +if font_max>max_halfword then bad:=15; +if (max_quarterword-min_quarterword<255)or@| + (max_halfword-min_halfword<65535) then bad:=16; + +@ The operation of subtracting |min_halfword| occurs rather frequently in +\MP, so it is convenient to abbreviate this operation by using the macro +|ho| defined here. \MP\ will run faster with respect to compilers that +don't optimize the expression `|x-0|', if this macro is simplified in the +obvious way when |min_halfword=0|. Similarly, |qi| and |qo| are used for +input to and output from quarterwords. +@^system dependencies@> + +@d ho(#)==#-min_halfword + {to take a sixteen-bit item from a halfword} +@d qo(#)==#-min_quarterword {to read eight bits from a quarterword} +@d qi(#)==#+min_quarterword {to store eight bits in a quarterword} + +@ The reader should study the following definitions closely: +@^system dependencies@> + +@d sc==int {|scaled| data is equivalent to |integer|} + +@<Types...@>= +@!quarterword = min_quarterword..max_quarterword; {1/4 of a word} +@!halfword=min_halfword..max_halfword; {1/2 of a word} +@!two_choices = 1..2; {used when there are two variants in a record} +@!three_choices = 1..3; {used when there are three variants in a record} +@!two_halves = packed record@;@/ + @!rh:halfword; + case two_choices of + 1: (@!lh:halfword); + 2: (@!b0:quarterword; @!b1:quarterword); + end; +@!four_quarters = packed record@;@/ + @!b0:quarterword; + @!b1:quarterword; + @!b2:quarterword; + @!b3:quarterword; + end; +@!memory_word = record@;@/ + case three_choices of + 1: (@!int:integer); + 2: (@!hh:two_halves); + 3: (@!qqqq:four_quarters); + end; +@!word_file = file of memory_word; + +@ When debugging, we may want to print a |memory_word| without knowing +what type it is; so we print it in all modes. +@^dirty \PASCAL@>@^debugging@> + +@p @!debug procedure print_word(@!w:memory_word); + {prints |w| in all ways} +begin print_int(w.int); print_char(" ");@/ +print_scaled(w.sc); print_char(" "); print_scaled(w.sc div @'10000); print_ln;@/ +print_int(w.hh.lh); print_char("="); print_int(w.hh.b0); print_char(":"); +print_int(w.hh.b1); print_char(";"); print_int(w.hh.rh); print_char(" ");@/ +print_int(w.qqqq.b0); print_char(":"); print_int(w.qqqq.b1); print_char(":"); +print_int(w.qqqq.b2); print_char(":"); print_int(w.qqqq.b3); +end; +gubed + +@* \[10] Dynamic memory allocation. +The \MP\ system does nearly all of its own memory allocation, so that it +can readily be transported into environments that do not have automatic +facilities for strings, garbage collection, etc., and so that it can be in +control of what error messages the user receives. The dynamic storage +requirements of \MP\ are handled by providing a large array |mem| in +which consecutive blocks of words are used as nodes by the \MP\ routines. + +Pointer variables are indices into this array, or into another array +called |eqtb| that will be explained later. A pointer variable might +also be a special flag that lies outside the bounds of |mem|, so we +allow pointers to assume any |halfword| value. The minimum memory +index represents a null pointer. + +@d pointer==halfword {a flag or a location in |mem| or |eqtb|} +@d null==mem_min {the null pointer} + +@ The |mem| array is divided into two regions that are allocated separately, +but the dividing line between these two regions is not fixed; they grow +together until finding their ``natural'' size in a particular job. +Locations less than or equal to |lo_mem_max| are used for storing +variable-length records consisting of two or more words each. This region +is maintained using an algorithm similar to the one described in exercise +2.5--19 of {\sl The Art of Computer Programming}. However, no size field +appears in the allocated nodes; the program is responsible for knowing the +relevant size when a node is freed. Locations greater than or equal to +|hi_mem_min| are used for storing one-word records; a conventional +\.{AVAIL} stack is used for allocation in this region. + +Locations of |mem| between |mem_min| and |mem_top| may be dumped as part +of preloaded format files, by the \.{INIMP} preprocessor. +@.INIMP@> +Production versions of \MP\ may extend the memory at the top end in order to +provide more space; these locations, between |mem_top| and |mem_max|, +are always used for single-word nodes. + +The key pointers that govern |mem| allocation have a prescribed order: +$$\hbox{|null=mem_min<lo_mem_max<hi_mem_min<mem_top<=mem_end<=mem_max|.}$$ + +@<Glob...@>= +@!mem : array[mem_min..mem_max] of memory_word; {the big dynamic storage area} +@!lo_mem_max : pointer; {the largest location of variable-size memory in use} +@!hi_mem_min : pointer; {the smallest location of one-word memory in use} + +@ Users who wish to study the memory requirements of particular applications can +can use optional special features that keep track of current and +maximum memory usage. When code between the delimiters |@!stat| $\ldots$ +|tats| is not ``commented out,'' \MP\ will run a bit slower but it will +report these statistics when |tracing_stats| is positive. + +@<Glob...@>= +@!var_used, @!dyn_used : integer; {how much memory is in use} + +@ Let's consider the one-word memory region first, since it's the +simplest. The pointer variable |mem_end| holds the highest-numbered location +of |mem| that has ever been used. The free locations of |mem| that +occur between |hi_mem_min| and |mem_end|, inclusive, are of type +|two_halves|, and we write |info(p)| and |link(p)| for the |lh| +and |rh| fields of |mem[p]| when it is of this type. The single-word +free locations form a linked list +$$|avail|,\;\hbox{|link(avail)|},\;\hbox{|link(link(avail))|},\;\ldots$$ +terminated by |null|. + +@d link(#) == mem[#].hh.rh {the |link| field of a memory word} +@d info(#) == mem[#].hh.lh {the |info| field of a memory word} + +@<Glob...@>= +@!avail : pointer; {head of the list of available one-word nodes} +@!mem_end : pointer; {the last one-word node used in |mem|} + +@ If one-word memory is exhausted, it might mean that the user has forgotten +a token like `\&{enddef}' or `\&{endfor}'. We will define some procedures +later that try to help pinpoint the trouble. + +@p @t\4@>@<Declare the procedure called |show_token_list|@>@; +@t\4@>@<Declare the procedure called |runaway|@> + +@ The function |get_avail| returns a pointer to a new one-word node whose +|link| field is null. However, \MP\ will halt if there is no more room left. +@^inner loop@> + +@p function get_avail : pointer; {single-word node allocation} +var @!p:pointer; {the new node being got} +begin p:=avail; {get top location in the |avail| stack} +if p<>null then avail:=link(avail) {and pop it off} +else if mem_end<mem_max then {or go into virgin territory} + begin incr(mem_end); p:=mem_end; + end +else begin decr(hi_mem_min); p:=hi_mem_min; + if hi_mem_min<=lo_mem_max then + begin runaway; {if memory is exhausted, display possible runaway text} + overflow("main memory size",mem_max+1-mem_min); + {quit; all one-word nodes are busy} +@:MetaPost capacity exceeded main memory size}{\quad main memory size@> + end; + end; +link(p):=null; {provide an oft-desired initialization of the new node} +@!stat incr(dyn_used);@+tats@;{maintain statistics} +get_avail:=p; +end; + +@ Conversely, a one-word node is recycled by calling |free_avail|. + +@d free_avail(#)== {single-word node liberation} + begin link(#):=avail; avail:=#; + @!stat decr(dyn_used);@+tats@/ + end + +@ There's also a |fast_get_avail| routine, which saves the procedure-call +overhead at the expense of extra programming. This macro is used in +the places that would otherwise account for the most calls of |get_avail|. +@^inner loop@> + +@d fast_get_avail(#)==@t@>@;@/ + begin #:=avail; {avoid |get_avail| if possible, to save time} + if #=null then #:=get_avail + else begin avail:=link(#); link(#):=null; + @!stat incr(dyn_used);@+tats@/ + end; + end + +@ The available-space list that keeps track of the variable-size portion +of |mem| is a nonempty, doubly-linked circular list of empty nodes, +pointed to by the roving pointer |rover|. + +Each empty node has size 2 or more; the first word contains the special +value |max_halfword| in its |link| field and the size in its |info| field; +the second word contains the two pointers for double linking. + +Each nonempty node also has size 2 or more. Its first word is of type +|two_halves|\kern-1pt, and its |link| field is never equal to |max_halfword|. +Otherwise there is complete flexibility with respect to the contents +of its other fields and its other words. + +(We require |mem_max<max_halfword| because terrible things can happen +when |max_halfword| appears in the |link| field of a nonempty node.) + +@d empty_flag == max_halfword {the |link| of an empty variable-size node} +@d is_empty(#) == (link(#)=empty_flag) {tests for empty node} +@d node_size == info {the size field in empty variable-size nodes} +@d llink(#) == info(#+1) {left link in doubly-linked list of empty nodes} +@d rlink(#) == link(#+1) {right link in doubly-linked list of empty nodes} + +@<Glob...@>= +@!rover : pointer; {points to some node in the list of empties} + +@ A call to |get_node| with argument |s| returns a pointer to a new node +of size~|s|, which must be 2~or more. The |link| field of the first word +of this new node is set to null. An overflow stop occurs if no suitable +space exists. + +If |get_node| is called with $s=2^{30}$, it simply merges adjacent free +areas and returns the value |max_halfword|. + +@p function get_node(@!s:integer):pointer; {variable-size node allocation} +label found,exit,restart; +var @!p:pointer; {the node currently under inspection} +@!q:pointer; {the node physically after node |p|} +@!r:integer; {the newly allocated node, or a candidate for this honor} +@!t,@!tt:integer; {temporary registers} +@^inner loop@> +begin restart: p:=rover; {start at some free node in the ring} +repeat @<Try to allocate within node |p| and its physical successors, + and |goto found| if allocation was possible@>; +p:=rlink(p); {move to the next node in the ring} +until p=rover; {repeat until the whole list has been traversed} +if s=@'10000000000 then + begin get_node:=max_halfword; return; + end; +if lo_mem_max+2<hi_mem_min then if lo_mem_max+2<=mem_min+max_halfword then + @<Grow more variable-size memory and |goto restart|@>; +overflow("main memory size",mem_max+1-mem_min); + {sorry, nothing satisfactory is left} +@:MetaPost capacity exceeded main memory size}{\quad main memory size@> +found: link(r):=null; {this node is now nonempty} +@!stat var_used:=var_used+s; {maintain usage statistics} +tats@;@/ +get_node:=r; +exit:end; + +@ The lower part of |mem| grows by 1000 words at a time, unless +we are very close to going under. When it grows, we simply link +a new node into the available-space list. This method of controlled +growth helps to keep the |mem| usage consecutive when \MP\ is +implemented on ``virtual memory'' systems. +@^virtual memory@> + +@<Grow more variable-size memory and |goto restart|@>= +begin if hi_mem_min-lo_mem_max>=1998 then t:=lo_mem_max+1000 +else t:=lo_mem_max+1+(hi_mem_min-lo_mem_max) div 2; + {|lo_mem_max+2<=t<hi_mem_min|} +if t>mem_min+max_halfword then t:=mem_min+max_halfword; +p:=llink(rover); q:=lo_mem_max; rlink(p):=q; llink(rover):=q;@/ +rlink(q):=rover; llink(q):=p; link(q):=empty_flag; node_size(q):=t-lo_mem_max;@/ +lo_mem_max:=t; link(lo_mem_max):=null; info(lo_mem_max):=null; +rover:=q; goto restart; +end + +@ @<Try to allocate...@>= +q:=p+node_size(p); {find the physical successor} +while is_empty(q) do {merge node |p| with node |q|} + begin t:=rlink(q); tt:=llink(q); +@^inner loop@> + if q=rover then rover:=t; + llink(t):=tt; rlink(tt):=t;@/ + q:=q+node_size(q); + end; +r:=q-s; +if r>p+1 then @<Allocate from the top of node |p| and |goto found|@>; +if r=p then if rlink(p)<>p then + @<Allocate entire node |p| and |goto found|@>; +node_size(p):=q-p {reset the size in case it grew} + +@ @<Allocate from the top...@>= +begin node_size(p):=r-p; {store the remaining size} +rover:=p; {start searching here next time} +goto found; +end + +@ Here we delete node |p| from the ring, and let |rover| rove around. + +@<Allocate entire...@>= +begin rover:=rlink(p); t:=llink(p); +llink(rover):=t; rlink(t):=rover; +goto found; +end + +@ Conversely, when some variable-size node |p| of size |s| is no longer needed, +the operation |free_node(p,s)| will make its words available, by inserting +|p| as a new empty node just before where |rover| now points. + +@p procedure free_node(@!p:pointer; @!s:halfword); {variable-size node + liberation} +var @!q:pointer; {|llink(rover)|} +begin node_size(p):=s; link(p):=empty_flag; +@^inner loop@> +q:=llink(rover); llink(p):=q; rlink(p):=rover; {set both links} +llink(rover):=p; rlink(q):=p; {insert |p| into the ring} +@!stat var_used:=var_used-s;@+tats@;{maintain statistics} +end; + +@ Just before \.{INIMP} writes out the memory, it sorts the doubly linked +available space list. The list is probably very short at such times, so a +simple insertion sort is used. The smallest available location will be +pointed to by |rover|, the next-smallest by |rlink(rover)|, etc. + +@p @!init procedure sort_avail; {sorts the available variable-size nodes + by location} +var @!p,@!q,@!r: pointer; {indices into |mem|} +@!old_rover:pointer; {initial |rover| setting} +begin p:=get_node(@'10000000000); {merge adjacent free areas} +p:=rlink(rover); rlink(rover):=max_halfword; old_rover:=rover; +while p<>old_rover do @<Sort |p| into the list starting at |rover| + and advance |p| to |rlink(p)|@>; +p:=rover; +while rlink(p)<>max_halfword do + begin llink(rlink(p)):=p; p:=rlink(p); + end; +rlink(p):=rover; llink(rover):=p; +end; +tini + +@ The following |while| loop is guaranteed to +terminate, since the list that starts at +|rover| ends with |max_halfword| during the sorting procedure. + +@<Sort |p|...@>= +if p<rover then + begin q:=p; p:=rlink(q); rlink(q):=rover; rover:=q; + end +else begin q:=rover; + while rlink(q)<p do q:=rlink(q); + r:=rlink(p); rlink(p):=rlink(q); rlink(q):=p; p:=r; + end + +@* \[11] Memory layout. +Some areas of |mem| are dedicated to fixed usage, since static allocation is +more efficient than dynamic allocation when we can get away with it. For +example, locations |mem_min| to |mem_min+1| are always used to store a +two-word dummy token whose second word is zero. +The following macro definitions accomplish the static allocation by giving +symbolic names to the fixed positions. Static variable-size nodes appear +in locations |mem_min| through |lo_mem_stat_max|, and static single-word nodes +appear in locations |hi_mem_stat_min| through |mem_top|, inclusive. + +@d null_dash==mem_min+2 {the first two words are reserved for a null value} +@d dep_head==null_dash+3 {we will define |dash_node_size=3|} +@d zero_val==dep_head+2 {two words for a permanently zero value} +@d temp_val==zero_val+2 {two words for a temporary value node} +@d end_attr==temp_val {we use |end_attr+2| only} +@d inf_val==end_attr+2 {and |inf_val+1| only} +@d test_pen==inf_val+2 + {nine words for a pen used when testing the turning number} +@d bad_vardef==test_pen+9 {two words for \&{vardef} error recovery} +@d lo_mem_stat_max==bad_vardef+1 {largest statically + allocated word in the variable-size |mem|} +@# +@d sentinel==mem_top {end of sorted lists} +@d temp_head==mem_top-1 {head of a temporary list of some kind} +@d hold_head==mem_top-2 {head of a temporary list of another kind} +@d spec_head==mem_top-3 {head of a list of unprocessed \&{special} items} +@d hi_mem_stat_min==mem_top-3 {smallest statically allocated word in + the one-word |mem|} + +@ The following code gets the dynamic part of |mem| off to a good start, +when \MP\ is initializing itself the slow way. + +@<Initialize table entries (done by \.{INIMP} only)@>= +@^data structure assumptions@> +rover:=lo_mem_stat_max+1; {initialize the dynamic memory} +link(rover):=empty_flag; +node_size(rover):=1000; {which is a 1000-word available node} +llink(rover):=rover; rlink(rover):=rover;@/ +lo_mem_max:=rover+1000; link(lo_mem_max):=null; info(lo_mem_max):=null;@/ +for k:=hi_mem_stat_min to mem_top do + mem[k]:=mem[lo_mem_max]; {clear list heads} +avail:=null; mem_end:=mem_top; +hi_mem_min:=hi_mem_stat_min; {initialize the one-word memory} +var_used:=lo_mem_stat_max+1-mem_min; dyn_used:=mem_top+1-(hi_mem_stat_min); + {initialize statistics} +@<Initialize a pen at |test_pen| so that it fits in nine words@>; + +@ The procedure |flush_list(p)| frees an entire linked list of one-word +nodes that starts at a given position, until coming to |sentinel| or a +pointer that is not in the one-word region. Another procedure, +|flush_node_list|, frees an entire linked list of one-word and two-word +nodes, until coming to a |null| pointer. +@^inner loop@> + +@p procedure flush_list(@!p:pointer); {makes list of single-word nodes + available} +label done; +var @!q,@!r:pointer; {list traversers} +begin if p>=hi_mem_min then if p<>sentinel then + begin r:=p; + repeat q:=r; r:=link(r); @!stat decr(dyn_used);@+tats@/ + if r<hi_mem_min then goto done; + until r=sentinel; + done: {now |q| is the last node on the list} + link(q):=avail; avail:=p; + end; +end; +@# +procedure flush_node_list(@!p:pointer); +var @!q:pointer; {the node being recycled} +begin while p<>null do + begin q:=p; p:=link(p); + if q<hi_mem_min then free_node(q,2)@+else free_avail(q); + end; +end; + +@ If \MP\ is extended improperly, the |mem| array might get screwed up. +For example, some pointers might be wrong, or some ``dead'' nodes might not +have been freed when the last reference to them disappeared. Procedures +|check_mem| and |search_mem| are available to help diagnose such +problems. These procedures make use of two arrays called |free| and +|was_free| that are present only if \MP's debugging routines have +been included. (You may want to decrease the size of |mem| while you +@^debugging@> +are debugging.) + +@<Glob...@>= +@!debug @!free: packed array [mem_min..mem_max] of boolean; {free cells} +@t\hskip1em@>@!was_free: packed array [mem_min..mem_max] of boolean; + {previously free cells} +@t\hskip1em@>@!was_mem_end,@!was_lo_max,@!was_hi_min: pointer; + {previous |mem_end|, |lo_mem_max|,and |hi_mem_min|} +@t\hskip1em@>@!panicking:boolean; {do we want to check memory constantly?} +gubed + +@ @<Set initial...@>= +@!debug was_mem_end:=mem_min; {indicate that everything was previously free} +was_lo_max:=mem_min; was_hi_min:=mem_max; +panicking:=false; +gubed + +@ Procedure |check_mem| makes sure that the available space lists of +|mem| are well formed, and it optionally prints out all locations +that are reserved now but were free the last time this procedure was called. + +@p @!debug procedure check_mem(@!print_locs : boolean); +label done1,done2,done3; {loop exits} +var @!p,@!q,@!r:pointer; {current locations of interest in |mem|} +@!clobbered:boolean; {is something amiss?} +begin for p:=mem_min to lo_mem_max do free[p]:=false; {you can probably + do this faster} +for p:=hi_mem_min to mem_end do free[p]:=false; {ditto} +@<Check single-word |avail| list@>; +@<Check variable-size |avail| list@>; +@<Check flags of unavailable nodes@>; +@<Check the list of linear dependencies@>; +if print_locs then @<Print newly busy locations@>; +for p:=mem_min to lo_mem_max do was_free[p]:=free[p]; +for p:=hi_mem_min to mem_end do was_free[p]:=free[p]; + {|was_free:=free| might be faster} +was_mem_end:=mem_end; was_lo_max:=lo_mem_max; was_hi_min:=hi_mem_min; +end; +gubed + +@ @<Check single-word...@>= +p:=avail; q:=null; clobbered:=false; +while p<>null do + begin if (p>mem_end)or(p<hi_mem_min) then clobbered:=true + else if free[p] then clobbered:=true; + if clobbered then + begin print_nl("AVAIL list clobbered at "); +@.AVAIL list clobbered...@> + print_int(q); goto done1; + end; + free[p]:=true; q:=p; p:=link(q); + end; +done1: + +@ @<Check variable-size...@>= +p:=rover; q:=null; clobbered:=false; +repeat if (p>=lo_mem_max)or(p<mem_min) then clobbered:=true + else if (rlink(p)>=lo_mem_max)or(rlink(p)<mem_min) then clobbered:=true + else if not(is_empty(p))or(node_size(p)<2)or@| + (p+node_size(p)>lo_mem_max)or@| (llink(rlink(p))<>p) then clobbered:=true; + if clobbered then + begin print_nl("Double-AVAIL list clobbered at "); +@.Double-AVAIL list clobbered...@> + print_int(q); goto done2; + end; +for q:=p to p+node_size(p)-1 do {mark all locations free} + begin if free[q] then + begin print_nl("Doubly free location at "); +@.Doubly free location...@> + print_int(q); goto done2; + end; + free[q]:=true; + end; +q:=p; p:=rlink(p); +until p=rover; +done2: + +@ @<Check flags...@>= +p:=mem_min; +while p<=lo_mem_max do {node |p| should not be empty} + begin if is_empty(p) then + begin print_nl("Bad flag at "); print_int(p); +@.Bad flag...@> + end; + while (p<=lo_mem_max) and not free[p] do incr(p); + while (p<=lo_mem_max) and free[p] do incr(p); + end + +@ @<Print newly busy...@>= +begin @<Do intialization required before printing new busy locations@>; +print_nl("New busy locs:"); +@.New busy locs@> +for p:=mem_min to lo_mem_max do + if not free[p] and ((p>was_lo_max) or was_free[p]) then + @<Indicate that |p| is a new busy location@>; +for p:=hi_mem_min to mem_end do + if not free[p] and + ((p<was_hi_min) or (p>was_mem_end) or was_free[p]) then + @<Indicate that |p| is a new busy location@>; +@<Finish printing new busy locations@>; +end + +@ There might be many new busy locations so we are careful to print contiguous +blocks compactly. During this operation |q| is the last new busy location and +|r| is the start of the block containing |q|. + +@<Indicate that |p| is a new busy location@>= +begin if p>q+1 then + begin if q>r then + begin print(".."); print_int(q); + end; + print_char(" "); print_int(p); + r:=p; + end; +q:=p; +end + +@ @<Do intialization required before printing new busy locations@>= +q:=mem_max; r:=mem_max + +@ @<Finish printing new busy locations@>= +if q>r then + begin print(".."); print_int(q); + end + +@ The |search_mem| procedure attempts to answer the question ``Who points +to node~|p|?'' In doing so, it fetches |link| and |info| fields of |mem| +that might not be of type |two_halves|. Strictly speaking, this is +@^dirty \PASCAL@> +undefined in \PASCAL, and it can lead to ``false drops'' (words that seem to +point to |p| purely by coincidence). But for debugging purposes, we want +to rule out the places that do {\sl not\/} point to |p|, so a few false +drops are tolerable. + +@p @!debug procedure search_mem(@!p:pointer); {look for pointers to |p|} +var @!q:integer; {current position being searched} +begin for q:=mem_min to lo_mem_max do + begin if link(q)=p then + begin print_nl("LINK("); print_int(q); print_char(")"); + end; + if info(q)=p then + begin print_nl("INFO("); print_int(q); print_char(")"); + end; + end; +for q:=hi_mem_min to mem_end do + begin if link(q)=p then + begin print_nl("LINK("); print_int(q); print_char(")"); + end; + if info(q)=p then + begin print_nl("INFO("); print_int(q); print_char(")"); + end; + end; +@<Search |eqtb| for equivalents equal to |p|@>; +end; +gubed + +@* \[12] The command codes. +Before we can go much further, we need to define symbolic names for the internal +code numbers that represent the various commands obeyed by \MP. These codes +are somewhat arbitrary, but not completely so. For example, +some codes have been made adjacent so that |case| statements in the +program need not consider cases that are widely spaced, or so that |case| +statements can be replaced by |if| statements. A command can begin an +expression if and only if its code lies between |min_primary_command| and +|max_primary_command|, inclusive. The first token of a statement that doesn't +begin with an expression has a command code between |min_command| and +|max_statement_command|, inclusive. Anything less than |min_command| is +eliminated during macro expansions, and anything no more than |max_pre_command| +is eliminated when expanding \TeX\ material. Ranges such as +|min_secondary_command..max_secondary_command| are used when parsing +expressions, but the relative ordering within such a range is generally not +critical. + +The ordering of the highest-numbered commands +(|comma<semicolon<end_group<stop|) is crucial for the parsing and +error-recovery methods of this program as is the ordering |if_test<fi_or_else| +for the smallest two commands. The ordering is also important in the ranges +|numeric_token..plus_or_minus| and |left_brace..ampersand|. + +At any rate, here is the list, for future reference. + +@d start_tex=1 {begin \TeX\ material (\&{btex}, \&{verbatimtex})} +@d etex_marker=2 {end \TeX\ material (\&{etex})} +@d mpx_break=3 {stop reading an \.{MPX} file (\&{mpxbreak})} +@d max_pre_command=mpx_break +@d if_test=4 {conditional text (\&{if})} +@d fi_or_else=5 {delimiters for conditionals (\&{elseif}, \&{else}, \&{fi}} +@d input=6 {input a source file (\&{input}, \&{endinput})} +@d iteration=7 {iterate (\&{for}, \&{forsuffixes}, \&{forever}, \&{endfor})} +@d repeat_loop=8 {special command substituted for \&{endfor}} +@d exit_test=9 {premature exit from a loop (\&{exitif})} +@d relax=10 {do nothing (\.{\char`\\})} +@d scan_tokens=11 {put a string into the input buffer} +@d expand_after=12 {look ahead one token} +@d defined_macro=13 {a macro defined by the user} +@d min_command=defined_macro+1 +@d save_command=14 {save a list of tokens (\&{save})} +@d interim_command=15 {save an internal quantity (\&{interim})} +@d let_command=16 {redefine a symbolic token (\&{let})} +@d new_internal=17 {define a new internal quantity (\&{newinternal})} +@d macro_def=18 {define a macro (\&{def}, \&{vardef}, etc.)} +@d ship_out_command=19 {output a character (\&{shipout})} +@d add_to_command=20 {add to edges (\&{addto})} +@d bounds_command=21 {add bounding path to edges (\&{setbounds}, \&{clip})} +@d tfm_command=22 {command for font metric info (\&{ligtable}, etc.)} +@d protection_command=23 {set protection flag (\&{outer}, \&{inner})} +@d show_command=24 {diagnostic output (\&{show}, \&{showvariable}, etc.)} +@d mode_command=25 {set interaction level (\&{batchmode}, etc.)} +@d random_seed=26 {initialize random number generator (\&{randomseed})} +@d message_command=27 {communicate to user (\&{message}, \&{errmessage})} +@d every_job_command=28 {designate a starting token (\&{everyjob})} +@d delimiters=29 {define a pair of delimiters (\&{delimiters})} +@d special_command=30 {output special info (\&{special})} +@d write_command=31 {write text to a file (\&{write})} +@d type_name=32 {declare a type (\&{numeric}, \&{pair}, etc.} +@d max_statement_command=type_name +@d min_primary_command=type_name +@d left_delimiter=33 {the left delimiter of a matching pair} +@d begin_group=34 {beginning of a group (\&{begingroup})} +@d nullary=35 {an operator without arguments (e.g., \&{normaldeviate})} +@d unary=36 {an operator with one argument (e.g., \&{sqrt})} +@d str_op=37 {convert a suffix to a string (\&{str})} +@d cycle=38 {close a cyclic path (\&{cycle})} +@d primary_binary=39 {binary operation taking `\&{of}' (e.g., \&{point})} +@d capsule_token=40 {a value that has been put into a token list} +@d string_token=41 {a string constant (e.g., |"hello"|)} +@d internal_quantity=42 {internal numeric parameter (e.g., \&{pausing})} +@d min_suffix_token=internal_quantity +@d tag_token=43 {a symbolic token without a primitive meaning} +@d numeric_token=44 {a numeric constant (e.g., \.{3.14159})} +@d max_suffix_token=numeric_token +@d plus_or_minus=45 {either `\.+' or `\.-'} +@d max_primary_command=plus_or_minus {should also be |numeric_token+1|} +@d min_tertiary_command=plus_or_minus +@d tertiary_secondary_macro=46 {a macro defined by \&{secondarydef}} +@d tertiary_binary=47 {an operator at the tertiary level (e.g., `\.{++}')} +@d max_tertiary_command=tertiary_binary +@d left_brace=48 {the operator `\.{\char`\{}'} +@d min_expression_command=left_brace +@d path_join=49 {the operator `\.{..}'} +@d ampersand=50 {the operator `\.\&'} +@d expression_tertiary_macro=51 {a macro defined by \&{tertiarydef}} +@d expression_binary=52 {an operator at the expression level (e.g., `\.<')} +@d equals=53 {the operator `\.='} +@d max_expression_command=equals +@d and_command=54 {the operator `\&{and}'} +@d min_secondary_command=and_command +@d secondary_primary_macro=55 {a macro defined by \&{primarydef}} +@d slash=56 {the operator `\./'} +@d secondary_binary=57 {an operator at the binary level (e.g., \&{shifted})} +@d max_secondary_command=secondary_binary +@d param_type=58 {type of parameter (\&{primary}, \&{expr}, \&{suffix}, etc.)} +@d controls=59 {specify control points explicitly (\&{controls})} +@d tension=60 {specify tension between knots (\&{tension})} +@d at_least=61 {bounded tension value (\&{atleast})} +@d curl_command=62 {specify curl at an end knot (\&{curl})} +@d macro_special=63 {special macro operators (\&{quote}, \.{\#\AT!}, etc.)} +@d right_delimiter=64 {the right delimiter of a matching pair} +@d left_bracket=65 {the operator `\.['} +@d right_bracket=66 {the operator `\.]'} +@d right_brace=67 {the operator `\.{\char`\}}'} +@d with_option=68 {option for filling (\&{withpen}, \&{withweight}, etc.)} +@d thing_to_add=69 + {variant of \&{addto} (\&{contour}, \&{doublepath}, \&{also})} +@d of_token=70 {the operator `\&{of}'} +@d to_token=71 {the operator `\&{to}'} +@d step_token=72 {the operator `\&{step}'} +@d until_token=73 {the operator `\&{until}'} +@d within_token=74 {the operator `\&{within}'} +@d lig_kern_token=75 + {the operators `\&{kern}' and `\.{=:}' and `\.{=:\char'174}, etc.} +@d assignment=76 {the operator `\.{:=}'} +@d skip_to=77 {the operation `\&{skipto}'} +@d bchar_label=78 {the operator `\.{\char'174\char'174:}'} +@d double_colon=79 {the operator `\.{::}'} +@d colon=80 {the operator `\.:'} +@# +@d comma=81 {the operator `\.,', must be |colon+1|} +@d end_of_statement==cur_cmd>comma +@d semicolon=82 {the operator `\.;', must be |comma+1|} +@d end_group=83 {end a group (\&{endgroup}), must be |semicolon+1|} +@d stop=84 {end a job (\&{end}, \&{dump}), must be |end_group+1|} +@d max_command_code=stop +@d outer_tag=max_command_code+1 {protection code added to command code} + +@<Types...@>= +@!command_code=1..max_command_code; + +@ Variables and capsules in \MP\ have a variety of ``types,'' +distinguished by the code numbers defined here. These numbers are also +not completely arbitrary. Things that get expanded must have types +|>independent|; a type remaining after expansion is numeric if and only if +its code number is at least |numeric_type|; objects containing numeric +parts must have types between |transform_type| and |pair_type|; +all other types must be smaller than |transform_type|; and among the types +that are not unknown or vacuous, the smallest two must be |boolean_type| +and |string_type| in that order. + +@d undefined=0 {no type has been declared} +@d unknown_tag=1 {this constant is added to certain type codes below} +@d vacuous=1 {no expression was present} +@d boolean_type=2 {\&{boolean} with a known value} +@d unknown_boolean=boolean_type+unknown_tag +@d string_type=4 {\&{string} with a known value} +@d unknown_string=string_type+unknown_tag +@d pen_type=6 {\&{pen} with a known value} +@d unknown_pen=pen_type+unknown_tag +@d path_type=8 {\&{path} with a known value} +@d unknown_path=path_type+unknown_tag +@d picture_type=10 {\&{picture} with a known value} +@d unknown_picture=picture_type+unknown_tag +@d transform_type=12 {\&{transform} variable or capsule} +@d color_type=13 {\&{color} variable or capsule} +@d pair_type=14 {\&{pair} variable or capsule} +@d numeric_type=15 {variable that has been declared \&{numeric} but not used} +@d known=16 {\&{numeric} with a known value} +@d dependent=17 {a linear combination with |fraction| coefficients} +@d proto_dependent=18 {a linear combination with |scaled| coefficients} +@d independent=19 {\&{numeric} with unknown value} +@d token_list=20 {variable name or suffix argument or text argument} +@d structured=21 {variable with subscripts and attributes} +@d unsuffixed_macro=22 {variable defined with \&{vardef} but no \.{\AT!\#}} +@d suffixed_macro=23 {variable defined with \&{vardef} and \.{\AT!\#}} +@# +@d unknown_types==unknown_boolean,unknown_string, + unknown_pen,unknown_picture,unknown_path + +@<Basic printing procedures@>= +procedure print_type(@!t:small_number); +begin case t of +vacuous:print("vacuous"); +boolean_type:print("boolean"); +unknown_boolean:print("unknown boolean"); +string_type:print("string"); +unknown_string:print("unknown string"); +pen_type:print("pen"); +unknown_pen:print("unknown pen"); +path_type:print("path"); +unknown_path:print("unknown path"); +picture_type:print("picture"); +unknown_picture:print("unknown picture"); +transform_type:print("transform"); +color_type:print("color"); +pair_type:print("pair"); +known:print("known numeric"); +dependent:print("dependent"); +proto_dependent:print("proto-dependent"); +numeric_type:print("numeric"); +independent:print("independent"); +token_list:print("token list"); +structured:print("structured"); +unsuffixed_macro:print("unsuffixed macro"); +suffixed_macro:print("suffixed macro"); +othercases print("undefined") +endcases; +end; + +@ Values inside \MP\ are stored in two-word nodes that have a |name_type| +as well as a |type|. The possibilities for |name_type| are defined +here; they will be explained in more detail later. + +@d root=0 {|name_type| at the top level of a variable} +@d saved_root=1 {same, when the variable has been saved} +@d structured_root=2 {|name_type| where a |structured| branch occurs} +@d subscr=3 {|name_type| in a subscript node} +@d attr=4 {|name_type| in an attribute node} +@d x_part_sector=5 {|name_type| in the \&{xpart} of a node} +@d y_part_sector=6 {|name_type| in the \&{ypart} of a node} +@d xx_part_sector=7 {|name_type| in the \&{xxpart} of a node} +@d xy_part_sector=8 {|name_type| in the \&{xypart} of a node} +@d yx_part_sector=9 {|name_type| in the \&{yxpart} of a node} +@d yy_part_sector=10 {|name_type| in the \&{yypart} of a node} +@d red_part_sector=11 {|name_type| in the \&{redpart} of a node} +@d green_part_sector=12 {|name_type| in the \&{greenpart} of a node} +@d blue_part_sector=13 {|name_type| in the \&{bluepart} of a node} +@d capsule=14 {|name_type| in stashed-away subexpressions} +@d token=15 {|name_type| in a numeric token or string token} + +@ Primitive operations that produce values have a secondary identification +code in addition to their command code; it's something like genera and species. +For example, `\.*' has the command code |primary_binary|, and its +secondary identification is |times|. The secondary codes start at 30 so that +they don't overlap with the type codes; some type codes (e.g., |string_type|) +are used as operators as well as type identifications. The relative values +are not critical, except for |true_code..false_code|, |or_op..and_op|, +and |filled_op..bounded_op|. The restrictions are that +|and_op-false_code=or_op-true_code|, that the ordering of +|x_part...blue_part| must match that of |x_part_sector..blue_part_sector|, +and the ordering of |filled_op..bounded_op| must match that of the code +values they test for. + +@d true_code=30 {operation code for \.{true}} +@d false_code=31 {operation code for \.{false}} +@d null_picture_code=32 {operation code for \.{nullpicture}} +@d null_pen_code=33 {operation code for \.{nullpen}} +@d job_name_op=34 {operation code for \.{jobname}} +@d read_string_op=35 {operation code for \.{readstring}} +@d pen_circle=36 {operation code for \.{pencircle}} +@d normal_deviate=37 {operation code for \.{normaldeviate}} +@d read_from_op=38 {operation code for \.{readfrom}} +@d close_from_op=39 {operation code for \.{closefrom}} +@d odd_op=40 {operation code for \.{odd}} +@d known_op=41 {operation code for \.{known}} +@d unknown_op=42 {operation code for \.{unknown}} +@d not_op=43 {operation code for \.{not}} +@d decimal=44 {operation code for \.{decimal}} +@d reverse=45 {operation code for \.{reverse}} +@d make_path_op=46 {operation code for \.{makepath}} +@d make_pen_op=47 {operation code for \.{makepen}} +@d oct_op=48 {operation code for \.{oct}} +@d hex_op=49 {operation code for \.{hex}} +@d ASCII_op=50 {operation code for \.{ASCII}} +@d char_op=51 {operation code for \.{char}} +@d length_op=52 {operation code for \.{length}} +@d turning_op=53 {operation code for \.{turningnumber}} +@d x_part=54 {operation code for \.{xpart}} +@d y_part=55 {operation code for \.{ypart}} +@d xx_part=56 {operation code for \.{xxpart}} +@d xy_part=57 {operation code for \.{xypart}} +@d yx_part=58 {operation code for \.{yxpart}} +@d yy_part=59 {operation code for \.{yypart}} +@d red_part=60 {operation code for \.{redpart}} +@d green_part=61 {operation code for \.{greenpart}} +@d blue_part=62 {operation code for \.{bluepart}} +@d font_part=63 {operation code for \.{fontpart}} +@d text_part=64 {operation code for \.{textpart}} +@d path_part=65 {operation code for \.{pathpart}} +@d pen_part=66 {operation code for \.{penpart}} +@d dash_part=67 {operation code for \.{dashpart}} +@d sqrt_op=68 {operation code for \.{sqrt}} +@d m_exp_op=69 {operation code for \.{mexp}} +@d m_log_op=70 {operation code for \.{mlog}} +@d sin_d_op=71 {operation code for \.{sind}} +@d cos_d_op=72 {operation code for \.{cosd}} +@d floor_op=73 {operation code for \.{floor}} +@d uniform_deviate=74 {operation code for \.{uniformdeviate}} +@d char_exists_op=75 {operation code for \.{charexists}} +@d font_size=76 {operation code for \.{fontsize}} +@d ll_corner_op=77 {operation code for \.{llcorner}} +@d lr_corner_op=78 {operation code for \.{lrcorner}} +@d ul_corner_op=79 {operation code for \.{ulcorner}} +@d ur_corner_op=80 {operation code for \.{urcorner}} +@d arc_length=81 {operation code for \.{arclength}} +@d angle_op=82 {operation code for \.{angle}} +@d cycle_op=83 {operation code for \.{cycle}} +@d filled_op=84 {operation code for \.{filled}} +@d stroked_op=85 {operation code for \.{stroked}} +@d textual_op=86 {operation code for \.{textual}} +@d clipped_op=87 {operation code for \.{clipped}} +@d bounded_op=88 {operation code for \.{bounded}} +@d plus=89 {operation code for \.+} +@d minus=90 {operation code for \.-} +@d times=91 {operation code for \.*} +@d over=92 {operation code for \./} +@d pythag_add=93 {operation code for \.{++}} +@d pythag_sub=94 {operation code for \.{+-+}} +@d or_op=95 {operation code for \.{or}} +@d and_op=96 {operation code for \.{and}} +@d less_than=97 {operation code for \.<} +@d less_or_equal=98 {operation code for \.{<=}} +@d greater_than=99 {operation code for \.>} +@d greater_or_equal=100 {operation code for \.{>=}} +@d equal_to=101 {operation code for \.=} +@d unequal_to=102 {operation code for \.{<>}} +@d concatenate=103 {operation code for \.\&} +@d rotated_by=104 {operation code for \.{rotated}} +@d slanted_by=105 {operation code for \.{slanted}} +@d scaled_by=106 {operation code for \.{scaled}} +@d shifted_by=107 {operation code for \.{shifted}} +@d transformed_by=108 {operation code for \.{transformed}} +@d x_scaled=109 {operation code for \.{xscaled}} +@d y_scaled=110 {operation code for \.{yscaled}} +@d z_scaled=111 {operation code for \.{zscaled}} +@d in_font=112 {operation code for \.{infont}} +@d intersect=113 {operation code for \.{intersectiontimes}} +@d double_dot=114 {operation code for improper \.{..}} +@d substring_of=115 {operation code for \.{substring}} +@d min_of=substring_of +@d subpath_of=116 {operation code for \.{subpath}} +@d direction_time_of=117 {operation code for \.{directiontime}} +@d point_of=118 {operation code for \.{point}} +@d precontrol_of=119 {operation code for \.{precontrol}} +@d postcontrol_of=120 {operation code for \.{postcontrol}} +@d pen_offset_of=121 {operation code for \.{penoffset}} +@d arc_time_of=122 {operation code for \.{arctime}} +@d mp_version=123 {operation code for \.{mpversion}} + +@p procedure print_op(@!c:quarterword); +begin if c<=numeric_type then print_type(c) +else case c of +true_code:print("true"); +false_code:print("false"); +null_picture_code:print("nullpicture"); +null_pen_code:print("nullpen"); +job_name_op:print("jobname"); +read_string_op:print("readstring"); +pen_circle:print("pencircle"); +normal_deviate:print("normaldeviate"); +read_from_op:print("readfrom"); +close_from_op:print("closefrom"); +odd_op:print("odd"); +known_op:print("known"); +unknown_op:print("unknown"); +not_op:print("not"); +decimal:print("decimal"); +reverse:print("reverse"); +make_path_op:print("makepath"); +make_pen_op:print("makepen"); +oct_op:print("oct"); +hex_op:print("hex"); +ASCII_op:print("ASCII"); +char_op:print("char"); +length_op:print("length"); +turning_op:print("turningnumber"); +x_part:print("xpart"); +y_part:print("ypart"); +xx_part:print("xxpart"); +xy_part:print("xypart"); +yx_part:print("yxpart"); +yy_part:print("yypart"); +red_part:print("redpart"); +green_part:print("greenpart"); +blue_part:print("bluepart"); +font_part:print("fontpart"); +text_part:print("textpart"); +path_part:print("pathpart"); +pen_part:print("penpart"); +dash_part:print("dashpart"); +sqrt_op:print("sqrt"); +m_exp_op:print("mexp"); +m_log_op:print("mlog"); +sin_d_op:print("sind"); +cos_d_op:print("cosd"); +floor_op:print("floor"); +uniform_deviate:print("uniformdeviate"); +char_exists_op:print("charexists"); +font_size:print("fontsize"); +ll_corner_op:print("llcorner"); +lr_corner_op:print("lrcorner"); +ul_corner_op:print("ulcorner"); +ur_corner_op:print("urcorner"); +arc_length:print("arclength"); +angle_op:print("angle"); +cycle_op:print("cycle"); +filled_op:print("filled"); +stroked_op:print("stroked"); +textual_op:print("textual"); +clipped_op:print("clipped"); +bounded_op:print("bounded"); +plus:print_char("+"); +minus:print_char("-"); +times:print_char("*"); +over:print_char("/"); +pythag_add:print("++"); +pythag_sub:print("+-+"); +or_op:print("or"); +and_op:print("and"); +less_than:print_char("<"); +less_or_equal:print("<="); +greater_than:print_char(">"); +greater_or_equal:print(">="); +equal_to:print_char("="); +unequal_to:print("<>"); +concatenate:print("&"); +rotated_by:print("rotated"); +slanted_by:print("slanted"); +scaled_by:print("scaled"); +shifted_by:print("shifted"); +transformed_by:print("transformed"); +x_scaled:print("xscaled"); +y_scaled:print("yscaled"); +z_scaled:print("zscaled"); +in_font:print("infont"); +intersect:print("intersectiontimes"); +substring_of:print("substring"); +subpath_of:print("subpath"); +direction_time_of:print("directiontime"); +point_of:print("point"); +precontrol_of:print("precontrol"); +postcontrol_of:print("postcontrol"); +pen_offset_of:print("penoffset"); +arc_time_of:print("arctime"); +mp_version:print("mpversion"); +othercases print("..") +endcases; +end; + +@ \MP\ also has a bunch of internal parameters that a user might want to +fuss with. Every such parameter has an identifying code number, defined here. + +@d tracing_titles=1 {show titles online when they appear} +@d tracing_equations=2 {show each variable when it becomes known} +@d tracing_capsules=3 {show capsules too} +@d tracing_choices=4 {show the control points chosen for paths} +@d tracing_specs=5 {show path subdivision prior to filling with polygonal a pen} +@d tracing_commands=6 {show commands and operations before they are performed} +@d tracing_restores=7 {show when a variable or internal is restored} +@d tracing_macros=8 {show macros before they are expanded} +@d tracing_output=9 {show digitized edges as they are output} +@d tracing_stats=10 {show memory usage at end of job} +@d tracing_lost_chars=11 {show characters that aren't \&{infont}} +@d tracing_online=12 {show long diagnostics on terminal and in the log file} +@d year=13 {the current year (e.g., 1984)} +@d month=14 {the current month (e.g, 3 $\equiv$ March)} +@d day=15 {the current day of the month} +@d time=16 {the number of minutes past midnight when this job started} +@d char_code=17 {the number of the next character to be output} +@d char_ext=18 {the extension code of the next character to be output} +@d char_wd=19 {the width of the next character to be output} +@d char_ht=20 {the height of the next character to be output} +@d char_dp=21 {the depth of the next character to be output} +@d char_ic=22 {the italic correction of the next character to be output} +@d design_size=23 {the unit of measure used for |char_wd..char_ic|, in points} +@d pausing=24 {positive to display lines on the terminal before they are read} +@d showstopping=25 {positive to stop after each \&{show} command} +@d fontmaking=26 {positive if font metric output is to be produced} +@d linejoin=27 {as in \ps: 0 for mitered, 1 for round, 2 for beveled} +@d linecap=28 {as in \ps: 0 for butt, 1 for round, 2 for square} +@d miterlimit=29 {controls miter length as in \ps} +@d warning_check=30 {controls error message when variable value is large} +@d boundary_char=31 {the right boundary character for ligatures} +@d prologues=32 {positive to output conforming PostScript using built-in fonts} +@d true_corners=33 {positive to make \&{llcorner} etc. ignore \&{setbounds}} +@d max_given_internal=33 + +@<Glob...@>= +@!internal:array[1..max_internal] of scaled; + {the values of internal quantities} +@!int_name:array[1..max_internal] of str_number; + {their names} +@!int_ptr:max_given_internal..max_internal; + {the maximum internal quantity defined so far} + +@ @<Set init...@>= +for k:=1 to max_given_internal do internal[k]:=0; +int_ptr:=max_given_internal; + +@ The symbolic names for internal quantities are put into \MP's hash table +by using a routine called |primitive|, which will be defined later. Let us +enter them now, so that we don't have to list all those names again +anywhere else. + +@<Put each of \MP's primitives into the hash table@>= +primitive("tracingtitles",internal_quantity,tracing_titles);@/ +@!@:tracingtitles_}{\&{tracingtitles} primitive@> +primitive("tracingequations",internal_quantity,tracing_equations);@/ +@!@:tracing_equations_}{\&{tracingequations} primitive@> +primitive("tracingcapsules",internal_quantity,tracing_capsules);@/ +@!@:tracing_capsules_}{\&{tracingcapsules} primitive@> +primitive("tracingchoices",internal_quantity,tracing_choices);@/ +@!@:tracing_choices_}{\&{tracingchoices} primitive@> +primitive("tracingspecs",internal_quantity,tracing_specs);@/ +@!@:tracing_specs_}{\&{tracingspecs} primitive@> +primitive("tracingcommands",internal_quantity,tracing_commands);@/ +@!@:tracing_commands_}{\&{tracingcommands} primitive@> +primitive("tracingrestores",internal_quantity,tracing_restores);@/ +@!@:tracing_restores_}{\&{tracingrestores} primitive@> +primitive("tracingmacros",internal_quantity,tracing_macros);@/ +@!@:tracing_macros_}{\&{tracingmacros} primitive@> +primitive("tracingoutput",internal_quantity,tracing_output);@/ +@!@:tracing_output_}{\&{tracingoutput} primitive@> +primitive("tracingstats",internal_quantity,tracing_stats);@/ +@!@:tracing_stats_}{\&{tracingstats} primitive@> +primitive("tracinglostchars",internal_quantity,tracing_lost_chars);@/ +@!@:tracing_lost_chars_}{\&{tracinglostchars} primitive@> +primitive("tracingonline",internal_quantity,tracing_online);@/ +@!@:tracing_online_}{\&{tracingonline} primitive@> +primitive("year",internal_quantity,year);@/ +@!@:year_}{\&{year} primitive@> +primitive("month",internal_quantity,month);@/ +@!@:month_}{\&{month} primitive@> +primitive("day",internal_quantity,day);@/ +@!@:day_}{\&{day} primitive@> +primitive("time",internal_quantity,time);@/ +@!@:time_}{\&{time} primitive@> +primitive("charcode",internal_quantity,char_code);@/ +@!@:char_code_}{\&{charcode} primitive@> +primitive("charext",internal_quantity,char_ext);@/ +@!@:char_ext_}{\&{charext} primitive@> +primitive("charwd",internal_quantity,char_wd);@/ +@!@:char_wd_}{\&{charwd} primitive@> +primitive("charht",internal_quantity,char_ht);@/ +@!@:char_ht_}{\&{charht} primitive@> +primitive("chardp",internal_quantity,char_dp);@/ +@!@:char_dp_}{\&{chardp} primitive@> +primitive("charic",internal_quantity,char_ic);@/ +@!@:char_ic_}{\&{charic} primitive@> +primitive("designsize",internal_quantity,design_size);@/ +@!@:design_size_}{\&{designsize} primitive@> +primitive("pausing",internal_quantity,pausing);@/ +@!@:pausing_}{\&{pausing} primitive@> +primitive("showstopping",internal_quantity,showstopping);@/ +@!@:showstopping_}{\&{showstopping} primitive@> +primitive("fontmaking",internal_quantity,fontmaking);@/ +@!@:fontmaking_}{\&{fontmaking} primitive@> +primitive("linejoin",internal_quantity,linejoin);@/ +@!@:linejoin_}{\&{linejoin} primitive@> +primitive("linecap",internal_quantity,linecap);@/ +@!@:linecap_}{\&{linecap} primitive@> +primitive("miterlimit",internal_quantity,miterlimit);@/ +@!@:miterlimit_}{\&{miterlimit} primitive@> +primitive("warningcheck",internal_quantity,warning_check);@/ +@!@:warning_check_}{\&{warningcheck} primitive@> +primitive("boundarychar",internal_quantity,boundary_char);@/ +@!@:boundary_char_}{\&{boundarychar} primitive@> +primitive("prologues",internal_quantity,prologues);@/ +@!@:prologues_}{\&{prologues} primitive@> +primitive("truecorners",internal_quantity,true_corners);@/ +@!@:true_corners_}{\&{truecorners} primitive@> + +@ Well, we do have to list the names one more time, for use in symbolic +printouts. + +@<Initialize table...@>= +int_name[tracing_titles]:="tracingtitles"; +int_name[tracing_equations]:="tracingequations"; +int_name[tracing_capsules]:="tracingcapsules"; +int_name[tracing_choices]:="tracingchoices"; +int_name[tracing_specs]:="tracingspecs"; +int_name[tracing_commands]:="tracingcommands"; +int_name[tracing_restores]:="tracingrestores"; +int_name[tracing_macros]:="tracingmacros"; +int_name[tracing_output]:="tracingoutput"; +int_name[tracing_stats]:="tracingstats"; +int_name[tracing_lost_chars]:="tracinglostchars"; +int_name[tracing_online]:="tracingonline"; +int_name[year]:="year"; +int_name[month]:="month"; +int_name[day]:="day"; +int_name[time]:="time"; +int_name[char_code]:="charcode"; +int_name[char_ext]:="charext"; +int_name[char_wd]:="charwd"; +int_name[char_ht]:="charht"; +int_name[char_dp]:="chardp"; +int_name[char_ic]:="charic"; +int_name[design_size]:="designsize"; +int_name[pausing]:="pausing"; +int_name[showstopping]:="showstopping"; +int_name[fontmaking]:="fontmaking"; +int_name[linejoin]:="linejoin"; +int_name[linecap]:="linecap"; +int_name[miterlimit]:="miterlimit"; +int_name[warning_check]:="warningcheck"; +int_name[boundary_char]:="boundarychar"; +int_name[prologues]:="prologues"; +int_name[true_corners]:="truecorners"; + +@ The following procedure, which is called just before \MP\ initializes its +input and output, establishes the initial values of the date and time. +@^system dependencies@> +Since standard \PASCAL\ cannot provide such information, something special +is needed. The program here simply specifies July 4, 1776, at noon; but +users probably want a better approximation to the truth. + +Note that the values are |scaled| integers. Hence \MP\ can no longer +be used after the year 32767. + +@p procedure fix_date_and_time; +begin internal[time]:=12*60*unity; {minutes since midnight} +internal[day]:=4*unity; {fourth day of the month} +internal[month]:=7*unity; {seventh month of the year} +internal[year]:=1776*unity; {Anno Domini} +end; + +@ \MP\ is occasionally supposed to print diagnostic information that +goes only into the transcript file, unless |tracing_online| is positive. +Now that we have defined |tracing_online| we can define +two routines that adjust the destination of print commands: + +@<Basic printing...@>= +@<Declare a function called |true_line|@>@; +procedure begin_diagnostic; {prepare to do some tracing} +begin old_setting:=selector; +if selector=ps_file_only then selector:=non_ps_setting; +if(internal[tracing_online]<=0)and(selector=term_and_log) then + begin decr(selector); + if history=spotless then history:=warning_issued; + end; +end; +@# +procedure end_diagnostic(@!blank_line:boolean); + {restore proper conditions after tracing} +begin print_nl(""); +if blank_line then print_ln; +selector:=old_setting; +end; + +@ The global variable |non_ps_setting| is initialized when it is time to print +on |ps_file|. + +@<Glob...@>= +@!old_setting,@!non_ps_setting:0..max_selector; + +@ We will occasionally use |begin_diagnostic| in connection with line-number +printing, as follows. (The parameter |s| is typically |"Path"| or +|"Cycle spec"|, etc.) + +@<Basic printing...@>= +procedure print_diagnostic(@!s,@!t:str_number;@!nuline:boolean); +begin begin_diagnostic; +if nuline then print_nl(s)@+else print(s); +print(" at line "); print_int(true_line); +print(t); print_char(":"); +end; + +@ The 256 |ASCII_code| characters are grouped into classes by means of +the |char_class| table. Individual class numbers have no semantic +or syntactic significance, except in a few instances defined here. +There's also |max_class|, which can be used as a basis for additional +class numbers in nonstandard extensions of \MP. + +@d digit_class=0 {the class number of \.{0123456789}} +@d period_class=1 {the class number of `\..'} +@d space_class=2 {the class number of spaces and nonstandard characters} +@d percent_class=3 {the class number of `\.\%'} +@d string_class=4 {the class number of `\."'} +@d right_paren_class=8 {the class number of `\.)'} +@d isolated_classes==5,6,7,8 {characters that make length-one tokens only} +@d letter_class=9 {letters and the underline character} +@d left_bracket_class=17 {`\.['} +@d right_bracket_class=18 {`\.]'} +@d invalid_class=20 {bad character in the input} +@d max_class=20 {the largest class number} + +@<Glob...@>= +@!char_class:array[ASCII_code] of 0..max_class; {the class numbers} + +@ If changes are made to accommodate non-ASCII character sets, they should +follow the guidelines in Appendix~C of {\sl The {\logos METAFONT\/}book}. +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +@^system dependencies@> + +@<Set init...@>= +for k:="0" to "9" do char_class[k]:=digit_class; +char_class["."]:=period_class; +char_class[" "]:=space_class; +char_class["%"]:=percent_class; +char_class[""""]:=string_class;@/ +char_class[","]:=5; +char_class[";"]:=6; +char_class["("]:=7; +char_class[")"]:=right_paren_class; +for k:="A" to "Z" do char_class[k]:=letter_class; +for k:="a" to "z" do char_class[k]:=letter_class; +char_class["_"]:=letter_class;@/ +char_class["<"]:=10; +char_class["="]:=10; +char_class[">"]:=10; +char_class[":"]:=10; +char_class["|"]:=10;@/ +char_class["`"]:=11; +char_class["'"]:=11;@/ +char_class["+"]:=12; +char_class["-"]:=12;@/ +char_class["/"]:=13; +char_class["*"]:=13; +char_class["\"]:=13;@/ +char_class["!"]:=14; +char_class["?"]:=14;@/ +char_class["#"]:=15; +char_class["&"]:=15; +char_class["@@"]:=15; +char_class["$"]:=15;@/ +char_class["^"]:=16; +char_class["~"]:=16;@/ +char_class["["]:=left_bracket_class; +char_class["]"]:=right_bracket_class;@/ +char_class["{"]:=19; +char_class["}"]:=19;@/ +for k:=0 to " "-1 do char_class[k]:=invalid_class; +for k:=127 to 255 do char_class[k]:=invalid_class; + +@* \[13] The hash table. +Symbolic tokens are stored and retrieved by means of a fairly standard hash +table algorithm called the method of ``coalescing lists'' (cf.\ Algorithm 6.4C +in {\sl The Art of Computer Programming\/}). Once a symbolic token enters the +table, it is never removed. + +The actual sequence of characters forming a symbolic token is +stored in the |str_pool| array together with all the other strings. An +auxiliary array |hash| consists of items with two halfword fields per +word. The first of these, called |next(p)|, points to the next identifier +belonging to the same coalesced list as the identifier corresponding to~|p|; +and the other, called |text(p)|, points to the |str_start| entry for +|p|'s identifier. If position~|p| of the hash table is empty, we have +|text(p)=0|; if position |p| is either empty or the end of a coalesced +hash list, we have |next(p)=0|. + +An auxiliary pointer variable called |hash_used| is maintained in such a +way that all locations |p>=hash_used| are nonempty. The global variable +|st_count| tells how many symbolic tokens have been defined, if statistics +are being kept. + +The first 256 locations of |hash| are reserved for symbols of length one. + +There's a parallel array called |eqtb| that contains the current equivalent +values of each symbolic token. The entries of this array consist of +two halfwords called |eq_type| (a command code) and |equiv| (a secondary +piece of information that qualifies the |eq_type|). + +@d next(#) == hash[#].lh {link for coalesced lists} +@d text(#) == hash[#].rh {string number for symbolic token name} +@d eq_type(#) == eqtb[#].lh {the current ``meaning'' of a symbolic token} +@d equiv(#) == eqtb[#].rh {parametric part of a token's meaning} +@d hash_base=257 {hashing actually starts here} +@d hash_is_full == (hash_used=hash_base) {are all positions occupied?} + +@<Glob...@>= +@!hash_used:pointer; {allocation pointer for |hash|} +@!st_count:integer; {total number of known identifiers} + +@ Certain entries in the hash table are ``frozen'' and not redefinable, +since they are used in error recovery. + +@d hash_top==hash_base+hash_size {the first location of the frozen area} +@d frozen_inaccessible==hash_top {|hash| location to protect the frozen area} +@d frozen_repeat_loop==hash_top+1 {|hash| location of a loop-repeat token} +@d frozen_right_delimiter==hash_top+2 {|hash| location of a permanent `\.)'} +@d frozen_left_bracket==hash_top+3 {|hash| location of a permanent `\.['} +@d frozen_slash==hash_top+4 {|hash| location of a permanent `\./'} +@d frozen_colon==hash_top+5 {|hash| location of a permanent `\.:'} +@d frozen_semicolon==hash_top+6 {|hash| location of a permanent `\.;'} +@d frozen_end_for==hash_top+7 {|hash| location of a permanent \&{endfor}} +@d frozen_end_def==hash_top+8 {|hash| location of a permanent \&{enddef}} +@d frozen_fi==hash_top+9 {|hash| location of a permanent \&{fi}} +@d frozen_end_group==hash_top+10 + {|hash| location of a permanent `\.{endgroup}'} +@d frozen_etex==hash_top+11 {|hash| location of a permanent \&{etex}} +@d frozen_mpx_break==hash_top+12 {|hash| location of a permanent \&{mpxbreak}} +@d frozen_bad_vardef==hash_top+13 {|hash| location of `\.{a bad variable}'} +@d frozen_undefined==hash_top+14 {|hash| location that never gets defined} +@d hash_end==hash_top+14 {the actual size of the |hash| and |eqtb| arrays} + +@<Glob...@>= +@!hash: array[1..hash_end] of two_halves; {the hash table} +@!eqtb: array[1..hash_end] of two_halves; {the equivalents} + +@ @<Set init...@>= +next(1):=0; text(1):=0; eq_type(1):=tag_token; equiv(1):=null; +for k:=2 to hash_end do + begin hash[k]:=hash[1]; eqtb[k]:=eqtb[1]; + end; + +@ @<Initialize table entries...@>= +hash_used:=frozen_inaccessible; {nothing is used} +st_count:=0;@/ +text(frozen_bad_vardef):="a bad variable"; +text(frozen_etex):="etex"; +text(frozen_mpx_break):="mpxbreak"; +text(frozen_fi):="fi"; +text(frozen_end_group):="endgroup"; +text(frozen_end_def):="enddef"; +text(frozen_end_for):="endfor";@/ +text(frozen_semicolon):=";"; +text(frozen_colon):=":"; +text(frozen_slash):="/"; +text(frozen_left_bracket):="["; +text(frozen_right_delimiter):=")";@/ +text(frozen_inaccessible):=" INACCESSIBLE";@/ +eq_type(frozen_right_delimiter):=right_delimiter; + +@ @<Check the ``constant'' values...@>= +if hash_end+max_internal>max_halfword then bad:=17; + +@ Here is the subroutine that searches the hash table for an identifier +that matches a given string of length~|l| appearing in |buffer[j.. +(j+l-1)]|. If the identifier is not found, it is inserted; hence it +will always be found, and the corresponding hash table address +will be returned. + +@p function id_lookup(@!j,@!l:integer):pointer; {search the hash table} +label found; {go here when you've found it} +var @!h:integer; {hash code} +@!p:pointer; {index in |hash| array} +@!k:pointer; {index in |buffer| array} +begin if l=1 then @<Treat special case of length 1 and |goto found|@>; +@<Compute the hash code |h|@>; +p:=h+hash_base; {we start searching here; note that |0<=h<hash_prime|} +loop@+ begin if text(p)>0 then if length(text(p))=l then + if str_eq_buf(text(p),j) then goto found; + if next(p)=0 then + @<Insert a new symbolic token after |p|, then + make |p| point to it and |goto found|@>; + p:=next(p); + end; +found: id_lookup:=p; +end; + +@ @<Treat special case of length 1...@>= +begin p:=buffer[j]+1; text(p):=p-1; goto found; +end + +@ @<Insert a new symbolic...@>= +begin if text(p)>0 then + begin repeat if hash_is_full then + overflow("hash size",hash_size); +@:MetaPost capacity exceeded hash size}{\quad hash size@> + decr(hash_used); + until text(hash_used)=0; {search for an empty location in |hash|} + next(p):=hash_used; p:=hash_used; + end; +str_room(l); +for k:=j to j+l-1 do append_char(buffer[k]); +text(p):=make_string; str_ref[text(p)]:=max_str_ref; +@!stat incr(st_count);@+tats@;@/ +goto found; +end + +@ The value of |hash_prime| should be roughly 85\pct! of |hash_size|, and it +should be a prime number. The theory of hashing tells us to expect fewer +than two table probes, on the average, when the search is successful. +[See J.~S. Vitter, {\sl Journal of the ACM\/ \bf30} (1983), 231--258.] +@^Vitter, Jeffrey Scott@> + +@<Compute the hash code |h|@>= +h:=buffer[j]; +for k:=j+1 to j+l-1 do + begin h:=h+h+buffer[k]; + while h>=hash_prime do h:=h-hash_prime; + end + +@ @<Search |eqtb| for equivalents equal to |p|@>= +for q:=1 to hash_end do + begin if equiv(q)=p then + begin print_nl("EQUIV("); print_int(q); print_char(")"); + end; + end + +@ We need to put \MP's ``primitive'' symbolic tokens into the hash +table, together with their command code (which will be the |eq_type|) +and an operand (which will be the |equiv|). The |primitive| procedure +does this, in a way that no \MP\ user can. The global value |cur_sym| +contains the new |eqtb| pointer after |primitive| has acted. + +@p @!init procedure primitive(@!s:str_number;@!c:halfword;@!o:halfword); +var @!k:pool_pointer; {index into |str_pool|} +@!j:small_number; {index into |buffer|} +@!l:small_number; {length of the string} +begin k:=str_start[s]; l:=str_stop(s)-k; + {we will move |s| into the (empty) |buffer|} +for j:=0 to l-1 do buffer[j]:=so(str_pool[k+j]); +cur_sym:=id_lookup(0,l);@/ +if s>=256 then {we don't want to have the string twice} + begin flush_string(text(cur_sym)); text(cur_sym):=s; + end; +eq_type(cur_sym):=c; equiv(cur_sym):=o; +end; +tini + +@ Many of \MP's primitives need no |equiv|, since they are identifiable +by their |eq_type| alone. These primitives are loaded into the hash table +as follows: + +@<Put each of \MP's primitives into the hash table@>= +primitive("..",path_join,0);@/ +@!@:.._}{\.{..} primitive@> +primitive("[",left_bracket,0); eqtb[frozen_left_bracket]:=eqtb[cur_sym];@/ +@!@:[ }{\.{[} primitive@> +primitive("]",right_bracket,0);@/ +@!@:] }{\.{]} primitive@> +primitive("}",right_brace,0);@/ +@!@:]]}{\.{\char`\}} primitive@> +primitive("{",left_brace,0);@/ +@!@:][}{\.{\char`\{} primitive@> +primitive(":",colon,0); eqtb[frozen_colon]:=eqtb[cur_sym];@/ +@!@:: }{\.{:} primitive@> +primitive("::",double_colon,0);@/ +@!@::: }{\.{::} primitive@> +primitive("||:",bchar_label,0);@/ +@!@:::: }{\.{\char'174\char'174:} primitive@> +primitive(":=",assignment,0);@/ +@!@::=_}{\.{:=} primitive@> +primitive(",",comma,0);@/ +@!@:, }{\., primitive@> +primitive(";",semicolon,0); eqtb[frozen_semicolon]:=eqtb[cur_sym];@/ +@!@:; }{\.; primitive@> +primitive("\",relax,0);@/ +@!@:]]\\}{\.{\char`\\} primitive@> +@# +primitive("addto",add_to_command,0);@/ +@!@:add_to_}{\&{addto} primitive@> +primitive("atleast",at_least,0);@/ +@!@:at_least_}{\&{atleast} primitive@> +primitive("begingroup",begin_group,0); bg_loc:=cur_sym;@/ +@!@:begin_group_}{\&{begingroup} primitive@> +primitive("controls",controls,0);@/ +@!@:controls_}{\&{controls} primitive@> +primitive("curl",curl_command,0);@/ +@!@:curl_}{\&{curl} primitive@> +primitive("delimiters",delimiters,0);@/ +@!@:delimiters_}{\&{delimiters} primitive@> +primitive("endgroup",end_group,0); + eqtb[frozen_end_group]:=eqtb[cur_sym]; eg_loc:=cur_sym;@/ +@!@:endgroup_}{\&{endgroup} primitive@> +primitive("everyjob",every_job_command,0);@/ +@!@:every_job_}{\&{everyjob} primitive@> +primitive("exitif",exit_test,0);@/ +@!@:exit_if_}{\&{exitif} primitive@> +primitive("expandafter",expand_after,0);@/ +@!@:expand_after_}{\&{expandafter} primitive@> +primitive("interim",interim_command,0);@/ +@!@:interim_}{\&{interim} primitive@> +primitive("let",let_command,0);@/ +@!@:let_}{\&{let} primitive@> +primitive("newinternal",new_internal,0);@/ +@!@:new_internal_}{\&{newinternal} primitive@> +primitive("of",of_token,0);@/ +@!@:of_}{\&{of} primitive@> +primitive("randomseed",random_seed,0);@/ +@!@:random_seed_}{\&{randomseed} primitive@> +primitive("save",save_command,0);@/ +@!@:save_}{\&{save} primitive@> +primitive("scantokens",scan_tokens,0);@/ +@!@:scan_tokens_}{\&{scantokens} primitive@> +primitive("shipout",ship_out_command,0);@/ +@!@:ship_out_}{\&{shipout} primitive@> +primitive("skipto",skip_to,0);@/ +@!@:skip_to_}{\&{skipto} primitive@> +primitive("special",special_command,0); +@!@:special}{\&{special} primitive@> +primitive("step",step_token,0);@/ +@!@:step_}{\&{step} primitive@> +primitive("str",str_op,0);@/ +@!@:str_}{\&{str} primitive@> +primitive("tension",tension,0);@/ +@!@:tension_}{\&{tension} primitive@> +primitive("to",to_token,0);@/ +@!@:to_}{\&{to} primitive@> +primitive("until",until_token,0);@/ +@!@:until_}{\&{until} primitive@> +primitive("within",within_token,0);@/ +@!@:within_}{\&{within} primitive@> +primitive("write",write_command,0);@/ +@!@:write_}{\&{write} primitive@> + +@ Each primitive has a corresponding inverse, so that it is possible to +display the cryptic numeric contents of |eqtb| in symbolic form. +Every call of |primitive| in this program is therefore accompanied by some +straightforward code that forms part of the |print_cmd_mod| routine +explained below. + +@<Cases of |print_cmd_mod| for symbolic printing of primitives@>= +add_to_command:print("addto"); +assignment:print(":="); +at_least:print("atleast"); +bchar_label:print("||:"); +begin_group:print("begingroup"); +colon:print(":"); +comma:print(","); +controls:print("controls"); +curl_command:print("curl"); +delimiters:print("delimiters"); +double_colon:print("::"); +end_group:print("endgroup"); +every_job_command:print("everyjob"); +exit_test:print("exitif"); +expand_after:print("expandafter"); +interim_command:print("interim"); +left_brace:print("{"); +left_bracket:print("["); +let_command:print("let"); +new_internal:print("newinternal"); +of_token:print("of"); +path_join:print(".."); +random_seed:print("randomseed"); +relax:print_char("\"); +right_brace:print("}"); +right_bracket:print("]"); +save_command:print("save"); +scan_tokens:print("scantokens"); +semicolon:print(";"); +ship_out_command:print("shipout"); +skip_to:print("skipto"); +special_command: print("special"); +step_token:print("step"); +str_op:print("str"); +tension:print("tension"); +to_token:print("to"); +until_token:print("until"); +within_token:print("within"); +write_command:print("write"); + +@ We will deal with the other primitives later, at some point in the program +where their |eq_type| and |equiv| values are more meaningful. For example, +the primitives for macro definitions will be loaded when we consider the +routines that define macros. +It is easy to find where each particular +primitive was treated by looking in the index at the end; for example, the +section where |"def"| entered |eqtb| is listed under `\&{def} primitive'. + +@* \[14] Token lists. +A \MP\ token is either symbolic or numeric or a string, or it denotes +a macro parameter or capsule; so there are five corresponding ways to encode it +@^token@> +internally: (1)~A symbolic token whose hash code is~|p| +is represented by the number |p|, in the |info| field of a single-word +node in~|mem|. (2)~A numeric token whose |scaled| value is~|v| is +represented in a two-word node of~|mem|; the |type| field is |known|, +the |name_type| field is |token|, and the |value| field holds~|v|. +The fact that this token appears in a two-word node rather than a +one-word node is, of course, clear from the node address. +(3)~A string token is also represented in a two-word node; the |type| +field is |string_type|, the |name_type| field is |token|, and the +|value| field holds the corresponding |str_number|. (4)~Capsules have +|name_type=capsule|, and their |type| and |value| fields represent +arbitrary values (in ways to be explained later). (5)~Macro parameters +are like symbolic tokens in that they appear in |info| fields of +one-word nodes. The $k$th parameter is represented by |expr_base+k| if it +is of type \&{expr}, or by |suffix_base+k| if it is of type \&{suffix}, or +by |text_base+k| if it is of type \&{text}. (Here |0<=k<param_size|.) +Actual values of these parameters are kept in a separate stack, as we will +see later. The constants |expr_base|, |suffix_base|, and |text_base| are, +of course, chosen so that there will be no confusion between symbolic +tokens and parameters of various types. + +Note that +the `\\{type}' field of a node has nothing to do with ``type'' in a +printer's sense. It's curious that the same word is used in such different ways. + +@d type(#) == mem[#].hh.b0 {identifies what kind of value this is} +@d name_type(#) == mem[#].hh.b1 {a clue to the name of this value} +@d token_node_size=2 {the number of words in a large token node} +@d value_loc(#)==#+1 {the word that contains the |value| field} +@d value(#)==mem[value_loc(#)].int {the value stored in a large token node} +@d expr_base==hash_end+1 {code for the zeroth \&{expr} parameter} +@d suffix_base==expr_base+param_size {code for the zeroth \&{suffix} parameter} +@d text_base==suffix_base+param_size {code for the zeroth \&{text} parameter} + +@<Check the ``constant''...@>= +if text_base+param_size>max_halfword then bad:=18; + +@ We have set aside a two word node beginning at |null| so that we can have +|value(null)=0|. We will make use of this coincidence later. + +@<Initialize table entries...@>= +link(null):=null; +value(null):=0; + +@ A numeric token is created by the following trivial routine. + +@p function new_num_tok(@!v:scaled):pointer; +var @!p:pointer; {the new node} +begin p:=get_node(token_node_size); value(p):=v; +type(p):=known; name_type(p):=token; new_num_tok:=p; +end; + +@ A token list is a singly linked list of nodes in |mem|, where +each node contains a token and a link. Here's a subroutine that gets rid +of a token list when it is no longer needed. + +@p procedure@?token_recycle; forward;@t\2@>@;@/ +procedure flush_token_list(@!p:pointer); +var @!q:pointer; {the node being recycled} +begin while p<>null do + begin q:=p; p:=link(p); + if q>=hi_mem_min then free_avail(q) + else begin case type(q) of + vacuous,boolean_type,known:do_nothing; + string_type:delete_str_ref(value(q)); + unknown_types,pen_type,path_type,picture_type,pair_type,color_type, + transform_type,dependent,proto_dependent,independent: + begin g_pointer:=q; token_recycle; + end; + othercases confusion("token") +@:this can't happen token}{\quad token@> + endcases;@/ + free_node(q,token_node_size); + end; + end; +end; + +@ The procedure |show_token_list|, which prints a symbolic form of +the token list that starts at a given node |p|, illustrates these +conventions. The token list being displayed should not begin with a reference +count. However, the procedure is intended to be fairly robust, so that if the +memory links are awry or if |p| is not really a pointer to a token list, +almost nothing catastrophic can happen. + +An additional parameter |q| is also given; this parameter is either null +or it points to a node in the token list where a certain magic computation +takes place that will be explained later. (Basically, |q| is non-null when +we are printing the two-line context information at the time of an error +message; |q| marks the place corresponding to where the second line +should begin.) + +The generation will stop, and `\.{\char`\ ETC.}' will be printed, if the length +of printing exceeds a given limit~|l|; the length of printing upon entry is +assumed to be a given amount called |null_tally|. (Note that +|show_token_list| sometimes uses itself recursively to print +variable names within a capsule.) +@^recursion@> + +Unusual entries are printed in the form of all-caps tokens +preceded by a space, e.g., `\.{\char`\ BAD}'. + +@<Declare the procedure called |show_token_list|@>= +procedure@?print_capsule; forward; @t\2@>@;@/ +procedure show_token_list(@!p,@!q:integer;@!l,@!null_tally:integer); +label exit; +var @!class,@!c:small_number; {the |char_class| of previous and new tokens} +@!r,@!v:integer; {temporary registers} +begin class:=percent_class; +tally:=null_tally; +while (p<>null) and (tally<l) do + begin if p=q then @<Do magic computation@>; + @<Display token |p| and set |c| to its class; + but |return| if there are problems@>; + class:=c; p:=link(p); + end; +if p<>null then print(" ETC."); +@.ETC@> +exit: +end; + +@ @<Display token |p| and set |c| to its class...@>= +c:=letter_class; {the default} +if (p<mem_min)or(p>mem_end) then + begin print(" CLOBBERED"); return; +@.CLOBBERED@> + end; +if p<hi_mem_min then @<Display two-word token@> +else begin r:=info(p); + if r>=expr_base then @<Display a parameter token@> + else if r<1 then + if r=0 then @<Display a collective subscript@> + else print(" IMPOSSIBLE") +@.IMPOSSIBLE@> + else begin r:=text(r); + if (r<0)or(r>max_str_ptr) then print(" NONEXISTENT") +@.NONEXISTENT@> + else @<Print string |r| as a symbolic token + and set |c| to its class@>; + end; + end + +@ @<Display two-word token@>= +if name_type(p)=token then + if type(p)=known then @<Display a numeric token@> + else if type(p)<>string_type then print(" BAD") +@.BAD@> + else begin print_char(""""); print(value(p)); print_char(""""); + c:=string_class; + end +else if (name_type(p)<>capsule)or(type(p)<vacuous)or(type(p)>independent) then + print(" BAD") +else begin g_pointer:=p; print_capsule; c:=right_paren_class; + end + +@ @<Display a numeric token@>= +begin if class=digit_class then print_char(" "); +v:=value(p); +if v<0 then + begin if class=left_bracket_class then print_char(" "); + print_char("["); print_scaled(v); print_char("]"); + c:=right_bracket_class; + end +else begin print_scaled(v); c:=digit_class; + end; +end + +@ Strictly speaking, a genuine token will never have |info(p)=0|. +But we will see later (in the |print_variable_name| routine) that +it is convenient to let |info(p)=0| stand for `\.{[]}'. + +@<Display a collective subscript@>= +begin if class=left_bracket_class then print_char(" "); +print("[]"); c:=right_bracket_class; +end + +@ @<Display a parameter token@>= +begin if r<suffix_base then + begin print("(EXPR"); r:=r-(expr_base); +@.EXPR@> + end +else if r<text_base then + begin print("(SUFFIX"); r:=r-(suffix_base); +@.SUFFIX@> + end +else begin print("(TEXT"); r:=r-(text_base); +@.TEXT@> + end; +print_int(r); print_char(")"); c:=right_paren_class; +end + +@ @<Print string |r| as a symbolic token...@>= +begin c:=char_class[so(str_pool[str_start[r]])]; +if c=class then + case c of + letter_class:print_char("."); + isolated_classes:do_nothing; + othercases print_char(" ") + endcases; +print(r); +end + +@ The following procedures have been declared |forward| with no parameters, +because the author dislikes \PASCAL's convention about |forward| procedures +with parameters. It was necessary to do something, because |show_token_list| +is recursive (although the recursion is limited to one level), and because +|flush_token_list| is syntactically (but not semantically) recursive. +@^recursion@> + +@<Declare miscellaneous procedures that were declared |forward|@>= +procedure print_capsule; +begin print_char("("); print_exp(g_pointer,0); print_char(")"); +end; +@# +procedure token_recycle; +begin recycle_value(g_pointer); +end; + +@ @<Glob...@>= +@!g_pointer:pointer; {(global) parameter to the |forward| procedures} + +@ Macro definitions are kept in \MP's memory in the form of token lists +that have a few extra one-word nodes at the beginning. + +The first node contains a reference count that is used to tell when the +list is no longer needed. To emphasize the fact that a reference count is +present, we shall refer to the |info| field of this special node as the +|ref_count| field. +@^reference counts@> + +The next node or nodes after the reference count serve to describe the +formal parameters. They either contain a code word that specifies all +of the parameters, or they contain zero or more parameter tokens followed +by the code `|general_macro|'. + +@d ref_count==info + {reference count preceding a macro definition or picture header} +@d add_mac_ref(#)==incr(ref_count(#)) {make a new reference to a macro list} +@d general_macro=0 {preface to a macro defined with a parameter list} +@d primary_macro=1 {preface to a macro with a \&{primary} parameter} +@d secondary_macro=2 {preface to a macro with a \&{secondary} parameter} +@d tertiary_macro=3 {preface to a macro with a \&{tertiary} parameter} +@d expr_macro=4 {preface to a macro with an undelimited \&{expr} parameter} +@d of_macro=5 {preface to a macro with + undelimited `\&{expr} |x| \&{of}~|y|' parameters} +@d suffix_macro=6 {preface to a macro with an undelimited \&{suffix} parameter} +@d text_macro=7 {preface to a macro with an undelimited \&{text} parameter} + +@p procedure delete_mac_ref(@!p:pointer); + {|p| points to the reference count of a macro list that is + losing one reference} +begin if ref_count(p)=null then flush_token_list(p) +else decr(ref_count(p)); +end; + +@ The following subroutine displays a macro, given a pointer to its +reference count. + +@p @t\4@>@<Declare the procedure called |print_cmd_mod|@>@; +procedure show_macro(@!p:pointer;@!q,@!l:integer); +label exit; +var @!r:pointer; {temporary storage} +begin p:=link(p); {bypass the reference count} +while info(p)>text_macro do + begin r:=link(p); link(p):=null; + show_token_list(p,null,l,0); link(p):=r; p:=r; + if l>0 then l:=l-tally@+else return; + end; {control printing of `\.{ETC.}'} +@.ETC@> +tally:=0; +case info(p) of +general_macro:print("->"); +@.->@> +primary_macro,secondary_macro,tertiary_macro:begin print_char("<"); + print_cmd_mod(param_type,info(p)); print(">->"); + end; +expr_macro:print("<expr>->"); +of_macro:print("<expr>of<primary>->"); +suffix_macro:print("<suffix>->"); +text_macro:print("<text>->"); +end; {there are no other cases} +show_token_list(link(p),q,l-tally,0); +exit:end; + +@* \[15] Data structures for variables. +The variables of \MP\ programs can be simple, like `\.x', or they can +combine the structural properties of arrays and records, like `\.{x20a.b}'. +A \MP\ user assigns a type to a variable like \.{x20a.b} by saying, for +example, `\.{boolean} \.{x20a.b}'. It's time for us to study how such +things are represented inside of the computer. + +Each variable value occupies two consecutive words, either in a two-word +node called a value node, or as a two-word subfield of a larger node. One +of those two words is called the |value| field; it is an integer, +containing either a |scaled| numeric value or the representation of some +other type of quantity. (It might also be subdivided into halfwords, in +which case it is referred to by other names instead of |value|.) The other +word is broken into subfields called |type|, |name_type|, and |link|. The +|type| field is a quarterword that specifies the variable's type, and +|name_type| is a quarterword from which \MP\ can reconstruct the +variable's name (sometimes by using the |link| field as well). Thus, only +1.25 words are actually devoted to the value itself; the other +three-quarters of a word are overhead, but they aren't wasted because they +allow \MP\ to deal with sparse arrays and to provide meaningful diagnostics. + +In this section we shall be concerned only with the structural aspects of +variables, not their values. Later parts of the program will change the +|type| and |value| fields, but we shall treat those fields as black boxes +whose contents should not be touched. + +However, if the |type| field is |structured|, there is no |value| field, +and the second word is broken into two pointer fields called |attr_head| +and |subscr_head|. Those fields point to additional nodes that +contain structural information, as we shall see. + +@d subscr_head_loc(#) == #+1 {where |value|, |subscr_head| and |attr_head| are} +@d attr_head(#) == info(subscr_head_loc(#)) {pointer to attribute info} +@d subscr_head(#) == link(subscr_head_loc(#)) {pointer to subscript info} +@d value_node_size=2 {the number of words in a value node} + +@ An attribute node is three words long. Two of these words contain |type| +and |value| fields as described above, and the third word contains +additional information: There is an |attr_loc| field, which contains the +hash address of the token that names this attribute; and there's also a +|parent| field, which points to the value node of |structured| type at the +next higher level (i.e., at the level to which this attribute is +subsidiary). The |name_type| in an attribute node is `|attr|'. The +|link| field points to the next attribute with the same parent; these are +arranged in increasing order, so that |attr_loc(link(p))>attr_loc(p)|. The +final attribute node links to the constant |end_attr|, whose |attr_loc| +field is greater than any legal hash address. The |attr_head| in the +parent points to a node whose |name_type| is |structured_root|; this +node represents the null attribute, i.e., the variable that is relevant +when no attributes are attached to the parent. The |attr_head| node is either +a value node, a subscript node, or an attribute node, depending on what +the parent would be if it were not structured; but the subscript and +attribute fields are ignored, so it effectively contains only the data of +a value node. The |link| field in this special node points to an attribute +node whose |attr_loc| field is zero; the latter node represents a collective +subscript `\.{[]}' attached to the parent, and its |link| field points to +the first non-special attribute node (or to |end_attr| if there are none). + +A subscript node likewise occupies three words, with |type| and |value| fields +plus extra information; its |name_type| is |subscr|. In this case the +third word is called the |subscript| field, which is a |scaled| integer. +The |link| field points to the subscript node with the next larger +subscript, if any; otherwise the |link| points to the attribute node +for collective subscripts at this level. We have seen that the latter node +contains an upward pointer, so that the parent can be deduced. + +The |name_type| in a parent-less value node is |root|, and the |link| +is the hash address of the token that names this value. + +In other words, variables have a hierarchical structure that includes +enough threads running around so that the program is able to move easily +between siblings, parents, and children. An example should be helpful: +(The reader is advised to draw a picture while reading the following +description, since that will help to firm up the ideas.) +Suppose that `\.x' and `\.{x.a}' and `\.{x[]b}' and `\.{x5}' +and `\.{x20b}' have been mentioned in a user's program, where +\.{x[]b} has been declared to be of \&{boolean} type. Let |h(x)|, |h(a)|, +and |h(b)| be the hash addresses of \.x, \.a, and~\.b. Then +|eq_type(h(x))=name| and |equiv(h(x))=p|, where |p|~is a two-word value +node with |name_type(p)=root| and |link(p)=h(x)|. We have |type(p)=structured|, +|attr_head(p)=q|, and |subscr_head(p)=r|, where |q| points to a value +node and |r| to a subscript node. (Are you still following this? Use +a pencil to draw a diagram.) The lone variable `\.x' is represented by +|type(q)| and |value(q)|; furthermore +|name_type(q)=structured_root| and |link(q)=q1|, where |q1| points +to an attribute node representing `\.{x[]}'. Thus |name_type(q1)=attr|, +|attr_loc(q1)=collective_subscript=0|, |parent(q1)=p|, +|type(q1)=structured|, |attr_head(q1)=qq|, and |subscr_head(q1)=qq1|; +|qq| is a value node with |type(qq)=numeric_type| (assuming that \.{x5} is +numeric, because |qq| represents `\.{x[]}' with no further attributes), +|name_type(qq)=structured_root|, and +|link(qq)=qq1|. (Now pay attention to the next part.) Node |qq1| is +an attribute node representing `\.{x[][]}', which has never yet +occurred; its |type| field is |undefined|, and its |value| field is +undefined. We have |name_type(qq1)=attr|, |attr_loc(qq1)=collective_subscript|, +|parent(qq1)=q1|, and |link(qq1)=qq2|. Since |qq2| represents +`\.{x[]b}', |type(qq2)=unknown_boolean|; also |attr_loc(qq2)=h(b)|, +|parent(qq2)=q1|, |name_type(qq2)=attr|, |link(qq2)=end_attr|. +(Maybe colored lines will help untangle your picture.) + Node |r| is a subscript node with |type| and |value| +representing `\.{x5}'; |name_type(r)=subscr|, |subscript(r)=5.0|, +and |link(r)=r1| is another subscript node. To complete the picture, +see if you can guess what |link(r1)| is; give up? It's~|q1|. +Furthermore |subscript(r1)=20.0|, |name_type(r1)=subscr|, +|type(r1)=structured|, |attr_head(r1)=qqq|, |subscr_head(r1)=qqq1|, +and we finish things off with three more nodes +|qqq|, |qqq1|, and |qqq2| hung onto~|r1|. (Perhaps you should start again +with a larger sheet of paper.) The value of variable \.{x20b} +appears in node~|qqq2|, as you can well imagine. + +If the example in the previous paragraph doesn't make things crystal +clear, a glance at some of the simpler subroutines below will reveal how +things work out in practice. + +The only really unusual thing about these conventions is the use of +collective subscript attributes. The idea is to avoid repeating a lot of +type information when many elements of an array are identical macros +(for which distinct values need not be stored) or when they don't have +all of the possible attributes. Branches of the structure below collective +subscript attributes do not carry actual values except for macro identifiers; +branches of the structure below subscript nodes do not carry significant +information in their collective subscript attributes. + +@d attr_loc_loc(#)==#+2 {where the |attr_loc| and |parent| fields are} +@d attr_loc(#)==info(attr_loc_loc(#)) {hash address of this attribute} +@d parent(#)==link(attr_loc_loc(#)) {pointer to |structured| variable} +@d subscript_loc(#)==#+2 {where the |subscript| field lives} +@d subscript(#)==mem[subscript_loc(#)].sc {subscript of this variable} +@d attr_node_size=3 {the number of words in an attribute node} +@d subscr_node_size=3 {the number of words in a subscript node} +@d collective_subscript=0 {code for the attribute `\.{[]}'} + +@<Initialize table...@>= +attr_loc(end_attr):=hash_end+1; parent(end_attr):=null; + +@ Variables of type \&{pair} will have values that point to four-word +nodes containing two numeric values. The first of these values has +|name_type=x_part_sector| and the second has |name_type=y_part_sector|; +the |link| in the first points back to the node whose |value| points +to this four-word node. + +Variables of type \&{transform} are similar, but in this case their +|value| points to a 12-word node containing six values, identified by +|x_part_sector|, |y_part_sector|, |xx_part_sector|, |xy_part_sector|, +|yx_part_sector|, and |yy_part_sector|. +Finally, variables of type \&{color} have three values in six words +identified by |red_part_sector|, |green_part_sector|, and |blue_part_sector|. + +When an entire structured variable is saved, the |root| indication +is temporarily replaced by |saved_root|. + +Some variables have no name; they just are used for temporary storage +while expressions are being evaluated. We call them {\sl capsules}. + +@d x_part_loc(#)==# {where the \&{xpart} is found in a pair or transform node} +@d y_part_loc(#)==#+2 {where the \&{ypart} is found in a pair or transform node} +@d xx_part_loc(#)==#+4 {where the \&{xxpart} is found in a transform node} +@d xy_part_loc(#)==#+6 {where the \&{xypart} is found in a transform node} +@d yx_part_loc(#)==#+8 {where the \&{yxpart} is found in a transform node} +@d yy_part_loc(#)==#+10 {where the \&{yypart} is found in a transform node} +@d red_part_loc(#)==# {where the \&{redpart} is found in a color node} +@d green_part_loc(#)==#+2 {where the \&{greenpart} is found in a color node} +@d blue_part_loc(#)==#+4 {where the \&{bluepart} is found in a color node} +@# +@d pair_node_size=4 {the number of words in a pair node} +@d transform_node_size=12 {the number of words in a transform node} +@d color_node_size=6 {the number of words in a color node} + +@<Glob...@>= +@!big_node_size:array[transform_type..pair_type] of small_number; +@!sector0:array[transform_type..pair_type] of small_number; +@!sector_offset:array[x_part_sector..blue_part_sector] of small_number; + +@ The |sector0| array gives for each big node type, |name_type| values +for its first subfield; the |sector_offset| array gives for each +|name_type| value, the offset from the first subfield in words; +and the |big_node_size| array gives the size in words for each type of +big node. + +@<Set init...@>= +big_node_size[transform_type]:=transform_node_size; +big_node_size[pair_type]:=pair_node_size; +big_node_size[color_type]:=color_node_size; +sector0[transform_type]:=x_part_sector; +sector0[pair_type]:=x_part_sector; +sector0[color_type]:=red_part_sector; +for k:=x_part_sector to yy_part_sector do + sector_offset[k]:=2*(k-x_part_sector); +for k:=red_part_sector to blue_part_sector do + sector_offset[k]:=2*(k-red_part_sector); + +@ If |type(p)=pair_type| or |transform_type| and if |value(p)=null|, the +procedure call |init_big_node(p)| will allocate a pair or transform node +for~|p|. The individual parts of such nodes are initially of type +|independent|. + +@p procedure init_big_node(@!p:pointer); +var @!q:pointer; {the new node} +@!s:small_number; {its size} +begin s:=big_node_size[type(p)]; q:=get_node(s); +repeat s:=s-2; @<Make variable |q+s| newly independent@>; +name_type(q+s):=halfp(s)+sector0[type(p)]; link(q+s):=null; +until s=0; +link(q):=p; value(p):=q; +end; + +@ The |id_transform| function creates a capsule for the +identity transformation. + +@p function id_transform:pointer; +var @!p,@!q,@!r:pointer; {list manipulation registers} +begin p:=get_node(value_node_size); type(p):=transform_type; +name_type(p):=capsule; value(p):=null; init_big_node(p); q:=value(p); +r:=q+transform_node_size; +repeat r:=r-2; +type(r):=known; value(r):=0; +until r=q; +value(xx_part_loc(q)):=unity; value(yy_part_loc(q)):=unity; +id_transform:=p; +end; + +@ Tokens are of type |tag_token| when they first appear, but they point +to |null| until they are first used as the root of a variable. +The following subroutine establishes the root node on such grand occasions. + +@p procedure new_root(@!x:pointer); +var @!p:pointer; {the new node} +begin p:=get_node(value_node_size); type(p):=undefined; name_type(p):=root; +link(p):=x; equiv(x):=p; +end; + +@ These conventions for variable representation are illustrated by the +|print_variable_name| routine, which displays the full name of a +variable given only a pointer to its two-word value packet. + +@p procedure print_variable_name(@!p:pointer); +label found,exit; +var @!q:pointer; {a token list that will name the variable's suffix} +@!r:pointer; {temporary for token list creation} +begin while name_type(p)>=x_part_sector do + @<Preface the output with a part specifier; |return| in the + case of a capsule@>; +q:=null; +while name_type(p)>saved_root do + @<Ascend one level, pushing a token onto list |q| + and replacing |p| by its parent@>; +r:=get_avail; info(r):=link(p); link(r):=q; +if name_type(p)=saved_root then print("(SAVED)"); +@.SAVED@> +show_token_list(r,null,el_gordo,tally); flush_token_list(r); +exit:end; + +@ @<Ascend one level, pushing a token onto list |q|...@>= +begin if name_type(p)=subscr then + begin r:=new_num_tok(subscript(p)); + repeat p:=link(p); + until name_type(p)=attr; + end +else if name_type(p)=structured_root then + begin p:=link(p); goto found; + end +else begin if name_type(p)<>attr then confusion("var"); +@:this can't happen var}{\quad var@> + r:=get_avail; info(r):=attr_loc(p); + end; +link(r):=q; q:=r; +found: p:=parent(p); +end + +@ @<Preface the output with a part specifier...@>= +begin case name_type(p) of +x_part_sector: print_char("x"); +y_part_sector: print_char("y"); +xx_part_sector: print("xx"); +xy_part_sector: print("xy"); +yx_part_sector: print("yx"); +yy_part_sector: print("yy"); +red_part_sector: print("red"); +green_part_sector: print("green"); +blue_part_sector: print("blue"); +capsule: begin print("%CAPSULE"); print_int(p-null); return; +@.CAPSULE@> + end; +end; {there are no other cases} +print("part "); p:=link(p-sector_offset[name_type(p)]); +end + +@ The |interesting| function returns |true| if a given variable is not +in a capsule, or if the user wants to trace capsules. + +@p function interesting(@!p:pointer):boolean; +var @!t:small_number; {a |name_type|} +begin if internal[tracing_capsules]>0 then interesting:=true +else begin t:=name_type(p); + if t>=x_part_sector then if t<>capsule then + t:=name_type(link(p-sector_offset[t])); + interesting:=(t<>capsule); + end; +end; + +@ Now here is a subroutine that converts an unstructured type into an +equivalent structured type, by inserting a |structured| node that is +capable of growing. This operation is done only when |name_type(p)=root|, +|subscr|, or |attr|. + +The procedure returns a pointer to the new node that has taken node~|p|'s +place in the structure. Node~|p| itself does not move, nor are its +|value| or |type| fields changed in any way. + +@p function new_structure(@!p:pointer):pointer; +var @!q,@!r:pointer; {list manipulation registers} +begin case name_type(p) of +root: begin q:=link(p); r:=get_node(value_node_size); equiv(q):=r; + end; +subscr: @<Link a new subscript node |r| in place of node |p|@>; +attr: @<Link a new attribute node |r| in place of node |p|@>; +othercases confusion("struct") +@:this can't happen struct}{\quad struct@> +endcases;@/ +link(r):=link(p); type(r):=structured; name_type(r):=name_type(p); +attr_head(r):=p; name_type(p):=structured_root;@/ +q:=get_node(attr_node_size); link(p):=q; subscr_head(r):=q; +parent(q):=r; type(q):=undefined; name_type(q):=attr; link(q):=end_attr; +attr_loc(q):=collective_subscript; new_structure:=r; +end; + +@ @<Link a new subscript node |r| in place of node |p|@>= +begin q:=p; +repeat q:=link(q); +until name_type(q)=attr; +q:=parent(q); r:=subscr_head_loc(q); {|link(r)=subscr_head(q)|} +repeat q:=r; r:=link(r); +until r=p; +r:=get_node(subscr_node_size); +link(q):=r; subscript(r):=subscript(p); +end + +@ If the attribute is |collective_subscript|, there are two pointers to +node~|p|, so we must change both of them. + +@<Link a new attribute node |r| in place of node |p|@>= +begin q:=parent(p); r:=attr_head(q); +repeat q:=r; r:=link(r); +until r=p; +r:=get_node(attr_node_size); link(q):=r;@/ +mem[attr_loc_loc(r)]:=mem[attr_loc_loc(p)]; {copy |attr_loc| and |parent|} +if attr_loc(p)=collective_subscript then + begin q:=subscr_head_loc(parent(p)); + while link(q)<>p do q:=link(q); + link(q):=r; + end; +end + +@ The |find_variable| routine is given a pointer~|t| to a nonempty token +list of suffixes; it returns a pointer to the corresponding two-word +value. For example, if |t| points to token \.x followed by a numeric +token containing the value~7, |find_variable| finds where the value of +\.{x7} is stored in memory. This may seem a simple task, and it +usually is, except when \.{x7} has never been referenced before. +Indeed, \.x may never have even been subscripted before; complexities +arise with respect to updating the collective subscript information. + +If a macro type is detected anywhere along path~|t|, or if the first +item on |t| isn't a |tag_token|, the value |null| is returned. +Otherwise |p| will be a non-null pointer to a node such that +|undefined<type(p)<structured|. + +@d abort_find==begin find_variable:=null; return;@+end + +@p function find_variable(@!t:pointer):pointer; +label exit; +var @!p,@!q,@!r,@!s:pointer; {nodes in the ``value'' line} +@!pp,@!qq,@!rr,@!ss:pointer; {nodes in the ``collective'' line} +@!n:integer; {subscript or attribute} +@!save_word:memory_word; {temporary storage for a word of |mem|} +@^inner loop@> +begin p:=info(t); t:=link(t); +if eq_type(p) mod outer_tag<>tag_token then abort_find; +if equiv(p)=null then new_root(p); +p:=equiv(p); pp:=p; +while t<>null do + begin @<Make sure that both nodes |p| and |pp| are of |structured| type@>; + if t<hi_mem_min then + @<Descend one level for the subscript |value(t)|@> + else @<Descend one level for the attribute |info(t)|@>; + t:=link(t); + end; +if type(pp)>=structured then + if type(pp)=structured then pp:=attr_head(pp)@+else abort_find; +if type(p)=structured then p:=attr_head(p); +if type(p)=undefined then + begin if type(pp)=undefined then + begin type(pp):=numeric_type; value(pp):=null; + end; + type(p):=type(pp); value(p):=null; + end; +find_variable:=p; +exit:end; + +@ Although |pp| and |p| begin together, they diverge when a subscript occurs; +|pp|~stays in the collective line while |p|~goes through actual subscript +values. + +@<Make sure that both nodes |p| and |pp|...@>= +if type(pp)<>structured then + begin if type(pp)>structured then abort_find; + ss:=new_structure(pp); + if p=pp then p:=ss; + pp:=ss; + end; {now |type(pp)=structured|} +if type(p)<>structured then {it cannot be |>structured|} + p:=new_structure(p) {now |type(p)=structured|} + +@ We want this part of the program to be reasonably fast, in case there are +@^inner loop@> +lots of subscripts at the same level of the data structure. Therefore +we store an ``infinite'' value in the word that appears at the end of the +subscript list, even though that word isn't part of a subscript node. + +@<Descend one level for the subscript |value(t)|@>= +begin n:=value(t); +pp:=link(attr_head(pp)); {now |attr_loc(pp)=collective_subscript|} +q:=link(attr_head(p)); save_word:=mem[subscript_loc(q)]; +subscript(q):=el_gordo; s:=subscr_head_loc(p); {|link(s)=subscr_head(p)|} +repeat r:=s; s:=link(s); +until n<=subscript(s); +if n=subscript(s) then p:=s +else begin p:=get_node(subscr_node_size); link(r):=p; link(p):=s; + subscript(p):=n; name_type(p):=subscr; type(p):=undefined; + end; +mem[subscript_loc(q)]:=save_word; +end + +@ @<Descend one level for the attribute |info(t)|@>= +begin n:=info(t); +ss:=attr_head(pp); +repeat rr:=ss; ss:=link(ss); +until n<=attr_loc(ss); +if n<attr_loc(ss) then + begin qq:=get_node(attr_node_size); link(rr):=qq; link(qq):=ss; + attr_loc(qq):=n; name_type(qq):=attr; type(qq):=undefined; + parent(qq):=pp; ss:=qq; + end; +if p=pp then + begin p:=ss; pp:=ss; + end +else begin pp:=ss; s:=attr_head(p); + repeat r:=s; s:=link(s); + until n<=attr_loc(s); + if n=attr_loc(s) then p:=s + else begin q:=get_node(attr_node_size); link(r):=q; link(q):=s; + attr_loc(q):=n; name_type(q):=attr; type(q):=undefined; + parent(q):=p; p:=q; + end; + end; +end + +@ Variables lose their former values when they appear in a type declaration, +or when they are defined to be macros or \&{let} equal to something else. +A subroutine will be defined later that recycles the storage associated +with any particular |type| or |value|; our goal now is to study a higher +level process called |flush_variable|, which selectively frees parts of a +variable structure. + +This routine has some complexity because of examples such as +`\hbox{\tt numeric x[]a[]b}' +which recycles all variables of the form \.{x[i]a[j]b} (and no others), while +`\hbox{\tt vardef x[]a[]=...}' +discards all variables of the form \.{x[i]a[j]} followed by an arbitrary +suffix, except for the collective node \.{x[]a[]} itself. The obvious way +to handle such examples is to use recursion; so that's what we~do. +@^recursion@> + +Parameter |p| points to the root information of the variable; +parameter |t| points to a list of one-word nodes that represent +suffixes, with |info=collective_subscript| for subscripts. + +@p @t\4@>@<Declare subroutines for printing expressions@>@;@/ +@t\4@>@<Declare basic dependency-list subroutines@>@; +@t\4@>@<Declare the recycling subroutines@>@; +@t\4@>@<Declare the procedure called |flush_cur_exp|@>@; +@t\4@>@<Declare the procedure called |flush_below_variable|@>@; +procedure flush_variable(@!p,@!t:pointer;@!discard_suffixes:boolean); +label exit; +var @!q,@!r:pointer; {list manipulation} +@!n:halfword; {attribute to match} +begin while t<>null do + begin if type(p)<>structured then return; + n:=info(t); t:=link(t); + if n=collective_subscript then + begin r:=subscr_head_loc(p); q:=link(r); {|q=subscr_head(p)|} + while name_type(q)=subscr do + begin flush_variable(q,t,discard_suffixes); + if t=null then + if type(q)=structured then r:=q + else begin link(r):=link(q); free_node(q,subscr_node_size); + end + else r:=q; + q:=link(r); + end; + end; + p:=attr_head(p); + repeat r:=p; p:=link(p); + until attr_loc(p)>=n; + if attr_loc(p)<>n then return; + end; +if discard_suffixes then flush_below_variable(p) +else begin if type(p)=structured then p:=attr_head(p); + recycle_value(p); + end; +exit:end; + +@ The next procedure is simpler; it wipes out everything but |p| itself, +which becomes undefined. + +@<Declare the procedure called |flush_below_variable|@>= +procedure flush_below_variable(@!p:pointer); +var @!q,@!r:pointer; {list manipulation registers} +begin if type(p)<>structured then + recycle_value(p) {this sets |type(p)=undefined|} +else begin q:=subscr_head(p); + while name_type(q)=subscr do + begin flush_below_variable(q); r:=q; q:=link(q); + free_node(r,subscr_node_size); + end; + r:=attr_head(p); q:=link(r); recycle_value(r); + if name_type(p)<=saved_root then free_node(r,value_node_size) + else free_node(r,subscr_node_size); + {we assume that |subscr_node_size=attr_node_size|} + repeat flush_below_variable(q); r:=q; q:=link(q); free_node(r,attr_node_size); + until q=end_attr; + type(p):=undefined; + end; +end; + +@ Just before assigning a new value to a variable, we will recycle the +old value and make the old value undefined. The |und_type| routine +determines what type of undefined value should be given, based on +the current type before recycling. + +@p function und_type(@!p:pointer):small_number; +begin case type(p) of +undefined,vacuous:und_type:=undefined; +boolean_type,unknown_boolean:und_type:=unknown_boolean; +string_type,unknown_string:und_type:=unknown_string; +pen_type,unknown_pen:und_type:=unknown_pen; +path_type,unknown_path:und_type:=unknown_path; +picture_type,unknown_picture:und_type:=unknown_picture; +transform_type,color_type,pair_type,numeric_type:und_type:=type(p); +known,dependent,proto_dependent,independent:und_type:=numeric_type; +end; {there are no other cases} +end; + +@ The |clear_symbol| routine is used when we want to redefine the equivalent +of a symbolic token. It must remove any variable structure or macro +definition that is currently attached to that symbol. If the |saving| +parameter is true, a subsidiary structure is saved instead of destroyed. + +@p procedure clear_symbol(@!p:pointer;@!saving:boolean); +var @!q:pointer; {|equiv(p)|} +begin q:=equiv(p); +case eq_type(p) mod outer_tag of +defined_macro,secondary_primary_macro,tertiary_secondary_macro, + expression_tertiary_macro: if not saving then delete_mac_ref(q); +tag_token:if q<>null then + if saving then name_type(q):=saved_root + else begin flush_below_variable(q); free_node(q,value_node_size); + end; +othercases do_nothing +endcases;@/ +eqtb[p]:=eqtb[frozen_undefined]; +end; + +@* \[16] Saving and restoring equivalents. +The nested structure given by \&{begingroup} and \&{endgroup} +allows |eqtb| entries to be saved and restored, so that temporary changes +can be made without difficulty. When the user requests a current value to +be saved, \MP\ puts that value into its ``save stack.'' An appearance of +\&{endgroup} ultimately causes the old values to be removed from the save +stack and put back in their former places. + +The save stack is a linked list containing three kinds of entries, +distinguished by their |info| fields. If |p| points to a saved item, +then + +\smallskip\hang +|info(p)=0| stands for a group boundary; each \&{begingroup} contributes +such an item to the save stack and each \&{endgroup} cuts back the stack +until the most recent such entry has been removed. + +\smallskip\hang +|info(p)=q|, where |1<=q<=hash_end|, means that |mem[p+1]| holds the former +contents of |eqtb[q]|. Such save stack entries are generated by \&{save} +commands or suitable \&{interim} commands. + +\smallskip\hang +|info(p)=hash_end+q|, where |q>0|, means that |value(p)| is a |scaled| +integer to be restored to internal parameter number~|q|. Such entries +are generated by \&{interim} commands. + +\smallskip\noindent +The global variable |save_ptr| points to the top item on the save stack. + +@d save_node_size=2 {number of words per non-boundary save-stack node} +@d saved_equiv(#)==mem[#+1].hh {where an |eqtb| entry gets saved} +@d save_boundary_item(#)==begin #:=get_avail; info(#):=0; + link(#):=save_ptr; save_ptr:=#; + end + +@<Glob...@>=@!save_ptr:pointer; {the most recently saved item} + +@ @<Set init...@>=save_ptr:=null; + +@ The |save_variable| routine is given a hash address |q|; it salts this +address in the save stack, together with its current equivalent, +then makes token~|q| behave as though it were brand new. + +Nothing is stacked when |save_ptr=null|, however; there's no way to remove +things from the stack when the program is not inside a group, so there's +no point in wasting the space. + +@p procedure save_variable(@!q:pointer); +var @!p:pointer; {temporary register} +begin if save_ptr<>null then + begin p:=get_node(save_node_size); info(p):=q; link(p):=save_ptr; + saved_equiv(p):=eqtb[q]; save_ptr:=p; + end; +clear_symbol(q,(save_ptr<>null)); +end; + +@ Similarly, |save_internal| is given the location |q| of an internal +quantity like |tracing_pens|. It creates a save stack entry of the +third kind. + +@p procedure save_internal(@!q:halfword); +var @!p:pointer; {new item for the save stack} +begin if save_ptr<>null then + begin p:=get_node(save_node_size); info(p):=hash_end+q; + link(p):=save_ptr; value(p):=internal[q]; save_ptr:=p; + end; +end; + +@ At the end of a group, the |unsave| routine restores all of the saved +equivalents in reverse order. This routine will be called only when there +is at least one boundary item on the save stack. + +@p procedure unsave; +var @!q:pointer; {index to saved item} +@!p:pointer; {temporary register} +begin while info(save_ptr)<>0 do + begin q:=info(save_ptr); + if q>hash_end then + begin if internal[tracing_restores]>0 then + begin begin_diagnostic; print_nl("{restoring "); + print(int_name[q-(hash_end)]); print_char("="); + print_scaled(value(save_ptr)); print_char("}"); + end_diagnostic(false); + end; + internal[q-(hash_end)]:=value(save_ptr); + end + else begin if internal[tracing_restores]>0 then + begin begin_diagnostic; print_nl("{restoring "); + print(text(q)); print_char("}"); + end_diagnostic(false); + end; + clear_symbol(q,false); + eqtb[q]:=saved_equiv(save_ptr); + if eq_type(q) mod outer_tag=tag_token then + begin p:=equiv(q); + if p<>null then name_type(p):=root; + end; + end; + p:=link(save_ptr); free_node(save_ptr,save_node_size); save_ptr:=p; + end; +p:=link(save_ptr); free_avail(save_ptr); save_ptr:=p; +end; + +@* \[17] Data structures for paths. +When a \MP\ user specifies a path, \MP\ will create a list of knots +and control points for the associated cubic spline curves. If the +knots are $z_0$, $z_1$, \dots, $z_n$, there are control points +$z_k^+$ and $z_{k+1}^-$ such that the cubic splines between knots +$z_k$ and $z_{k+1}$ are defined by B\'ezier's formula +@:Bezier}{B\'ezier, Pierre Etienne@> +$$\eqalign{z(t)&=B(z_k,z_k^+,z_{k+1}^-,z_{k+1};t)\cr +&=(1-t)^3z_k+3(1-t)^2tz_k^++3(1-t)t^2z_{k+1}^-+t^3z_{k+1}\cr}$$ +for |0<=t<=1|. + +There is a 8-word node for each knot $z_k$, containing one word of +control information and six words for the |x| and |y| coordinates of +$z_k^-$ and $z_k$ and~$z_k^+$. The control information appears in the +|left_type| and |right_type| fields, which each occupy a quarter of +the first word in the node; they specify properties of the curve as it +enters and leaves the knot. There's also a halfword |link| field, +which points to the following knot, and a final supplementary word (of +which only a quarter is used). + +If the path is a closed contour, knots 0 and |n| are identical; +i.e., the |link| in knot |n-1| points to knot~0. But if the path +is not closed, the |left_type| of knot~0 and the |right_type| of knot~|n| +are equal to |endpoint|. In the latter case the |link| in knot~|n| points +to knot~0, and the control points $z_0^-$ and $z_n^+$ are not used. + +@d left_type(#) == mem[#].hh.b0 {characterizes the path entering this knot} +@d right_type(#) == mem[#].hh.b1 {characterizes the path leaving this knot} +@d endpoint=0 {|left_type| at path beginning and |right_type| at path end} +@d x_coord(#) == mem[#+1].sc {the |x| coordinate of this knot} +@d y_coord(#) == mem[#+2].sc {the |y| coordinate of this knot} +@d left_x(#) == mem[#+3].sc {the |x| coordinate of previous control point} +@d left_y(#) == mem[#+4].sc {the |y| coordinate of previous control point} +@d right_x(#) == mem[#+5].sc {the |x| coordinate of next control point} +@d right_y(#) == mem[#+6].sc {the |y| coordinate of next control point} +@d x_loc(#) == #+1 {where the |x| coordinate is stored in a knot} +@d y_loc(#) == #+2 {where the |y| coordinate is stored in a knot} +@d knot_coord(#) == mem[#].sc {|x| or |y| coordinate given |x_loc| or |y_loc|} +@d left_coord(#) == mem[#+2].sc + {coordinate of previous control point given |x_loc| or |y_loc|} +@d right_coord(#) == mem[#+4].sc + {coordinate of next control point given |x_loc| or |y_loc|} +@d knot_node_size=8 {number of words in a knot node} + +@ Before the B\'ezier control points have been calculated, the memory +space they will ultimately occupy is taken up by information that can be +used to compute them. There are four cases: + +\yskip +\textindent{$\bullet$} If |right_type=open|, the curve should leave +the knot in the same direction it entered; \MP\ will figure out a +suitable direction. + +\yskip +\textindent{$\bullet$} If |right_type=curl|, the curve should leave the +knot in a direction depending on the angle at which it enters the next +knot and on the curl parameter stored in |right_curl|. + +\yskip +\textindent{$\bullet$} If |right_type=given|, the curve should leave the +knot in a nonzero direction stored as an |angle| in |right_given|. + +\yskip +\textindent{$\bullet$} If |right_type=explicit|, the B\'ezier control +point for leaving this knot has already been computed; it is in the +|right_x| and |right_y| fields. + +\yskip\noindent +The rules for |left_type| are similar, but they refer to the curve entering +the knot, and to \\{left} fields instead of \\{right} fields. + +Non-|explicit| control points will be chosen based on ``tension'' parameters +in the |left_tension| and |right_tension| fields. The +`\&{atleast}' option is represented by negative tension values. +@!@:at_least_}{\&{atleast} primitive@> + +For example, the \MP\ path specification +$$\.{z0..z1..tension atleast 1..\{curl 2\}z2..z3\{-1,-2\}..tension + 3 and 4..p},$$ +where \.p is the path `\.{z4..controls z45 and z54..z5}', will be represented +by the six knots +\def\lodash{\hbox to 1.1em{\thinspace\hrulefill\thinspace}} +$$\vbox{\halign{#\hfil&&\qquad#\hfil\cr +|left_type|&\\{left} info&|x_coord,y_coord|&|right_type|&\\{right} info\cr +\noalign{\yskip} +|endpoint|&\lodash$,\,$\lodash&$x_0,y_0$&|curl|&$1.0,1.0$\cr +|open|&\lodash$,1.0$&$x_1,y_1$&|open|&\lodash$,-1.0$\cr +|curl|&$2.0,-1.0$&$x_2,y_2$&|curl|&$2.0,1.0$\cr +|given|&$d,1.0$&$x_3,y_3$&|given|&$d,3.0$\cr +|open|&\lodash$,4.0$&$x_4,y_4$&|explicit|&$x_{45},y_{45}$\cr +|explicit|&$x_{54},y_{54}$&$x_5,y_5$&|endpoint|&\lodash$,\,$\lodash\cr}}$$ +Here |d| is the |angle| obtained by calling |n_arg(-unity,-two)|. +Of course, this example is more complicated than anything a normal user +would ever write. + +These types must satisfy certain restrictions because of the form of \MP's +path syntax: +(i)~|open| type never appears in the same node together with |endpoint|, +|given|, or |curl|. +(ii)~The |right_type| of a node is |explicit| if and only if the +|left_type| of the following node is |explicit|. +(iii)~|endpoint| types occur only at the ends, as mentioned above. + +@d left_curl==left_x {curl information when entering this knot} +@d left_given==left_x {given direction when entering this knot} +@d left_tension==left_y {tension information when entering this knot} +@d right_curl==right_x {curl information when leaving this knot} +@d right_given==right_x {given direction when leaving this knot} +@d right_tension==right_y {tension information when leaving this knot} +@d explicit=1 {|left_type| or |right_type| when control points are known} +@d given=2 {|left_type| or |right_type| when a direction is given} +@d curl=3 {|left_type| or |right_type| when a curl is desired} +@d open=4 {|left_type| or |right_type| when \MP\ should choose the direction} + +@ Knots can be user-supplied, or they can be created by program code, +like the |split_cubic| function, or |copy_path|. The distinction is +needed for the cleanup routine that runs after |split_cubic|, because +it should only delete knots it has previously inserted, and never +anything that was user-supplied. In order to be able to differentiate +one knot from another, we will set |originator(p):=metapost_user| when +it appeared in the actual metapost program, and +|originator(p):=program_code| in all other cases. + +@d originator(#) == mem[#+7].hh.b0 {the creator of this knot} +@d program_code=0 {not created by a user} +@d metapost_user=1 {created by a user} + +@ Here is a routine that prints a given knot list +in symbolic form. It illustrates the conventions discussed above, +and checks for anomalies that might arise while \MP\ is being debugged. + +@<Declare subroutines for printing expressions@>= +procedure pr_path(@!h:pointer); +label done,done1; +var @!p,@!q:pointer; {for list traversal} +begin p:=h; +repeat q:=link(p); +if (p=null)or(q=null) then + begin print_nl("???"); goto done; {this won't happen} +@.???@> + end; +@<Print information for adjacent knots |p| and |q|@>; +p:=q; +if (p<>h)or(left_type(h)<>endpoint) then + @<Print two dots, followed by |given| or |curl| if present@>; +until p=h; +if left_type(h)<>endpoint then print("cycle"); +done:end; + +@ @<Print information for adjacent knots...@>= +print_two(x_coord(p),y_coord(p)); +case right_type(p) of +endpoint: begin if left_type(p)=open then print("{open?}"); {can't happen} +@.open?@> + if (left_type(q)<>endpoint)or(q<>h) then q:=null; {force an error} + goto done1; + end; +explicit: @<Print control points between |p| and |q|, then |goto done1|@>; +open: @<Print information for a curve that begins |open|@>; +curl,given: @<Print information for a curve that begins |curl| or |given|@>; +othercases print("???") {can't happen} +@.???@> +endcases;@/ +if left_type(q)<=explicit then print("..control?") {can't happen} +@.control?@> +else if (right_tension(p)<>unity)or(left_tension(q)<>unity) then + @<Print tension between |p| and |q|@>; +done1: + +@ Since |n_sin_cos| produces |fraction| results, which we will print as if they +were |scaled|, the magnitude of a |given| direction vector will be~4096. + +@<Print two dots...@>= +begin print_nl(" .."); +if left_type(p)=given then + begin n_sin_cos(left_given(p)); print_char("{"); + print_scaled(n_cos); print_char(","); + print_scaled(n_sin); print_char("}"); + end +else if left_type(p)=curl then + begin print("{curl "); print_scaled(left_curl(p)); print_char("}"); + end; +end + +@ @<Print tension between |p| and |q|@>= +begin print("..tension "); +if right_tension(p)<0 then print("atleast"); +print_scaled(abs(right_tension(p))); +if right_tension(p)<>left_tension(q) then + begin print(" and "); + if left_tension(q)<0 then print("atleast"); + print_scaled(abs(left_tension(q))); + end; +end + +@ @<Print control points between |p| and |q|, then |goto done1|@>= +begin print("..controls "); print_two(right_x(p),right_y(p)); print(" and "); +if left_type(q)<>explicit then print("??") {can't happen} +@.??@> +else print_two(left_x(q),left_y(q)); +goto done1; +end + +@ @<Print information for a curve that begins |open|@>= +if (left_type(p)<>explicit)and(left_type(p)<>open) then + print("{open?}") {can't happen} +@.open?@> + +@ A curl of 1 is shown explicitly, so that the user sees clearly that +\MP's default curl is present. + +The code here uses the fact that |left_curl==left_given| and +|right_curl==right_given|. + +@<Print information for a curve that begins |curl|...@>= +begin if left_type(p)=open then print("??"); {can't happen} +@.??@> +if right_type(p)=curl then + begin print("{curl "); print_scaled(right_curl(p)); + end +else begin n_sin_cos(right_given(p)); print_char("{"); + print_scaled(n_cos); print_char(","); print_scaled(n_sin); + end; +print_char("}"); +end + +@ It is convenient to have another version of |pr_path| that prints the path +as a diagnostic message. + +@<Declare subroutines for printing expressions@>= +procedure print_path(@!h:pointer;@!s:str_number;@!nuline:boolean); +begin print_diagnostic("Path",s,nuline); print_ln; +@.Path at line...@> +pr_path(h); +end_diagnostic(true); +end; + +@ If we want to duplicate a knot node, we can say |copy_knot|: + +@p function copy_knot(@!p:pointer):pointer; +var @!q:pointer; {the copy} +@!k:0..knot_node_size-1; {runs through the words of a knot node} +begin q:=get_node(knot_node_size); +for k:=0 to knot_node_size-1 do mem[q+k]:=mem[p+k]; +originator(q):=originator(p); +copy_knot:=q; +end; + +@ The |copy_path| routine makes a clone of a given path. + +@p function copy_path(@!p:pointer):pointer; +var @!q,@!pp,@!qq:pointer; {for list manipulation} +begin q:=copy_knot(p); +qq:=q; pp:=link(p); +while pp<>p do + begin link(qq):=copy_knot(pp);@/ + qq:=link(qq); + pp:=link(pp); + end; +link(qq):=q; +copy_path:=q; +end; + +@ Similarly, there's a way to copy the {\sl reverse\/} of a path. This procedure +returns a pointer to the first node of the copy, if the path is a cycle, +but to the final node of a non-cyclic copy. The global +variable |path_tail| will point to the final node of the original path; +this trick makes it easier to implement `\&{doublepath}'. + +All node types are assumed to be |endpoint| or |explicit| only. + +@p function htap_ypoc(@!p:pointer):pointer; +label exit; +var @!q,@!pp,@!qq,@!rr:pointer; {for list manipulation} +begin q:=get_node(knot_node_size); {this will correspond to |p|} +qq:=q; pp:=p; +loop@+ begin right_type(qq):=left_type(pp); left_type(qq):=right_type(pp);@/ + x_coord(qq):=x_coord(pp); y_coord(qq):=y_coord(pp);@/ + right_x(qq):=left_x(pp); right_y(qq):=left_y(pp);@/ + left_x(qq):=right_x(pp); left_y(qq):=right_y(pp);@/ + originator(qq):=originator(pp);@/ + if link(pp)=p then + begin link(q):=qq; path_tail:=pp; htap_ypoc:=q; return; + end; + rr:=get_node(knot_node_size); link(rr):=qq; qq:=rr; pp:=link(pp); + end; +exit:end; + +@ @<Glob...@>= +@!path_tail:pointer; {the node that links to the beginning of a path} + +@ When a cyclic list of knot nodes is no longer needed, it can be recycled by +calling the following subroutine. + +@<Declare the recycling subroutines@>= +procedure toss_knot_list(@!p:pointer); +var @!q:pointer; {the node being freed} +@!r:pointer; {the next node} +begin q:=p; +repeat r:=link(q); free_node(q,knot_node_size); q:=r; +until q=p; +end; + +@* \[18] Choosing control points. +Now we must actually delve into one of \MP's more difficult routines, +the |make_choices| procedure that chooses angles and control points for +the splines of a curve when the user has not specified them explicitly. +The parameter to |make_choices| points to a list of knots and +path information, as described above. + +A path decomposes into independent segments at ``breakpoint'' knots, +which are knots whose left and right angles are both prespecified in +some way (i.e., their |left_type| and |right_type| aren't both open). + +@p @t\4@>@<Declare the procedure called |solve_choices|@>@; +procedure make_choices(@!knots:pointer); +label done; +var @!h:pointer; {the first breakpoint} +@!p,@!q:pointer; {consecutive breakpoints being processed} +@<Other local variables for |make_choices|@>@; +begin check_arith; {make sure that |arith_error=false|} +if internal[tracing_choices]>0 then + print_path(knots,", before choices",true); +@<If consecutive knots are equal, join them explicitly@>; +@<Find the first breakpoint, |h|, on the path; + insert an artificial breakpoint if the path is an unbroken cycle@>; +p:=h; +repeat @<Fill in the control points between |p| and the next breakpoint, + then advance |p| to that breakpoint@>; +until p=h; +if internal[tracing_choices]>0 then + print_path(knots,", after choices",true); +if arith_error then @<Report an unexpected problem during the choice-making@>; +end; + +@ @<Report an unexpected problem during the choice...@>= +begin print_err("Some number got too big"); +@.Some number got too big@> +help2("The path that I just computed is out of range.")@/ + ("So it will probably look funny. Proceed, for a laugh."); +put_get_error; arith_error:=false; +end + +@ Two knots in a row with the same coordinates will always be joined +by an explicit ``curve'' whose control points are identical with the +knots. + +@<If consecutive knots are equal, join them explicitly@>= +p:=knots; +repeat q:=link(p); +if x_coord(p)=x_coord(q) then if y_coord(p)=y_coord(q) then + if right_type(p)>explicit then + begin right_type(p):=explicit; + if left_type(p)=open then + begin left_type(p):=curl; left_curl(p):=unity; + end; + left_type(q):=explicit; + if right_type(q)=open then + begin right_type(q):=curl; right_curl(q):=unity; + end; + right_x(p):=x_coord(p); left_x(q):=x_coord(p);@/ + right_y(p):=y_coord(p); left_y(q):=y_coord(p); + end; +p:=q; +until p=knots + +@ If there are no breakpoints, it is necessary to compute the direction +angles around an entire cycle. In this case the |left_type| of the first +node is temporarily changed to |end_cycle|. + +@d end_cycle=open+1 + +@<Find the first breakpoint, |h|, on the path...@>= +h:=knots; +loop@+ begin if left_type(h)<>open then goto done; + if right_type(h)<>open then goto done; + h:=link(h); + if h=knots then + begin left_type(h):=end_cycle; goto done; + end; + end; +done: + +@ If |right_type(p)<given| and |q=link(p)|, we must have +|right_type(p)=left_type(q)=explicit| or |endpoint|. + +@<Fill in the control points between |p| and the next breakpoint...@>= +q:=link(p); +if right_type(p)>=given then + begin while (left_type(q)=open)and(right_type(q)=open) do q:=link(q); + @<Fill in the control information between + consecutive breakpoints |p| and |q|@>; + end +else if right_type(p)=endpoint then + @<Give reasonable values for the unused control points between |p| and~|q|@>; +p:=q + +@ This step makes it possible to transform an explicitly computed path without +checking the |left_type| and |right_type| fields. + +@<Give reasonable values for the unused control points between |p| and~|q|@>= +begin right_x(p):=x_coord(p); right_y(p):=y_coord(p);@/ +left_x(q):=x_coord(q); left_y(q):=y_coord(q); +end + +@ Before we can go further into the way choices are made, we need to +consider the underlying theory. The basic ideas implemented in |make_choices| +are due to John Hobby, who introduced the notion of ``mock curvature'' +@^Hobby, John Douglas@> +at a knot. Angles are chosen so that they preserve mock curvature when +a knot is passed, and this has been found to produce excellent results. + +It is convenient to introduce some notations that simplify the necessary +formulas. Let $d_{k,k+1}=\vert z\k-z_k\vert$ be the (nonzero) distance +between knots |k| and |k+1|; and let +$${z\k-z_k\over z_k-z_{k-1}}={d_{k,k+1}\over d_{k-1,k}}e^{i\psi_k}$$ +so that a polygonal line from $z_{k-1}$ to $z_k$ to $z\k$ turns left +through an angle of~$\psi_k$. We assume that $\vert\psi_k\vert\L180^\circ$. +The control points for the spline from $z_k$ to $z\k$ will be denoted by +$$\eqalign{z_k^+&=z_k+ + \textstyle{1\over3}\rho_k e^{i\theta_k}(z\k-z_k),\cr + z\k^-&=z\k- + \textstyle{1\over3}\sigma\k e^{-i\phi\k}(z\k-z_k),\cr}$$ +where $\rho_k$ and $\sigma\k$ are nonnegative ``velocity ratios'' at the +beginning and end of the curve, while $\theta_k$ and $\phi\k$ are the +corresponding ``offset angles.'' These angles satisfy the condition +$$\theta_k+\phi_k+\psi_k=0,\eqno(*)$$ +whenever the curve leaves an intermediate knot~|k| in the direction that +it enters. + +@ Let $\alpha_k$ and $\beta\k$ be the reciprocals of the ``tension'' of +the curve at its beginning and ending points. This means that +$\rho_k=\alpha_k f(\theta_k,\phi\k)$ and $\sigma\k=\beta\k f(\phi\k,\theta_k)$, +where $f(\theta,\phi)$ is \MP's standard velocity function defined in +the |velocity| subroutine. The cubic spline $B(z_k^{\phantom+},z_k^+, +z\k^-,z\k^{\phantom+};t)$ +has curvature +@^curvature@> +$${2\sigma\k\sin(\theta_k+\phi\k)-6\sin\theta_k\over\rho_k^2d_{k,k+1}} +\qquad{\rm and}\qquad +{2\rho_k\sin(\theta_k+\phi\k)-6\sin\phi\k\over\sigma\k^2d_{k,k+1}}$$ +at |t=0| and |t=1|, respectively. The mock curvature is the linear +@^mock curvature@> +approximation to this true curvature that arises in the limit for +small $\theta_k$ and~$\phi\k$, if second-order terms are discarded. +The standard velocity function satisfies +$$f(\theta,\phi)=1+O(\theta^2+\theta\phi+\phi^2);$$ +hence the mock curvatures are respectively +$${2\beta\k(\theta_k+\phi\k)-6\theta_k\over\alpha_k^2d_{k,k+1}} +\qquad{\rm and}\qquad +{2\alpha_k(\theta_k+\phi\k)-6\phi\k\over\beta\k^2d_{k,k+1}}.\eqno(**)$$ + +@ The turning angles $\psi_k$ are given, and equation $(*)$ above +determines $\phi_k$ when $\theta_k$ is known, so the task of +angle selection is essentially to choose appropriate values for each +$\theta_k$. When equation~$(*)$ is used to eliminate $\phi$~variables +from $(**)$, we obtain a system of linear equations of the form +$$A_k\theta_{k-1}+(B_k+C_k)\theta_k+D_k\theta\k=-B_k\psi_k-D_k\psi\k,$$ +where +$$A_k={\alpha_{k-1}\over\beta_k^2d_{k-1,k}}, +\qquad B_k={3-\alpha_{k-1}\over\beta_k^2d_{k-1,k}}, +\qquad C_k={3-\beta\k\over\alpha_k^2d_{k,k+1}}, +\qquad D_k={\beta\k\over\alpha_k^2d_{k,k+1}}.$$ +The tensions are always $3\over4$ or more, hence each $\alpha$ and~$\beta$ +will be at most $4\over3$. It follows that $B_k\G{5\over4}A_k$ and +$C_k\G{5\over4}D_k$; hence the equations are diagonally dominant; +hence they have a unique solution. Moreover, in most cases the tensions +are equal to~1, so that $B_k=2A_k$ and $C_k=2D_k$. This makes the +solution numerically stable, and there is an exponential damping +effect: The data at knot $k\pm j$ affects the angle at knot~$k$ by +a factor of~$O(2^{-j})$. + +@ However, we still must consider the angles at the starting and ending +knots of a non-cyclic path. These angles might be given explicitly, or +they might be specified implicitly in terms of an amount of ``curl.'' + +Let's assume that angles need to be determined for a non-cyclic path +starting at $z_0$ and ending at~$z_n$. Then equations of the form +$$A_k\theta_{k-1}+(B_k+C_k)\theta_k+D_k\theta_{k+1}=R_k$$ +have been given for $0<k<n$, and it will be convenient to introduce +equations of the same form for $k=0$ and $k=n$, where +$$A_0=B_0=C_n=D_n=0.$$ +If $\theta_0$ is supposed to have a given value $E_0$, we simply +define $C_0=0$, $D_0=0$, and $R_0=E_0$. Otherwise a curl +parameter, $\gamma_0$, has been specified at~$z_0$; this means +that the mock curvature at $z_0$ should be $\gamma_0$ times the +mock curvature at $z_1$; i.e., +$${2\beta_1(\theta_0+\phi_1)-6\theta_0\over\alpha_0^2d_{01}} +=\gamma_0{2\alpha_0(\theta_0+\phi_1)-6\phi_1\over\beta_1^2d_{01}}.$$ +This equation simplifies to +$$(\alpha_0\chi_0+3-\beta_1)\theta_0+ + \bigl((3-\alpha_0)\chi_0+\beta_1\bigr)\theta_1= + -\bigl((3-\alpha_0)\chi_0+\beta_1\bigr)\psi_1,$$ +where $\chi_0=\alpha_0^2\gamma_0/\beta_1^2$; so we can set $C_0= +\chi_0\alpha_0+3-\beta_1$, $D_0=(3-\alpha_0)\chi_0+\beta_1$, $R_0=-D_0\psi_1$. +It can be shown that $C_0>0$ and $C_0B_1-A_1D_0>0$ when $\gamma_0\G0$, +hence the linear equations remain nonsingular. + +Similar considerations apply at the right end, when the final angle $\phi_n$ +may or may not need to be determined. It is convenient to let $\psi_n=0$, +hence $\theta_n=-\phi_n$. We either have an explicit equation $\theta_n=E_n$, +or we have +$$\bigl((3-\beta_n)\chi_n+\alpha_{n-1}\bigr)\theta_{n-1}+ +(\beta_n\chi_n+3-\alpha_{n-1})\theta_n=0,\qquad + \chi_n={\beta_n^2\gamma_n\over\alpha_{n-1}^2}.$$ + +When |make_choices| chooses angles, it must compute the coefficients of +these linear equations, then solve the equations. To compute the coefficients, +it is necessary to compute arctangents of the given turning angles~$\psi_k$. +When the equations are solved, the chosen directions $\theta_k$ are put +back into the form of control points by essentially computing sines and +cosines. + +@ OK, we are ready to make the hard choices of |make_choices|. +Most of the work is relegated to an auxiliary procedure +called |solve_choices|, which has been introduced to keep +|make_choices| from being extremely long. + +@<Fill in the control information between...@>= +@<Calculate the turning angles $\psi_k$ and the distances $d_{k,k+1}$; + set $n$ to the length of the path@>; +@<Remove |open| types at the breakpoints@>; +solve_choices(p,q,n) + +@ It's convenient to precompute quantities that will be needed several +times later. The values of |delta_x[k]| and |delta_y[k]| will be the +coordinates of $z\k-z_k$, and the magnitude of this vector will be +|delta[k]=@t$d_{k,k+1}$@>|. The path angle $\psi_k$ between $z_k-z_{k-1}$ +and $z\k-z_k$ will be stored in |psi[k]|. + +@<Glob...@>= +@!delta_x,@!delta_y,@!delta:array[0..path_size] of scaled; {knot differences} +@!psi:array[1..path_size] of angle; {turning angles} + +@ @<Other local variables for |make_choices|@>= +@!k,@!n:0..path_size; {current and final knot numbers} +@!s,@!t:pointer; {registers for list traversal} +@!delx,@!dely:scaled; {directions where |open| meets |explicit|} +@!sine,@!cosine:fraction; {trig functions of various angles} + +@ @<Calculate the turning angles...@>= +k:=0; s:=p; n:=path_size; +repeat t:=link(s); +delta_x[k]:=x_coord(t)-x_coord(s); +delta_y[k]:=y_coord(t)-y_coord(s); +delta[k]:=pyth_add(delta_x[k],delta_y[k]); +if k>0 then + begin sine:=make_fraction(delta_y[k-1],delta[k-1]); + cosine:=make_fraction(delta_x[k-1],delta[k-1]); + psi[k]:=n_arg(take_fraction(delta_x[k],cosine)+ + take_fraction(delta_y[k],sine), + take_fraction(delta_y[k],cosine)- + take_fraction(delta_x[k],sine)); + end; +@:MetaPost capacity exceeded path size}{\quad path size@> +incr(k); s:=t; +if k=path_size then overflow("path size",path_size); +if s=q then n:=k; +until (k>=n)and(left_type(s)<>end_cycle); +if k=n then psi[n]:=0@+else psi[k]:=psi[1] + +@ When we get to this point of the code, |right_type(p)| is either +|given| or |curl| or |open|. If it is |open|, we must have +|left_type(p)=end_cycle| or |left_type(p)=explicit|. In the latter +case, the |open| type is converted to |given|; however, if the +velocity coming into this knot is zero, the |open| type is +converted to a |curl|, since we don't know the incoming direction. + +Similarly, |left_type(q)| is either |given| or |curl| or |open| or +|end_cycle|. The |open| possibility is reduced either to |given| or to |curl|. + +@<Remove |open| types at the breakpoints@>= +if left_type(q)=open then + begin delx:=right_x(q)-x_coord(q); dely:=right_y(q)-y_coord(q); + if (delx=0)and(dely=0) then + begin left_type(q):=curl; left_curl(q):=unity; + end + else begin left_type(q):=given; left_given(q):=n_arg(delx,dely); + end; + end; +if (right_type(p)=open)and(left_type(p)=explicit) then + begin delx:=x_coord(p)-left_x(p); dely:=y_coord(p)-left_y(p); + if (delx=0)and(dely=0) then + begin right_type(p):=curl; right_curl(p):=unity; + end + else begin right_type(p):=given; right_given(p):=n_arg(delx,dely); + end; + end + +@ Linear equations need to be solved whenever |n>1|; and also when |n=1| +and exactly one of the breakpoints involves a curl. The simplest case occurs +when |n=1| and there is a curl at both breakpoints; then we simply draw +a straight line. + +But before coding up the simple cases, we might as well face the general case, +since we must deal with it sooner or later, and since the general case +is likely to give some insight into the way simple cases can be handled best. + +When there is no cycle, the linear equations to be solved form a tridiagonal +system, and we can apply the standard technique of Gaussian elimination +to convert that system to a sequence of equations of the form +$$\theta_0+u_0\theta_1=v_0,\quad +\theta_1+u_1\theta_2=v_1,\quad\ldots,\quad +\theta_{n-1}+u_{n-1}\theta_n=v_{n-1},\quad +\theta_n=v_n.$$ +It is possible to do this diagonalization while generating the equations. +Once $\theta_n$ is known, it is easy to determine $\theta_{n-1}$, \dots, +$\theta_1$, $\theta_0$; thus, the equations will be solved. + +The procedure is slightly more complex when there is a cycle, but the +basic idea will be nearly the same. In the cyclic case the right-hand +sides will be $v_k+w_k\theta_0$ instead of simply $v_k$, and we will start +the process off with $u_0=v_0=0$, $w_0=1$. The final equation will be not +$\theta_n=v_n$ but $\theta_n+u_n\theta_1=v_n+w_n\theta_0$; an appropriate +ending routine will take account of the fact that $\theta_n=\theta_0$ and +eliminate the $w$'s from the system, after which the solution can be +obtained as before. + +When $u_k$, $v_k$, and $w_k$ are being computed, the three pointer +variables |r|, |s|,~|t| will point respectively to knots |k-1|, |k|, +and~|k+1|. The $u$'s and $w$'s are scaled by $2^{28}$, i.e., they are +of type |fraction|; the $\theta$'s and $v$'s are of type |angle|. + +@<Glob...@>= +@!theta:array[0..path_size] of angle; {values of $\theta_k$} +@!uu:array[0..path_size] of fraction; {values of $u_k$} +@!vv:array[0..path_size] of angle; {values of $v_k$} +@!ww:array[0..path_size] of fraction; {values of $w_k$} + +@ Our immediate problem is to get the ball rolling by setting up the +first equation or by realizing that no equations are needed, and to fit +this initialization into a framework suitable for the overall computation. + +@<Declare the procedure called |solve_choices|@>= +@t\4@>@<Declare subroutines needed by |solve_choices|@>@; +procedure solve_choices(@!p,@!q:pointer;@!n:halfword); +label found,exit; +var @!k:0..path_size; {current knot number} +@!r,@!s,@!t:pointer; {registers for list traversal} +@<Other local variables for |solve_choices|@>@; +begin k:=0; s:=p; +loop@+ begin t:=link(s); + if k=0 then @<Get the linear equations started; or |return| + with the control points in place, if linear equations + needn't be solved@> + else case left_type(s) of + end_cycle,open:@<Set up equation to match mock curvatures + at $z_k$; then |goto found| with $\theta_n$ + adjusted to equal $\theta_0$, if a cycle has ended@>; + curl:@<Set up equation for a curl at $\theta_n$ + and |goto found|@>; + given:@<Calculate the given value of $\theta_n$ + and |goto found|@>; + end; {there are no other cases} + r:=s; s:=t; incr(k); + end; +found:@<Finish choosing angles and assigning control points@>; +exit:end; + +@ On the first time through the loop, we have |k=0| and |r| is not yet +defined. The first linear equation, if any, will have $A_0=B_0=0$. + +@<Get the linear equations started...@>= +case right_type(s) of +given: if left_type(t)=given then @<Reduce to simple case of two givens + and |return|@> + else @<Set up the equation for a given value of $\theta_0$@>; +curl: if left_type(t)=curl then @<Reduce to simple case of straight line + and |return|@> + else @<Set up the equation for a curl at $\theta_0$@>; +open: begin uu[0]:=0; vv[0]:=0; ww[0]:=fraction_one; + end; {this begins a cycle} +end {there are no other cases} + +@ The general equation that specifies equality of mock curvature at $z_k$ is +$$A_k\theta_{k-1}+(B_k+C_k)\theta_k+D_k\theta\k=-B_k\psi_k-D_k\psi\k,$$ +as derived above. We want to combine this with the already-derived equation +$\theta_{k-1}+u_{k-1}\theta_k=v_{k-1}+w_{k-1}\theta_0$ in order to obtain +a new equation +$\theta_k+u_k\theta\k=v_k+w_k\theta_0$. This can be done by dividing the +equation +$$(B_k-u_{k-1}A_k+C_k)\theta_k+D_k\theta\k=-B_k\psi_k-D_k\psi\k-A_kv_{k-1} + -A_kw_{k-1}\theta_0$$ +by $B_k-u_{k-1}A_k+C_k$. The trick is to do this carefully with +fixed-point arithmetic, avoiding the chance of overflow while retaining +suitable precision. + +The calculations will be performed in several registers that +provide temporary storage for intermediate quantities. + +@<Other local variables for |solve_choices|@>= +@!aa,@!bb,@!cc,@!ff,@!acc:fraction; {temporary registers} +@!dd,@!ee:scaled; {likewise, but |scaled|} +@!lt,@!rt:scaled; {tension values} + +@ @<Set up equation to match mock curvatures...@>= +begin @<Calculate the values $\\{aa}=A_k/B_k$, $\\{bb}=D_k/C_k$, + $\\{dd}=(3-\alpha_{k-1})d_{k,k+1}$, $\\{ee}=(3-\beta\k)d_{k-1,k}$, + and $\\{cc}=(B_k-u_{k-1}A_k)/B_k$@>; +@<Calculate the ratio $\\{ff}=C_k/(C_k+B_k-u_{k-1}A_k)$@>; +uu[k]:=take_fraction(ff,bb); +@<Calculate the values of $v_k$ and $w_k$@>; +if left_type(s)=end_cycle then + @<Adjust $\theta_n$ to equal $\theta_0$ and |goto found|@>; +end + +@ Since tension values are never less than 3/4, the values |aa| and +|bb| computed here are never more than 4/5. + +@<Calculate the values $\\{aa}=...@>= +if abs(right_tension(r))=unity then + begin aa:=fraction_half; dd:=2*delta[k]; + end +else begin aa:=make_fraction(unity,3*abs(right_tension(r))-unity); + dd:=take_fraction(delta[k], + fraction_three-make_fraction(unity,abs(right_tension(r)))); + end; +if abs(left_tension(t))=unity then + begin bb:=fraction_half; ee:=2*delta[k-1]; + end +else begin bb:=make_fraction(unity,3*abs(left_tension(t))-unity); + ee:=take_fraction(delta[k-1], + fraction_three-make_fraction(unity,abs(left_tension(t)))); + end; +cc:=fraction_one-take_fraction(uu[k-1],aa) + +@ The ratio to be calculated in this step can be written in the form +$$\beta_k^2\cdot\\{ee}\over\beta_k^2\cdot\\{ee}+\alpha_k^2\cdot + \\{cc}\cdot\\{dd},$$ +because of the quantities just calculated. The values of |dd| and |ee| +will not be needed after this step has been performed. + +@<Calculate the ratio $\\{ff}=C_k/(C_k+B_k-u_{k-1}A_k)$@>= +dd:=take_fraction(dd,cc); lt:=abs(left_tension(s)); rt:=abs(right_tension(s)); +if lt<>rt then {$\beta_k^{-1}\ne\alpha_k^{-1}$} + if lt<rt then + begin ff:=make_fraction(lt,rt); + ff:=take_fraction(ff,ff); {$\alpha_k^2/\beta_k^2$} + dd:=take_fraction(dd,ff); + end + else begin ff:=make_fraction(rt,lt); + ff:=take_fraction(ff,ff); {$\beta_k^2/\alpha_k^2$} + ee:=take_fraction(ee,ff); + end; +ff:=make_fraction(ee,ee+dd) + +@ The value of $u_{k-1}$ will be |<=1| except when $k=1$ and the previous +equation was specified by a curl. In that case we must use a special +method of computation to prevent overflow. + +Fortunately, the calculations turn out to be even simpler in this ``hard'' +case. The curl equation makes $w_0=0$ and $v_0=-u_0\psi_1$, hence +$-B_1\psi_1-A_1v_0=-(B_1-u_0A_1)\psi_1=-\\{cc}\cdot B_1\psi_1$. + +@<Calculate the values of $v_k$ and $w_k$@>= +acc:=-take_fraction(psi[k+1],uu[k]); +if right_type(r)=curl then + begin ww[k]:=0; + vv[k]:=acc-take_fraction(psi[1],fraction_one-ff); + end +else begin ff:=make_fraction(fraction_one-ff,cc); {this is + $B_k/(C_k+B_k-u_{k-1}A_k)<5$} + acc:=acc-take_fraction(psi[k],ff); + ff:=take_fraction(ff,aa); {this is $A_k/(C_k+B_k-u_{k-1}A_k)$} + vv[k]:=acc-take_fraction(vv[k-1],ff); + if ww[k-1]=0 then ww[k]:=0 + else ww[k]:=-take_fraction(ww[k-1],ff); + end + +@ When a complete cycle has been traversed, we have $\theta_k+u_k\theta\k= +v_k+w_k\theta_0$, for |1<=k<=n|. We would like to determine the value of +$\theta_n$ and reduce the system to the form $\theta_k+u_k\theta\k=v_k$ +for |0<=k<n|, so that the cyclic case can be finished up just as if there +were no cycle. + +The idea in the following code is to observe that +$$\eqalign{\theta_n&=v_n+w_n\theta_0-u_n\theta_1=\cdots\cr +&=v_n+w_n\theta_0-u_n\bigl(v_1+w_1\theta_0-u_1(v_2+\cdots + -u_{n-2}(v_{n-1}+w_{n-1}\theta_0-u_{n-1}\theta_0))\bigr),\cr}$$ +so we can solve for $\theta_n=\theta_0$. + +@<Adjust $\theta_n$ to equal $\theta_0$ and |goto found|@>= +begin aa:=0; bb:=fraction_one; {we have |k=n|} +repeat decr(k); +if k=0 then k:=n; +aa:=vv[k]-take_fraction(aa,uu[k]); +bb:=ww[k]-take_fraction(bb,uu[k]); +until k=n; {now $\theta_n=\\{aa}+\\{bb}\cdot\theta_n$} +aa:=make_fraction(aa,fraction_one-bb); +theta[n]:=aa; vv[0]:=aa; +for k:=1 to n-1 do vv[k]:=vv[k]+take_fraction(aa,ww[k]); +goto found; +end + +@ @d reduce_angle(#)==if abs(#)>one_eighty_deg then + if #>0 then #:=#-three_sixty_deg@+else #:=#+three_sixty_deg + +@<Calculate the given value of $\theta_n$...@>= +begin theta[n]:=left_given(s)-n_arg(delta_x[n-1],delta_y[n-1]); +reduce_angle(theta[n]); +goto found; +end + +@ @<Set up the equation for a given value of $\theta_0$@>= +begin vv[0]:=right_given(s)-n_arg(delta_x[0],delta_y[0]); +reduce_angle(vv[0]); +uu[0]:=0; ww[0]:=0; +end + +@ @<Set up the equation for a curl at $\theta_0$@>= +begin cc:=right_curl(s); lt:=abs(left_tension(t)); rt:=abs(right_tension(s)); +if (rt=unity)and(lt=unity) then + uu[0]:=make_fraction(cc+cc+unity,cc+two) +else uu[0]:=curl_ratio(cc,rt,lt); +vv[0]:=-take_fraction(psi[1],uu[0]); ww[0]:=0; +end + +@ @<Set up equation for a curl at $\theta_n$...@>= +begin cc:=left_curl(s); lt:=abs(left_tension(s)); rt:=abs(right_tension(r)); +if (rt=unity)and(lt=unity) then + ff:=make_fraction(cc+cc+unity,cc+two) +else ff:=curl_ratio(cc,lt,rt); +theta[n]:=-make_fraction(take_fraction(vv[n-1],ff), + fraction_one-take_fraction(ff,uu[n-1])); +goto found; +end + +@ The |curl_ratio| subroutine has three arguments, which our previous notation +encourages us to call $\gamma$, $\alpha^{-1}$, and $\beta^{-1}$. It is +a somewhat tedious program to calculate +$${(3-\alpha)\alpha^2\gamma+\beta^3\over + \alpha^3\gamma+(3-\beta)\beta^2},$$ +with the result reduced to 4 if it exceeds 4. (This reduction of curl +is necessary only if the curl and tension are both large.) +The values of $\alpha$ and $\beta$ will be at most~4/3. + +@<Declare subroutines needed by |solve_choices|@>= +function curl_ratio(@!gamma,@!a_tension,@!b_tension:scaled):fraction; +var @!alpha,@!beta,@!num,@!denom,@!ff:fraction; {registers} +begin alpha:=make_fraction(unity,a_tension); +beta:=make_fraction(unity,b_tension);@/ +if alpha<=beta then + begin ff:=make_fraction(alpha,beta); ff:=take_fraction(ff,ff); + gamma:=take_fraction(gamma,ff);@/ + beta:=beta div @'10000; {convert |fraction| to |scaled|} + denom:=take_fraction(gamma,alpha)+three-beta; + num:=take_fraction(gamma,fraction_three-alpha)+beta; + end +else begin ff:=make_fraction(beta,alpha); ff:=take_fraction(ff,ff); + beta:=take_fraction(beta,ff) div @'10000; {convert |fraction| to |scaled|} + denom:=take_fraction(gamma,alpha)+(ff div 1365)-beta; + {$1365\approx 2^{12}/3$} + num:=take_fraction(gamma,fraction_three-alpha)+beta; + end; +if num>=denom+denom+denom+denom then curl_ratio:=fraction_four +else curl_ratio:=make_fraction(num,denom); +end; + +@ We're in the home stretch now. + +@<Finish choosing angles and assigning control points@>= +for k:=n-1 downto 0 do theta[k]:=vv[k]-take_fraction(theta[k+1],uu[k]); +s:=p; k:=0; +repeat t:=link(s);@/ +n_sin_cos(theta[k]); st:=n_sin; ct:=n_cos;@/ +n_sin_cos(-psi[k+1]-theta[k+1]); sf:=n_sin; cf:=n_cos;@/ +set_controls(s,t,k);@/ +incr(k); s:=t; +until k=n + +@ The |set_controls| routine actually puts the control points into +a pair of consecutive nodes |p| and~|q|. Global variables are used to +record the values of $\sin\theta$, $\cos\theta$, $\sin\phi$, and +$\cos\phi$ needed in this calculation. + +@<Glob...@>= +@!st,@!ct,@!sf,@!cf:fraction; {sines and cosines} + +@ @<Declare subroutines needed by |solve_choices|@>= +procedure set_controls(@!p,@!q:pointer;@!k:integer); +var @!rr,@!ss:fraction; {velocities, divided by thrice the tension} +@!lt,@!rt:scaled; {tensions} +@!sine:fraction; {$\sin(\theta+\phi)$} +begin lt:=abs(left_tension(q)); rt:=abs(right_tension(p)); +rr:=velocity(st,ct,sf,cf,rt); +ss:=velocity(sf,cf,st,ct,lt); +if (right_tension(p)<0)or(left_tension(q)<0) then @<Decrease the velocities, + if necessary, to stay inside the bounding triangle@>; +right_x(p):=x_coord(p)+take_fraction( + take_fraction(delta_x[k],ct)-take_fraction(delta_y[k],st),rr); +right_y(p):=y_coord(p)+take_fraction( + take_fraction(delta_y[k],ct)+take_fraction(delta_x[k],st),rr); +left_x(q):=x_coord(q)-take_fraction( + take_fraction(delta_x[k],cf)+take_fraction(delta_y[k],sf),ss); +left_y(q):=y_coord(q)-take_fraction( + take_fraction(delta_y[k],cf)-take_fraction(delta_x[k],sf),ss); +right_type(p):=explicit; left_type(q):=explicit; +end; + +@ The boundedness conditions $\\{rr}\L\sin\phi\,/\sin(\theta+\phi)$ and +$\\{ss}\L\sin\theta\,/\sin(\theta+\phi)$ are to be enforced if $\sin\theta$, +$\sin\phi$, and $\sin(\theta+\phi)$ all have the same sign. Otherwise +there is no ``bounding triangle.'' +@!@:at_least_}{\&{atleast} primitive@> + +@<Decrease the velocities, if necessary...@>= +if((st>=0)and(sf>=0))or((st<=0)and(sf<=0)) then + begin sine:=take_fraction(abs(st),cf)+take_fraction(abs(sf),ct); + if sine>0 then + begin sine:=take_fraction(sine,fraction_one+unity); {safety factor} + if right_tension(p)<0 then + if ab_vs_cd(abs(sf),fraction_one,rr,sine)<0 then + rr:=make_fraction(abs(sf),sine); + if left_tension(q)<0 then + if ab_vs_cd(abs(st),fraction_one,ss,sine)<0 then + ss:=make_fraction(abs(st),sine); + end; + end + +@ Only the simple cases remain to be handled. + +@<Reduce to simple case of two givens and |return|@>= +begin aa:=n_arg(delta_x[0],delta_y[0]);@/ +n_sin_cos(right_given(p)-aa); ct:=n_cos; st:=n_sin;@/ +n_sin_cos(left_given(q)-aa); cf:=n_cos; sf:=-n_sin;@/ +set_controls(p,q,0); return; +end + +@ @<Reduce to simple case of straight line and |return|@>= +begin right_type(p):=explicit; left_type(q):=explicit; +lt:=abs(left_tension(q)); rt:=abs(right_tension(p)); +if rt=unity then + begin if delta_x[0]>=0 then right_x(p):=x_coord(p)+((delta_x[0]+1) div 3) + else right_x(p):=x_coord(p)+((delta_x[0]-1) div 3); + if delta_y[0]>=0 then right_y(p):=y_coord(p)+((delta_y[0]+1) div 3) + else right_y(p):=y_coord(p)+((delta_y[0]-1) div 3); + end +else begin ff:=make_fraction(unity,3*rt); {$\alpha/3$} + right_x(p):=x_coord(p)+take_fraction(delta_x[0],ff); + right_y(p):=y_coord(p)+take_fraction(delta_y[0],ff); + end; +if lt=unity then + begin if delta_x[0]>=0 then left_x(q):=x_coord(q)-((delta_x[0]+1) div 3) + else left_x(q):=x_coord(q)-((delta_x[0]-1) div 3); + if delta_y[0]>=0 then left_y(q):=y_coord(q)-((delta_y[0]+1) div 3) + else left_y(q):=y_coord(q)-((delta_y[0]-1) div 3); + end +else begin ff:=make_fraction(unity,3*lt); {$\beta/3$} + left_x(q):=x_coord(q)-take_fraction(delta_x[0],ff); + left_y(q):=y_coord(q)-take_fraction(delta_y[0],ff); + end; +return; +end + +@* \[19] Measuring paths. +\MP's \&{llcorner}, \&{lrcorner}, \&{ulcorner}, and \&{urcorner} operators +allow the user to measure the bounding box of anything that can go into a +picture. It's easy to get rough bounds on the $x$ and $y$ extent of a path +by just finding the bounding box of the knots and the control points. We +need a more accurate version of the bounding box, but we can still use the +easy estimate to save time by focusing on the interesting parts of the path. + +@ Computing an accurate bounding box involves a theme that will come up again +and again. Given a Bernshte{\u\i}n polynomial +@^Bernshte{\u\i}n, Serge{\u\i} Natanovich@> +$$B(z_0,z_1,\ldots,z_n;t)=\sum_k{n\choose k}t^k(1-t)^{n-k}z_k,$$ +we can conveniently bisect its range as follows: + +\smallskip +\textindent{1)} Let $z_k^{(0)}=z_k$, for |0<=k<=n|. + +\smallskip +\textindent{2)} Let $z_k^{(j+1)}={1\over2}(z_k^{(j)}+z\k^{(j)})$, for +|0<=k<n-j|, for |0<=j<n|. + +\smallskip\noindent +Then +$$B(z_0,z_1,\ldots,z_n;t)=B(z_0^{(0)},z_0^{(1)},\ldots,z_0^{(n)};2t) + =B(z_0^{(n)},z_1^{(n-1)},\ldots,z_n^{(0)};2t-1).$$ +This formula gives us the coefficients of polynomials to use over the ranges +$0\L t\L{1\over2}$ and ${1\over2}\L t\L1$. + +@ Now here's a subroutine that's handy for all sorts of path computations: +Given a quadratic polynomial $B(a,b,c;t)$, the |crossing_point| function +returns the unique |fraction| value |t| between 0 and~1 at which +$B(a,b,c;t)$ changes from positive to negative, or returns +|t=fraction_one+1| if no such value exists. If |a<0| (so that $B(a,b,c;t)$ +is already negative at |t=0|), |crossing_point| returns the value zero. + +@d no_crossing==begin crossing_point:=fraction_one+1; return; + end +@d one_crossing==begin crossing_point:=fraction_one; return; + end +@d zero_crossing==begin crossing_point:=0; return; + end + +@p function crossing_point(@!a,@!b,@!c:integer):fraction; +label exit; +var @!d:integer; {recursive counter} +@!x,@!xx,@!x0,@!x1,@!x2:integer; {temporary registers for bisection} +begin if a<0 then zero_crossing; +if c>=0 then + begin if b>=0 then + if c>0 then no_crossing + else if (a=0)and(b=0) then no_crossing + else one_crossing; + if a=0 then zero_crossing; + end +else if a=0 then if b<=0 then zero_crossing; +@<Use bisection to find the crossing point, if one exists@>; +exit:end; + +@ The general bisection method is quite simple when $n=2$, hence +|crossing_point| does not take much time. At each stage in the +recursion we have a subinterval defined by |l| and~|j| such that +$B(a,b,c;2^{-l}(j+t))=B(x_0,x_1,x_2;t)$, and we want to ``zero in'' on +the subinterval where $x_0\G0$ and $\min(x_1,x_2)<0$. + +It is convenient for purposes of calculation to combine the values +of |l| and~|j| in a single variable $d=2^l+j$, because the operation +of bisection then corresponds simply to doubling $d$ and possibly +adding~1. Furthermore it proves to be convenient to modify +our previous conventions for bisection slightly, maintaining the +variables $X_0=2^lx_0$, $X_1=2^l(x_0-x_1)$, and $X_2=2^l(x_1-x_2)$. +With these variables the conditions $x_0\ge0$ and $\min(x_1,x_2)<0$ are +equivalent to $\max(X_1,X_1+X_2)>X_0\ge0$. + +The following code maintains the invariant relations +$0\L|x0|<\max(|x1|,|x1|+|x2|)$, +$\vert|x1|\vert<2^{30}$, $\vert|x2|\vert<2^{30}$; +it has been constructed in such a way that no arithmetic overflow +will occur if the inputs satisfy +$a<2^{30}$, $\vert a-b\vert<2^{30}$, and $\vert b-c\vert<2^{30}$. + +@<Use bisection to find the crossing point...@>= +d:=1; x0:=a; x1:=a-b; x2:=b-c; +repeat x:=half(x1+x2); +if x1-x0>x0 then + begin x2:=x; double(x0); double(d); + end +else begin xx:=x1+x-x0; + if xx>x0 then + begin x2:=x; double(x0); double(d); + end + else begin x0:=x0-xx; + if x<=x0 then if x+x2<=x0 then no_crossing; + x1:=x; d:=d+d+1; + end; + end; +until d>=fraction_one; +crossing_point:=d-fraction_one + +@ Here is a routine that computes the $x$ or $y$ coordinate of the point on +a cubic corresponding to the |fraction| value~|t|. + +It is convenient to define a \.{WEB} macro |t_of_the_way| such that +|t_of_the_way(a)(b)| expands to |a-(a-b)*t|, i.e., to |t[a,b]|. + +@d t_of_the_way_end(#)==#,t@=)@> +@d t_of_the_way(#)==#-take_fraction@=(@>#-t_of_the_way_end + +@p function eval_cubic(@!p,@!q:pointer;t:fraction):scaled; +var @!x1,@!x2,@!x3:scaled; {intermediate values} +begin x1:=t_of_the_way(knot_coord(p))(right_coord(p)); +x2:=t_of_the_way(right_coord(p))(left_coord(q)); +x3:=t_of_the_way(left_coord(q))(knot_coord(q));@/ +x1:=t_of_the_way(x1)(x2); +x2:=t_of_the_way(x2)(x3); +eval_cubic:=t_of_the_way(x1)(x2); +end; + +@ The actual bounding box information is stored in global variables. +Since it is convenient to address the $x$ and $y$ information +separately, we define arrays indexed by |x_code..y_code| and use +macros to give them more convenient names. + +@d x_code=0 {index for |minx| and |maxx|} +@d y_code=1 {index for |miny| and |maxy|} +@d minx==bbmin[x_code] +@d maxx==bbmax[x_code] +@d miny==bbmin[y_code] +@d maxy==bbmax[y_code] + +@<Glob...@>= +@!bbmin,@!bbmax:array[x_code..y_code] of scaled; + {the result of procedures that compute bounding box information} + +@ Now we're ready for the key part of the bounding box computation. +The |bound_cubic| procedure updates |bbmin[c]| and |bbmax[c]| based on +$$B(\hbox{|knot_coord(p)|}, \hbox{|right_coord(p)|}, + \hbox{|left_coord(q)|}, \hbox{|knot_coord(q)|};t) +$$ +for $0<t\le1$. In other words, the procedure adjusts the bounds to +accommodate |knot_coord(q)| and any extremes over the range $0<t<1$. +The |c| parameter is |x_code| or |y_code|. + +@p procedure bound_cubic(@!p,@!q:pointer;c:small_number); +var @!wavy:boolean; {whether we need to look for extremes} +@!del1,@!del2,@!del3,@!del,@!dmax:scaled; {proportional to the control + points of a quadratic derived from a cubic} +@!t,@!tt:fraction; {where a quadratic crosses zero} +@!x:scaled; {a value that |bbmin[c]| and |bbmax[c]| must accommodate} +begin x:=knot_coord(q); +@<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@>; +@<Check the control points against the bounding box and set |wavy:=true| + if any of them lie outside@>; +if wavy then + begin del1:=right_coord(p)-knot_coord(p); + del2:=left_coord(q)-right_coord(p); + del3:=knot_coord(q)-left_coord(q); + @<Scale up |del1|, |del2|, and |del3| for greater accuracy; + also set |del| to the first nonzero element of |(del1,del2,del3)|@>; + if del<0 then + begin negate(del1); negate(del2); negate(del3); + end; + t:=crossing_point(del1,del2,del3); + if t<fraction_one then + @<Test the extremes of the cubic against the bounding box@>; + end; +end; + +@ @<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@>= +if x<bbmin[c] then bbmin[c]:=x; +if x>bbmax[c] then bbmax[c]:=x + +@ @<Check the control points against the bounding box and set...@>= +wavy:=true; +if bbmin[c]<=right_coord(p) then + if right_coord(p)<=bbmax[c] then + if bbmin[c]<=left_coord(q) then + if left_coord(q)<=bbmax[c] then + wavy:=false + +@ If |del1=del2=del3=0|, it's impossible to obey the title of this +section. We just set |del=0| in that case. + +@<Scale up |del1|, |del2|, and |del3| for greater accuracy...@>= +if del1<>0 then del:=del1 +else if del2<>0 then del:=del2 +else del:=del3; +if del<>0 then + begin dmax:=abs(del1); + if abs(del2)>dmax then dmax:=abs(del2); + if abs(del3)>dmax then dmax:=abs(del3); + while dmax<fraction_half do + begin double(dmax); double(del1); double(del2); double(del3); + end; + end + +@ Since |crossing_point| has tried to choose |t| so that +$B(|del1|,|del2|,|del3|;\tau)$ crosses zero at $\tau=|t|$ with negative +slope, the value of |del2| computed below should not be positive. +But rounding error could make it slightly positive in which case we +must cut it to zero to avoid confusion. + +@<Test the extremes of the cubic against the bounding box@>= +begin x:=eval_cubic(p,q,t); +@<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@>; +del2:=t_of_the_way(del2)(del3); + {now |0,del2,del3| represent the derivative on the remaining interval} +if del2>0 then del2:=0; +tt:=crossing_point(0,-del2,-del3); +if tt<fraction_one then + @<Test the second extreme against the bounding box@>; +end + +@ @<Test the second extreme against the bounding box@>= +begin x:=eval_cubic(p,q,t_of_the_way(tt)(fraction_one)); +@<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@>; +end + +@ Finding the bounding box of a path is basically a matter of applying +|bound_cubic| twice for each pair of adjacent knots. + +@p procedure path_bbox(@!h:pointer); +label exit; +var @!p,@!q:pointer; {a pair of adjacent knots} +begin minx:=x_coord(h); miny:=y_coord(h); +maxx:=minx; maxy:=miny;@/ +p:=h; +repeat if right_type(p)=endpoint then return; +q:=link(p);@/ +bound_cubic(x_loc(p),x_loc(q),x_code); +bound_cubic(y_loc(p),y_loc(q),y_code); +p:=q; +until p=h; +exit:end; + +@ Another important way to measure a path is to find its arc length. This +is best done by using the general bisection algorithm to subdivide the path +until obtaining ``well behaved'' subpaths whose arc lengths can be approximated +by simple means. + +Since the arc length is the integral with respect to time of the magnitude of +the velocity, it is natural to use Simpson's rule for the approximation. +@^Simpson's rule@> +If $\dot B(t)$ is the spline velocity, Simpson's rule gives +$$ \vb\dot B(0)\vb + 4\vb\dot B({1\over2})\vb + \vb\dot B(1)\vb \over 6 $$ +for the arc length of a path of length~1. For a cubic spline +$B(z_0,z_1,z_2,z_3;t)$, the time derivative $\dot B(t)$ is +$3B(dz_0,dz_1,dz_2;t)$, where $dz_i=z_{i+1}-z_i$. Hence the arc length +approximation is +$$ {\vb dz_0\vb \over 2} + 2\vb dz_{02}\vb + {\vb dz_2\vb \over 2}, $$ +where +$$ dz_{02}={1\over2}\left({dz_0+dz_1\over 2}+{dz_1+dz_2\over 2}\right)$$ +is the result of the bisection algorithm. + +@ The remaining problem is how to decide when a subpath is ``well behaved.'' +This could be done via the theoretical error bound for Simpson's rule, +@^Simpson's rule@> +but this is impractical because it requires an estimate of the fourth +derivative of the quantity being integrated. It is much easier to just perform +a bisection step and see how much the arc length estimate changes. Since the +error for Simpson's rule is proportional to the fourth power of the sample +spacing, the remaining error is typically about $1\over16$ of the amount of +the change. We say ``typically'' because the error has a pseudo-random behavior +that could cause the two estimates to agree when each contain large errors. + +To protect against disasters such as undetected cusps, the bisection process +should always continue until all the $dz_i$ vectors belong to a single +$90^\circ$ sector. This ensures that no point on the spline can have velocity +less than 70\% of the minimum of $\vb dz_0\vb$, $\vb dz_1\vb$ and $\vb dz_2\vb$. +If such a spline happens to produce an erroneous arc length estimate that +is little changed by bisection, the amount of the error is likely to be fairly +small. We will try to arrange things so that freak accidents of this type do +not destroy the inverse relationship between the \&{arclength} and +\&{arctime} operations. +@:arclength_}{\&{arclength} primitive@> +@:arctime_}{\&{arctime} primitive@> + +@ The \&{arclength} and \&{arctime} operations are both based on a recursive +@^recursion@> +function that finds the arc length of a cubic spline given $dz_0$, $dz_1$, +$dz_2$. This |arc_test| routine also takes an arc length goal |a_goal| and +returns the time when the arc length reaches |a_goal| if there is such a time. +Thus the return value is either an arc length less than |a_goal| or, if the +arc length would be at least |a_goal|, it returns a time value decreased by +|two|. This allows the caller to use the sign of the result to distinguish +between arc lengths and time values. On certain types of overflow, it is +possible for |a_goal| and the result of |arc_test| both to be |el_gordo|. +Otherwise, the result is always less than |a_goal|. + +Rather than halving the control point coordinates on each recursive call to +|arc_test|, it is better to keep them proportional to velocity on the original +curve and halve the results instead. This means that recursive calls can +potentially use larger error tolerances in their arc length estimates. How +much larger depends on to what extent the errors behave as though they are +independent of each other. To save computing time, we use optimistic assumptions +and increase the tolerance by a factor of about $\sqrt2$ for each recursive +call. + +In addition to the tolerance parameter, |arc_test| should also have parameters +for ${1\over3}\vb\dot B(0)\vb$, ${2\over3}\vb\dot B({1\over2})\vb$, and +${1\over3}\vb\dot B(1)\vb$. These quantities are relatively expensive to compute +and they are needed in different instances of |arc_test|. + +@p @t\4@>@<Declare subroutines needed by |arc_test|@>@; +function arc_test(@!dx0, @!dy0, @!dx1, @!dy1, @!dx2, @!dy2, + @!v0, @!v02, @!v2, @!a_goal, @!tol:scaled): scaled; +label exit; +var simple: boolean; {are the control points confined to a $90^\circ$ sector?} +@!dx01, @!dy01, @!dx12, @!dy12, @!dx02, @!dy02: scaled; {bisection results} +@!v002, @!v022: scaled; + {twice the velocity magnitudes at $t={1\over4}$ and $t={3\over4}$} +@!arc: scaled; {best arc length estimate before recursion} +@<Other local variables in |arc_test|@>@; +begin @<Bisect the B\'ezier quadratic given by |dx0|, |dy0|, |dx1|, |dy1|, + |dx2|, |dy2|@>; +@<Initialize |v002|, |v022|, and the arc length estimate |arc|; if it overflows + set |arc_test| and |return|@>; +@<Test if the control points are confined to one quadrant or rotating them + $45^\circ$ would put them in one quadrant. Then set |simple| appropriately@>; +if simple and (abs(arc-v02-halfp(v0+v2)) <= tol) then + if arc < a_goal then @+arc_test := arc + else @<Estimate when the arc length reaches |a_goal| and set |arc_test| to + that time minus |two|@> +else @<Use one or two recursive calls to compute the |arc_test| function@>; +exit:end; + +@ The |tol| value should by multiplied by $\sqrt 2$ before making recursive +calls, but $1.5$ is an adequate approximation. It is best to avoid using +|make_fraction| in this inner loop. +@^inner loop@> + +@<Use one or two recursive calls to compute the |arc_test| function@>= +begin @<Set |a_new| and |a_aux| so their sum is |2*a_goal| and |a_new| is as + large as possible@>; +tol := tol + halfp(tol); +a := arc_test(dx0,dy0, dx01,dy01, dx02,dy02, v0, v002, halfp(v02), a_new, tol); +if a<0 then @+arc_test := -halfp(two-a) +else begin @<Update |a_new| to reduce |a_new+a_aux| by |a|@>; + b := arc_test(dx02,dy02, dx12,dy12, dx2,dy2, + halfp(v02), v022, v2, a_new, tol); + if b<0 then @+arc_test := -halfp(-b) - half_unit + else arc_test := a + half(b-a); + end; +end + +@ @<Other local variables in |arc_test|@>= +@!a, @!b: scaled; {results of recursive calls} +@!a_new, @!a_aux: scaled; {the sum of these gives the |a_goal|} + +@ @<Set |a_new| and |a_aux| so their sum is |2*a_goal| and |a_new| is...@>= +a_aux := el_gordo - a_goal; +if a_goal > a_aux then + begin a_aux := a_goal - a_aux; + a_new := el_gordo; + end +else begin a_new := a_goal + a_goal; + a_aux := 0; + end + +@ There is no need to maintain |a_aux| at this point so we use it as a temporary +to force the additions and subtractions to be done in an order that avoids +overflow. + +@<Update |a_new| to reduce |a_new+a_aux| by |a|@>= +if a > a_aux then + begin a_aux := a_aux - a; + a_new := a_new + a_aux; + end + +@ This code assumes all {\it dx} and {\it dy} variables have magnitude less than +|fraction_four|. To simplify the rest of the |arc_test| routine, we strengthen +this assumption by requiring the norm of each $({\it dx},{\it dy})$ pair to obey +this bound. Note that recursive calls will maintain this invariant. + +@<Bisect the B\'ezier quadratic given by |dx0|, |dy0|, |dx1|, |dy1|,...@>= +dx01 := half(dx0 + dx1); +dx12 := half(dx1 + dx2); +dx02 := half(dx01 + dx12);@/ +dy01 := half(dy0 + dy1); +dy12 := half(dy1 + dy2); +dy02 := half(dy01 + dy12) + +@ We should be careful to keep |arc<el_gordo| so that calling |arc_test| with +|a_goal=el_gordo| is guaranteed to yield the arc length. + +@<Initialize |v002|, |v022|, and the arc length estimate |arc|;...@>= +v002 := pyth_add(dx01+half(dx0+dx02), dy01+half(dy0+dy02)); +v022 := pyth_add(dx12+half(dx02+dx2), dy12+half(dy02+dy2)); +tmp := halfp(v02+2); +arc1 := v002 + half(halfp(v0+tmp) - v002); +arc := v022 + half(halfp(v2+tmp) - v022); +if (arc < el_gordo-arc1) then @+arc := arc+arc1 +else begin arith_error := true; + if a_goal=el_gordo then @+arc_test := el_gordo + else arc_test := -two; + return; + end + +@ @<Other local variables in |arc_test|@>= +tmp, tmp2: scaled; {all purpose temporary registers} +arc1: scaled; {arc length estimate for the first half} + +@ @<Test if the control points are confined to one quadrant or rotating...@>= +simple := (dx0>=0) and (dx1>=0) and (dx2>=0) or@| + (dx0<=0) and (dx1<=0) and (dx2<=0); +if simple then + simple := (dy0>=0) and (dy1>=0) and (dy2>=0) or@| + (dy0<=0) and (dy1<=0) and (dy2<=0); +if not simple then + begin simple := (dx0>=dy0) and (dx1>=dy1) and (dx2>=dy2) or@| + (dx0<=dy0) and (dx1<=dy1) and (dx2<=dy2); + if simple then + simple := (-dx0>=dy0) and (-dx1>=dy1) and (-dx2>=dy2) or@| + (-dx0<=dy0) and (-dx1<=dy1) and (-dx2<=dy2); + end + +@ Since Simpson's rule is based on approximating the integrand by a parabola, +@^Simpson's rule@> +it is appropriate to use the same approximation to decide when the integral +reaches the intermediate value |a_goal|. At this point +$$\eqalign{ + {\vb\dot B(0)\vb\over 3} &= \hbox{|v0|}, \qquad + {\vb\dot B({1\over4})\vb\over 3} = {\hbox{|v002|}\over 2}, \qquad + {\vb\dot B({1\over2})\vb\over 3} = {\hbox{|v02|}\over 2}, \cr + {\vb\dot B({3\over4})\vb\over 3} &= {\hbox{|v022|}\over 2}, \qquad + {\vb\dot B(1)\vb\over 3} = \hbox{|v2|} \cr +} +$$ +and +$$ {\vb\dot B(t)\vb\over 3} \approx + \cases{B\left(\hbox{|v0|}, + \hbox{|v002|}-{1\over 2}\hbox{|v0|}-{1\over 4}\hbox{|v02|}, + {1\over 2}\hbox{|v02|}; 2t \right)& + if $t\le{1\over 2}$\cr + B\left({1\over 2}\hbox{|v02|}, + \hbox{|v022|}-{1\over 4}\hbox{|v02|}-{1\over 2}\hbox{|v2|}, + \hbox{|v2|}; 2t-1 \right)& + if $t\ge{1\over 2}$.\cr} + \eqno (*) +$$ +We can integrate $\vb\dot B(t)\vb$ by using +$$\int 3B(a,b,c;\tau)\,dt = + {B(0,a,a+b,a+b+c;\tau) + {\rm constant} \over {d\tau\over dt}}. +$$ + +This construction allows us to find the time when the arc length reaches +|a_goal| by solving a cubic equation of the form +$$ B(0,a,a+b,a+b+c;\tau) = x, $$ +where $\tau$ is $2t$ or $2t+1$, $x$ is |a_goal| or |a_goal-arc1|, and $a$, $b$, +and $c$ are the Bernshte{\u\i}n coefficients from $(*)$ divided by +@^Bernshte{\u\i}n, Serge{\u\i} Natanovich@> +$d\tau\over dt$. We shall define a function |solve_rising_cubic| that finds +$\tau$ given $a$, $b$, $c$, and $x$. + +@<Estimate when the arc length reaches |a_goal| and set |arc_test| to...@>= +begin tmp := (v02 + 2) div 4; +if a_goal<=arc1 then + begin tmp2 := halfp(v0); + arc_test := halfp(solve_rising_cubic(tmp2, arc1-tmp2-tmp, tmp, a_goal)) + - two; + end +else begin tmp2 := halfp(v2); + arc_test := (half_unit - two) +@| + halfp(solve_rising_cubic(tmp, arc-arc1-tmp-tmp2, tmp2, a_goal-arc1)); + end; +end + +@ Here is the |solve_rising_cubic| routine that finds the time~$t$ when +$$ B(0, a, a+b, a+b+c; t) = x. $$ +This routine is based on |crossing_point| but is simplified by the +assumptions that $B(a,b,c;t)\ge0$ for $0\le t\le1$ and that |0<=x<=a+b+c|. +If rounding error causes this condition to be violated slightly, we just ignore +it and proceed with binary search. This finds a time when the function value +reaches |x| and the slope is positive. + +@<Declare subroutines needed by |arc_test|@>= +function solve_rising_cubic(@!a, @!b, @!c, @!x: scaled): scaled; +var @!ab, @!bc, @!ac: scaled; {bisection results} +@!t: integer; {$2^k+q$ where unscaled answer is in $[q2^{-k},(q+1)2^{-k})$} +@!xx: integer; {temporary for updating |x|} +begin if (a<0) or (c<0) then confusion("rising?"); +@:this can't happen rising?}{\quad rising?@> +if x<=0 then solve_rising_cubic := 0 +else if x >= a+b+c then solve_rising_cubic := unity +else begin t := 1; + @<Rescale if necessary to make sure |a|, |b|, and |c| are all less than + |el_gordo div 3|@>; + repeat double(t); + @<Subdivide the B\'ezier quadratic defined by |a|, |b|, |c|@>; + xx := x - a - ab - ac; + if xx < -x then + begin double(x); + b:=ab; c:=ac; + end + else begin x := x + xx; + a:=ac; b:=bc; + t := t+1; + end; + until t >= unity; + solve_rising_cubic := t - unity; + end; +end; + +@ @<Subdivide the B\'ezier quadratic defined by |a|, |b|, |c|@>= +ab := half(a+b); +bc := half(b+c); +ac := half(ab + bc) + +@ @d one_third_el_gordo==@'5252525252 {upper bound on |a|, |b|, and |c|} + +@<Rescale if necessary to make sure |a|, |b|, and |c| are all less than...@>= +while (a>one_third_el_gordo) or@| (b>one_third_el_gordo) + or@| (c>one_third_el_gordo) do + begin a := halfp(a); + b := half(b); + c := halfp(c); + x := halfp(x); + end + +@ It is convenient to have a simpler interface to |arc_test| that requires no +unnecessary arguments and ensures that each $({\it dx},{\it dy})$ pair has +length less than |fraction_four|. + +@d arc_tol = 16 {quit when change in arc length estimate reaches this} + +@p function do_arc_test(@!dx0, @!dy0, @!dx1, @!dy1, @!dx2, @!dy2, + @!a_goal: scaled): scaled; +var @!v0, @!v1, @!v2: scaled; {length of each $({\it dx},{\it dy})$ pair} +@!v02: scaled; {twice the norm of the quadratic at $t={1\over2}$} +begin v0 := pyth_add(dx0,dy0); +v1 := pyth_add(dx1,dy1); +v2 := pyth_add(dx2,dy2); +if (v0>=fraction_four) or (v1>=fraction_four) or (v2>=fraction_four) then + begin arith_error := true; + if a_goal=el_gordo then @+do_arc_test := el_gordo + else do_arc_test := -two; + end +else begin v02 := pyth_add(dx1+half(dx0+dx2), dy1+half(dy0+dy2));@/ + do_arc_test := arc_test(dx0,dy0, dx1,dy1, dx2,dy2,@| + v0, v02, v2, a_goal, arc_tol); + end; +end; + +@ Now it is easy to find the arc length of an entire path. + +@p function get_arc_length(@!h: pointer): scaled; +label done; +var @!p, @!q: pointer; {for traversing the path} +@!a, @!a_tot: scaled; {current and total arc lengths} +begin a_tot := 0; +p := h; +while right_type(p)<>endpoint do + begin q := link(p); + a := do_arc_test(right_x(p)-x_coord(p), right_y(p)-y_coord(p),@| + left_x(q)-right_x(p), left_y(q)-right_y(p),@| + x_coord(q)-left_x(q), y_coord(q)-left_y(q), el_gordo); + a_tot := slow_add(a, a_tot); + if q=h then goto done @+else p:=q; + end; +done:check_arith; +get_arc_length := a_tot; +end; + +@ The inverse operation of finding the time on a path~|h| when the arc length +reaches some value |arc0| can also be accomplished via |do_arc_test|. Some care +is required to handle very large times or negative times on cyclic paths. For +non-cyclic paths, |arc0| values that are negative or too large cause +|get_arc_time| to return 0 or the length of path~|h|. + +If |arc0| is greater than the arc length of a cyclic path~|h|, the result is a +time value greater than the length of the path. Since it could be much greater, +we must be prepared to compute the arc length of path~|h| and divide this into +|arc0| to find how many multiples of the length of path~|h| to add. + +@p function get_arc_time(@!h: pointer; @!arc0:scaled): scaled; +label done; +var @!p, @!q: pointer; {for traversing the path} +@!t_tot: scaled; {accumulator for the result} +@!t: scaled; {the result of |do_arc_test|} +@!arc:scaled; {portion of |arc0| not used up so far} +@!n: integer; {number of extra times to go around the cycle} +begin if arc0<0 then @<Deal with a negative |arc0| value and |goto done|@>; +if arc0=el_gordo then decr(arc0); +t_tot := 0; +arc := arc0; +p := h; +while (right_type(p)<>endpoint) and (arc>0) do + begin q := link(p); + t := do_arc_test(right_x(p)-x_coord(p), right_y(p)-y_coord(p),@| + left_x(q)-right_x(p), left_y(q)-right_y(p),@| + x_coord(q)-left_x(q), y_coord(q)-left_y(q), arc); + @<Update |arc| and |t_tot| after |do_arc_test| has just returned |t|@>; + if q=h then @<Update |t_tot| and |arc| to avoid going around the cyclic + path too many times but set |arith_error:=true| and |goto done| on + overflow@>; + p := q; + end; +done: check_arith; +get_arc_time := t_tot; +end; + +@ @<Update |arc| and |t_tot| after |do_arc_test| has just returned |t|@>= +if t<0 then + begin t_tot := t_tot + t + two; + arc := 0; + end +else begin t_tot := t_tot + unity; + arc := arc - t; + end + +@ @<Deal with a negative |arc0| value and |goto done|@>= +begin if left_type(h)=endpoint then t_tot:=0 +else begin p := htap_ypoc(h); + t_tot := -get_arc_time(p, -arc0); + toss_knot_list(p); + end; +goto done; +end + +@ @<Update |t_tot| and |arc| to avoid going around the cyclic...@>= +if arc>0 then + begin n := arc div (arc0 - arc); + arc := arc - n*(arc0 - arc); + if t_tot > el_gordo div (n+1) then + begin arith_error := true; + t_tot := el_gordo; + goto done; + end; + t_tot := (n + 1)*t_tot; + end + +@* \[20] Data structures for pens. +A Pen in \MP\ can be either elliptical or polygonal. Elliptical pens result +in \ps\ \&{stroke} commands, while anything drawn with a polygonal pen is +@:stroke}{\&{stroke} command@> +converted into an area fill as described in the next part of this program. +The mathematics behind this process is based on simple aspects of the theory +of tracings developed by Leo Guibas, Lyle Ramshaw, and Jorge Stolfi +[``A kinematic framework for computational geometry,'' Proc.\ IEEE Symp.\ +Foundations of Computer Science {\bf 24} (1983), 100--111]. + +Polygonal pens are created from paths via \MP's \&{makepen} primitive. +@:makepen_}{\&{makepen} primitive@> +This path representation is almost sufficient for our purposes except that +a pen path should always be a convex polygon with the vertices in +counter-clockwise order. +Since we will need to scan pen polygons both forward and backward, a pen +should be represented as a doubly linked ring of knot nodes. There is +room for the extra back pointer because we do not need the +|left_type| or |right_type| fields. In fact, we don't need the |left_x|, +|left_y|, |right_x|, or |right_y| fields either but we leave these alone +so that certain procedures can operate on both pens and paths. In particular, +pens can be copied using |copy_path| and recycled using |toss_knot_list|. + +@d knil==info + {this replaces the |left_type| and |right_type| fields in a pen knot} + +@ The |make_pen| procedure turns a path into a pen by initializing +the |knil| pointers and making sure the knots form a convex polygon. +Thus each cubic in the given path becomes a straight line and the control +points are ignored. If the path is not cyclic, the ends are connected by a +straight line. + +@d copy_pen(#)==make_pen(copy_path(#),false) + +@p @<Declare a function called |convex_hull|@>@; +function make_pen(h:pointer;@!need_hull:boolean):pointer; +var @!p,@!q:pointer; {two consecutive knots} +begin q:=h; +repeat p:=q; q:=link(q); +knil(q):=p; +until q=h; +if need_hull then + begin h:=convex_hull(h); + @<Make sure |h| isn't confused with an elliptical pen@>; + end; +make_pen:=h; +end; + +@ The only information required about an elliptical pen is the overall +transformation that has been applied to the original \&{pencircle}. +@:pencircle_}{\&{pencircle} primitive@> +Since it suffices to keep track of how the three points $(0,0)$, $(1,0)$, +and $(0,1)$ are transformed, an elliptical pen can be stored in a single +knot node and transformed as if it were a path. + +@d pen_is_elliptical(#)==(#=link(#)) + +@p function get_pen_circle(@!diam:scaled):pointer; +var @!h:pointer; {the knot node to return} +begin h:=get_node(knot_node_size); +link(h):=h; knil(h):=h;@/ +originator(h):=program_code;@/ +x_coord(h):=0; y_coord(h):=0;@/ +left_x(h):=diam; left_y(h):=0;@/ +right_x(h):=0; right_y(h):=diam;@/ +get_pen_circle:=h; +end; + +@ If the polygon being returned by |make_pen| has only one vertex, it will +be interpreted as an elliptical pen. This is no problem since a degenerate +polygon can equally well be thought of as a degenerate ellipse. We need only +initialize the |left_x|, |left_y|, |right_x|, and |right_y| fields. + +@<Make sure |h| isn't confused with an elliptical pen@>= +if pen_is_elliptical(h) then + begin left_x(h):=x_coord(h); left_y(h):=y_coord(h);@/ + right_x(h):=x_coord(h); right_y(h):=y_coord(h); + end + +@ We have to cheat a little here but most operations on pens only use +the first three words in each knot node. +@^data structure assumptions@> + +@<Initialize a pen at |test_pen| so that it fits in nine words@>= +x_coord(test_pen):=-half_unit; +y_coord(test_pen):=0;@/ +x_coord(test_pen+3):=half_unit; +y_coord(test_pen+3):=0;@/ +x_coord(test_pen+6):=0; +y_coord(test_pen+6):=unity;@/ +link(test_pen):=test_pen+3; +link(test_pen+3):=test_pen+6; +link(test_pen+6):=test_pen; +knil(test_pen):=test_pen+6; +knil(test_pen+3):=test_pen; +knil(test_pen+6):=test_pen+3 + +@ Printing a polygonal pen is very much like printing a path + +@<Declare subroutines for printing expressions@>= +procedure pr_pen(@!h:pointer); +label done; +var @!p,@!q:pointer; {for list traversal} +begin if pen_is_elliptical(h) then + @<Print the elliptical pen |h|@> +else begin p:=h; + repeat print_two(x_coord(p),y_coord(p)); + print_nl(" .. "); + @<Advance |p| making sure the links are OK and |return| if there is + a problem@>; + until p=h; + print("cycle"); + end; +done:end; + +@ @<Advance |p| making sure the links are OK and |return| if there is...@>= +q:=link(p); +if (q=null) or (knil(q)<>p) then + begin print_nl("???"); goto done; {this won't happen} +@.???@> + end; +p:=q + +@ @<Print the elliptical pen |h|@>= +begin print("pencircle transformed ("); +print_scaled(x_coord(h)); +print_char(","); +print_scaled(y_coord(h));@/ +print_char(","); +print_scaled(left_x(h)-x_coord(h)); +print_char(","); +print_scaled(right_x(h)-x_coord(h)); +print_char(","); +print_scaled(left_y(h)-y_coord(h));@/ +print_char(","); +print_scaled(right_y(h)-y_coord(h));@/ +print_char(")"); +end + +@ Here us another version of |pr_pen| that prints the pen as a diagnostic +message. + +@<Declare subroutines for printing expressions@>= +procedure print_pen(@!h:pointer;@!s:str_number;@!nuline:boolean); +begin print_diagnostic("Pen",s,nuline); print_ln; +@.Pen at line...@> +pr_pen(h); +end_diagnostic(true); +end; + +@ Making a polygonal pen into a path involves restoring the |left_type| and +|right_type| fields and setting the control points so as to make a polygonal +path. + +@p procedure make_path(@!h:pointer); +var @!p:pointer; {for traversing the knot list} +@!k:small_number; {a loop counter} +@<Other local variables in |make_path|@>@; +begin if pen_is_elliptical(h) then + @<Make the elliptical pen |h| into a path@> +else begin p:=h; + repeat left_type(p):=explicit; + right_type(p):=explicit;@/ + @<copy the coordinates of knot |p| into its control points@>;@/ + p:=link(p); + until p=h; + end; +end; + +@ @<copy the coordinates of knot |p| into its control points@>= +left_x(p):=x_coord(p); +left_y(p):=y_coord(p);@/ +right_x(p):=x_coord(p); +right_y(p):=y_coord(p) + +@ We need an eight knot path to get a good approximation to an ellipse. + +@<Make the elliptical pen |h| into a path@>= +begin @<Extract the transformation parameters from the elliptical pen~|h|@>; +p:=h; +for k:=0 to 7 do + begin @<Initialize |p| as the |k|th knot of a circle of unit diameter, + transforming it appropriately@>; + if k=7 then link(p):=h @+else link(p):=get_node(knot_node_size); + p:=link(p); + end; +end + +@ @<Extract the transformation parameters from the elliptical pen~|h|@>= +center_x:=x_coord(h); +center_y:=y_coord(h);@/ +width_x:=left_x(h)-center_x; +width_y:=left_y(h)-center_y;@/ +height_x:=right_x(h)-center_x; +height_y:=right_y(h)-center_y + +@ @<Other local variables in |make_path|@>= +@!center_x,@!center_y:scaled; {translation parameters for an elliptical pen} +@!width_x,@!width_y:scaled; {the effect of a unit change in $x$} +@!height_x,@!height_y:scaled; {the effect of a unit change in $y$} +@!dx,@!dy:scaled; {the vector from knot |p| to its right control point} +@!kk:integer; + {|k| advanced $270^\circ$ around the ring (cf. $\sin\theta=\cos(\theta+270)$)} + +@ The only tricky thing here are the tables |half_cos| and |d_cos| used to +find the point $k/8$ of the way around the circle and the direction vector +to use there. + +@<Initialize |p| as the |k|th knot of a circle of unit diameter,...@>= +kk:=(k+6)mod 8;@/ +x_coord(p):=center_x+take_fraction(half_cos[k],width_x) + +take_fraction(half_cos[kk],height_x); +y_coord(p):=center_y+take_fraction(half_cos[k],width_y) + +take_fraction(half_cos[kk],height_y); +dx:=-take_fraction(d_cos[kk],width_x)+take_fraction(d_cos[k],height_x); +dy:=-take_fraction(d_cos[kk],width_y)+take_fraction(d_cos[k],height_y); +right_x(p):=x_coord(p)+dx; +right_y(p):=y_coord(p)+dy;@/ +left_x(p):=x_coord(p)-dx; +left_y(p):=y_coord(p)-dy;@/ +left_type(p):=explicit; +right_type(p):=explicit; +originator(p):=program_code + +@ @<Glob...@>= +half_cos:array[0..7] of fraction; {${1\over2}\cos(45k)$} +d_cos:array[0..7] of fraction; {a magic constant times $\cos(45k)$} + +@ The magic constant for |d_cos| is the distance between $({1\over2},0)$ and +$({1\over4}\sqrt2,{1\over4}\sqrt2)$ times the result of the |velocity| +function for $\theta=\phi=22.5^\circ$. This comes out to be +$$ d = {\sqrt{2-\sqrt2}\over 3+3\cos22.5^\circ} + \approx 0.132608244919772. +$$ + +@<Set init...@>= +half_cos[0]:=fraction_half; +half_cos[1]:=94906266; {$2^{26}\sqrt2\approx94906265.62$} +half_cos[2]:=0;@/ +d_cos[0]:=35596755; {$2^{28}d\approx35596754.69$} +d_cos[1]:=25170707; {$2^{27}\sqrt2\,d\approx25170706.63$} +d_cos[2]:=0; +for k:=3 to 4 do + begin half_cos[k]:=-half_cos[4-k]; + d_cos[k]:=-d_cos[4-k]; + end; +for k:=5 to 7 do + begin half_cos[k]:=half_cos[8-k]; + d_cos[k]:=d_cos[8-k]; + end; + +@ The |convex_hull| function forces a pen polygon to be convex when it is +returned by |make_pen| and after any subsequent transformation where rounding +error might allow the convexity to be lost. +The convex hull algorithm used here is described by F.~P. Preparata and +M.~I. Shamos [{\sl Computational Geometry}, Springer-Verlag, 1985]. + +@<Declare a function called |convex_hull|@>= +@<Declare a procedure called |move_knot|@>@; +function convex_hull(@!h:pointer):pointer; {Make a polygonal pen convex} +label done1,done2,done3; +var @!l,@!r:pointer; {the leftmost and rightmost knots} +@!p,@!q:pointer; {knots being scanned} +@!s:pointer; {the starting point for an upcoming scan} +@!dx,@!dy:scaled; {a temporary pointer} +begin if pen_is_elliptical(h) then convex_hull:=h +else begin @<Set |l| to the leftmost knot in polygon~|h|@>; + @<Set |r| to the rightmost knot in polygon~|h|@>; + if l<>r then + begin s:=link(r); + @<Find any knots on the path from |l| to |r| above the |l|-|r| line and + move them past~|r|@>; + @<Find any knots on the path from |s| to |l| below the |l|-|r| line and + move them past~|l|@>; + @<Sort the path from |l| to |r| by increasing $x$@>; + @<Sort the path from |r| to |l| by decreasing $x$@>; + end; + if l<>link(l) then @<Do a Gramm scan and remove vertices where there + is no left turn@>; + convex_hull:=l; + end; +end; + +@ All comparisons are done primarily on $x$ and secondarily on $y$. + +@<Set |l| to the leftmost knot in polygon~|h|@>= +l:=h; +p:=link(h); +while p<>h do + begin if x_coord(p)<=x_coord(l) then + if (x_coord(p)<x_coord(l)) or (y_coord(p)<y_coord(l)) then + l:=p; + p:=link(p); + end + +@ @<Set |r| to the rightmost knot in polygon~|h|@>= +r:=h; +p:=link(h); +while p<>h do + begin if x_coord(p)>=x_coord(r) then + if (x_coord(p)>x_coord(r)) or (y_coord(p)>y_coord(r)) then + r:=p; + p:=link(p); + end + +@ @<Find any knots on the path from |l| to |r| above the |l|-|r| line...@>= +dx:=x_coord(r)-x_coord(l); +dy:=y_coord(r)-y_coord(l); +p:=link(l); +while p<>r do + begin q:=link(p); + if ab_vs_cd(dx,y_coord(p)-y_coord(l),dy,x_coord(p)-x_coord(l))>0 then + move_knot(p,r); + p:=q; + end + +@ The |move_knot| procedure removes |p| from a doubly linked list and inserts +it after |q|. + +@ @<Declare a procedure called |move_knot|@>= +procedure move_knot(@!p,@!q:pointer); +begin link(knil(p)):=link(p); +knil(link(p)):=knil(p);@/ +knil(p):=q; +link(p):=link(q); +link(q):=p; +knil(link(p)):=p; +end; + +@ @<Find any knots on the path from |s| to |l| below the |l|-|r| line...@>= +p:=s; +while p<>l do + begin q:=link(p); + if ab_vs_cd(dx,y_coord(p)-y_coord(l),dy,x_coord(p)-x_coord(l))<0 then + move_knot(p,l); + p:=q; + end + +@ The list is likely to be in order already so we just do linear insertions. +Secondary comparisons on $y$ ensure that the sort is consistent with the +choice of |l| and |r|. + +@<Sort the path from |l| to |r| by increasing $x$@>= +p:=link(l); +while p<>r do + begin q:=knil(p); + while x_coord(q)>x_coord(p) do q:=knil(q); + while x_coord(q)=x_coord(p) do + if y_coord(q)>y_coord(p) then q:=knil(q) else goto done1; +done1: + if q=knil(p) then p:=link(p) + else begin p:=link(p); move_knot(knil(p),q); + end; + end + +@ @<Sort the path from |r| to |l| by decreasing $x$@>= +p:=link(r); +while p<>l do + begin q:=knil(p); + while x_coord(q)<x_coord(p) do q:=knil(q); + while x_coord(q)=x_coord(p) do + if y_coord(q)<y_coord(p) then q:=knil(q) else goto done2; +done2: + if q=knil(p) then p:=link(p) + else begin p:=link(p); move_knot(knil(p),q); + end; + end + +@ The condition involving |ab_vs_cd| tests if there is not a left turn +at knot |q|. There usually will be a left turn so we streamline the case +where the |then| clause is not executed. + +@<Do a Gramm scan and remove vertices where there...@>= +begin p:=l; q:=link(l); +loop @+begin dx:=x_coord(q)-x_coord(p); + dy:=y_coord(q)-y_coord(p); + p:=q; q:=link(q); + if p=l then goto done3; + if p<>r then + if ab_vs_cd(dx,y_coord(q)-y_coord(p),dy,x_coord(q)-x_coord(p))<=0 then + @<Remove knot |p| and back up |p| and |q| but don't go past |l|@>; + end; +done3: do_nothing; +end + +@ @<Remove knot |p| and back up |p| and |q| but don't go past |l|@>= +begin s:=knil(p); +free_node(p,knot_node_size); +link(s):=q; knil(q):=s; +if s=l then p:=s +else begin p:=knil(s); q:=s; + end; +end + +@ The |find_offset| procedure sets global variables |(cur_x,cur_y)| to the +offset associated with the given direction |(x,y)|. If two different offsets +apply, it chooses one of them. + +@p procedure find_offset(@!x,@!y:scaled;@!h:pointer); +var @!p,@!q:pointer; {consecutive knots} +@!wx,@!wy,@!hx,@!hy:scaled; + {the transformation matrix for an elliptical pen} +@!xx,@!yy:fraction; {untransformed offset for an elliptical pen} +@!d:fraction; {a temporary register} +begin if pen_is_elliptical(h) then + @<Find the offset for |(x,y)| on the elliptical pen~|h|@> +else begin q:=h; + repeat p:=q; q:=link(q); + until ab_vs_cd(x_coord(q)-x_coord(p),y, y_coord(q)-y_coord(p),x)>=0; + repeat p:=q; q:=link(q); + until ab_vs_cd(x_coord(q)-x_coord(p),y, y_coord(q)-y_coord(p),x)<=0; + cur_x:=x_coord(p); + cur_y:=y_coord(p); + end; +end; + +@ @<Glob...@>= +@!cur_x,@!cur_y:scaled; {all-purpose return value registers} + +@ @<Find the offset for |(x,y)| on the elliptical pen~|h|@>= +if (x=0) and (y=0) then + begin cur_x:=x_coord(h); cur_y:=y_coord(h); @+end +else begin @<Find the non-constant part of the transformation for |h|@>; + while (abs(x)<fraction_half) and (abs(y)<fraction_half) do + begin double(x); double(y); @+end; + @<Make |(xx,yy)| the offset on the untransformed \&{pencircle} for the + untransformed version of |(x,y)|@>; + cur_x:=x_coord(h)+take_fraction(xx,wx)+take_fraction(yy,hx); + cur_y:=y_coord(h)+take_fraction(xx,wy)+take_fraction(yy,hy); + end + +@ @<Find the non-constant part of the transformation for |h|@>= +wx:=left_x(h)-x_coord(h); +wy:=left_y(h)-y_coord(h); +hx:=right_x(h)-x_coord(h); +hy:=right_y(h)-y_coord(h) + +@ @<Make |(xx,yy)| the offset on the untransformed \&{pencircle} for the...@>= +yy:=-(take_fraction(x,hy)+take_fraction(y,-hx));@/ +xx:=take_fraction(x,-wy)+take_fraction(y,wx);@/ +d:=pyth_add(xx,yy);@/ +if d>0 then + begin xx:=half(make_fraction(xx,d)); + yy:=half(make_fraction(yy,d)); + end + +@ Finding the bounding box of a pen is easy except if the pen is elliptical. +But we can handle that case by just calling |find_offset| twice. The answer +is stored in the global variables |minx|, |maxx|, |miny|, and |maxy|. + +@p procedure pen_bbox(@!h:pointer); +var @!p:pointer; {for scanning the knot list} +begin if pen_is_elliptical(h) then + @<Find the bounding box of an elliptical pen@> +else begin minx:=x_coord(h); maxx:=minx; + miny:=y_coord(h); maxy:=miny;@/ + p:=link(h); + while p<>h do + begin if x_coord(p)<minx then minx:=x_coord(p); + if y_coord(p)<miny then miny:=y_coord(p); + if x_coord(p)>maxx then maxx:=x_coord(p); + if y_coord(p)>maxy then maxy:=y_coord(p); + p:=link(p); + end; + end; +end; + +@ @<Find the bounding box of an elliptical pen@>= +begin find_offset(0,fraction_one,h); +maxx:=cur_x; +minx:=2*x_coord(h)-cur_x;@/ +find_offset(-fraction_one,0,h); +maxy:=cur_y; +miny:=2*y_coord(h)-cur_y; +end + +@* \[21] Edge structures. +Now we come to \MP's internal scheme for representing pictures. +The representation is very different from \MF's edge structures +because \MP\ pictures contain \ps\ graphics objects instead of pixel +images. However, the basic idea is somewhat similar in that shapes +are represented via their boundaries. + +The main purpose of edge structures is to keep track of graphical objects +until it is time to translate them into \ps. Since \MP\ does not need to +know anything about an edge structure other than how to translate it into +\ps\ and how to find its bounding box, edge structures can be just linked +lists of graphical objects. \MP\ has no easy way to determine whether +two such objects overlap, but it suffices to draw the first one first and +let the second one overwrite it if necessary. + +@ Let's consider the types of graphical objects one at a time. +First of all, a filled contour is represented by a six-word node. The first +word contains |type| and |link| fields, and the next four words contain a +pointer to a cyclic path and the value to use for \ps' \&{currentrgbcolor} +parameter. If a pen is used for filling |pen_p|, |ljoin_val| and |miterlim_val| +give the relevant information. + +@d path_p(#)==link(#+1) + {a pointer to the path that needs filling} +@d pen_p(#)==info(#+1) + {a pointer to the pen to fill or stroke with} +@d obj_red_loc(#)==#+2 {the first of three locations for the color} +@d red_val(#)==mem[#+2].sc + {the red component of the color in the range $0\ldots1$} +@d green_val(#)==mem[#+3].sc + {the green component of the color in the range $0\ldots1$} +@d blue_val(#)==mem[#+4].sc + {the blue component of the color in the range $0\ldots1$} +@d ljoin_val(#)==name_type(#) {the value of \&{linejoin}} +@:linejoin_}{\&{linejoin} primitive@> +@d miterlim_val(#)==mem[#+5].sc {the value of \&{miterlimit}} +@:miterlimit_}{\&{miterlimit} primitive@> +@d obj_color_part(#)==mem[#+2-red_part].sc + {interpret an object pointer that has been offset by |red_part..blue_part|} +@d fill_node_size=6 +@d fill_code=1 + +@p function new_fill_node(@!p: pointer): pointer; + {make a fill node for cyclic path |p| and color black} +var @!t:pointer; {the new node} +begin t:=get_node(fill_node_size); + type(t):=fill_code; + path_p(t):=p; + pen_p(t):=null; {|null| means don't use a pen} + red_val(t):=0; + green_val(t):=0; + blue_val(t):=0; + @<Set the |ljoin_val| and |miterlim_val| fields in object |t|@>; + new_fill_node:=t; +end; + +@ @<Set the |ljoin_val| and |miterlim_val| fields in object |t|@>= +if internal[linejoin]>unity then ljoin_val(t):=2 +else if internal[linejoin]>0 then ljoin_val(t):=1 +else ljoin_val(t):=0; +if internal[miterlimit]<unity then + miterlim_val(t):=unity +else miterlim_val(t):=internal[miterlimit] + +@ A stroked path is represented by an eight-word node that is like a filled +contour node except that it contains the current \&{linecap} value, a scale +factor for the dash pattern, and a pointer that is non-null if the stroke +is to be dashed. The purpose of the scale factor is to allow a picture to +be transformed without touching the picture that |dash_p| points to. + +@d dash_p(#)==link(#+6) + {a pointer to the edge structure that gives the dash pattern} +@d lcap_val(#)==type(#+6) + {the value of \&{linecap}} +@:linecap_}{\&{linecap} primitive@> +@d dash_scale(#)==mem[#+7].sc {dash lengths are scaled by this factor} +@d stroked_node_size=8 +@d stroked_code=2 + +@p function new_stroked_node(@!p:pointer): pointer; + {make a stroked node for path |p| with |pen_p(p)| temporarily |null|} +var @!t:pointer; {the new node} +begin t:=get_node(stroked_node_size); + type(t):=stroked_code; + path_p(t):=p; pen_p(t):=null; + dash_p(t):=null; + dash_scale(t):=unity; + red_val(t):=0; + green_val(t):=0; + blue_val(t):=0; + @<Set the |ljoin_val| and |miterlim_val| fields in object |t|@>; + if internal[linecap]>unity then lcap_val(t):=2 + else if internal[linecap]>0 then lcap_val(t):=1 + else lcap_val(t):=0; + new_stroked_node:=t; +end; + +@ When a dashed line is computed in a transformed coordinate system, the dash +lengths get scaled like the pen shape and we need to compensate for this. Since +there is no unique scale factor for an arbitrary transformation, we use the +the square root of the determinant. The properties of the determinant make it +easier to maintain the |dash_scale|. The computation is fairly straight-forward +except for the initialization of the scale factor |s|. The factor of 64 is +needed because |square_rt| scales its result by $2^8$ while we need $2^{14}$ +to counteract the effect of |take_fraction|. + +@<Declare subroutines needed by |print_edges|@>= +function sqrt_det(a,b,c,d:scaled):scaled; +var @!maxabs:scaled; {$max(|a|,|b|,|c|,|d|)$} +@!s:integer; {amount by which the result of |square_rt| needs to be scaled} +begin @<Initialize |maxabs|@>; +s:=64; +while (maxabs<fraction_one) and (s>1) do + begin double(a); double(b); double(c); double(d);@/ + double(maxabs); s:=halfp(s); + end; +sqrt_det:=s*square_rt(abs(take_fraction(a,d)-take_fraction(b,c))); +end; +@# +function get_pen_scale(p:pointer):scaled; +begin get_pen_scale:=sqrt_det( + left_x(p)-x_coord(p), right_x(p)-x_coord(p),@/ + left_y(p)-y_coord(p), right_y(p)-y_coord(p)); +end; + +@ @<Initialize |maxabs|@>= +maxabs:=abs(a); +if abs(b)>maxabs then maxabs:=abs(b); +if abs(c)>maxabs then maxabs:=abs(c); +if abs(d)>maxabs then maxabs:=abs(d) + +@ When a picture contains text, this is represented by a fourteen-word node +where the color information and |type| and |link| fields are augmented by +additional fields that describe the text and how it is transformed. +The |path_p| and |pen_p| pointers are replaced by a number that identifies +the font and a string number that gives the text to be displayed. +The |width|, |height|, and |depth| fields +give the dimensions of the text at its design size, and the remaining six +words give a transformation to be applied to the text. The |new_text_node| +function initializes everything to default values so that the text comes out +black with its reference point at the origin. + +@d text_p(#)==link(#+1) {a string pointer for the text to display} +@d font_n(#)==info(#+1) {the font number} +@d width_val(#)==mem[#+5].sc {unscaled width of the text} +@d height_val(#)==mem[#+6].sc {unscaled height of the text} +@d depth_val(#)==mem[#+7].sc {unscaled depth of the text} +@d text_tx_loc(#)==#+8 + {the first of six locations for transformation parameters} +@d tx_val(#)==mem[#+8].sc {$x$ shift amount} +@d ty_val(#)==mem[#+9].sc {$y$ shift amount} +@d txx_val(#)==mem[#+10].sc {|txx| transformation parameter} +@d txy_val(#)==mem[#+11].sc {|txy| transformation parameter} +@d tyx_val(#)==mem[#+12].sc {|tyx| transformation parameter} +@d tyy_val(#)==mem[#+13].sc {|tyy| transformation parameter} +@d text_trans_part(#)==mem[#+8-x_part].sc + {interpret a text node ponter that has been offset by |x_part..yy_part|} +@d text_node_size=14 +@d text_code=3 + +@p @<Declare text measuring subroutines@>@; +function new_text_node(f,s:str_number):pointer; + {make a text node for font |f| and text string |s|} +var @!t:pointer; {the new node} +begin t:=get_node(text_node_size); + type(t):=text_code; + text_p(t):=s; + font_n(t):=find_font(f); {this identifies the font} + red_val(t):=0; + green_val(t):=0; + blue_val(t):=0; + tx_val(t):=0; ty_val(t):=0; + txx_val(t):=unity; txy_val(t):=0; + tyx_val(t):=0; tyy_val(t):=unity; + set_text_box(t); {this finds the bounding box} + new_text_node:=t; +end; + +@ The last two types of graphical objects that can occur in an edge structure +are clipping paths and \&{setbounds} paths. These are slightly more difficult +@:set_bounds_}{\&{setbounds} primitive@> +to implement because we must keep track of exactly what is being clipped or +bounded when pictures get merged together. For this reason, each clipping or +\&{setbounds} operation is represented by a pair of nodes: first comes a +two-word node whose |path_p| gives the relevant path, then there is the list +of objects to clip or bound followed by a two-word node whose second word is +unused. + +Using at least two words for each graphical object node allows them all to be +allocated and deallocated similarly with a global array |gr_object_size| to +give the size in words for each object type. + +@d start_clip_size=2 +@d start_clip_code=4 {|type| of a node that starts clipping} +@d start_bounds_size=2 +@d start_bounds_code=5 {|type| of a node that gives a \&{setbounds} path} +@d stop_clip_size=2 {the second word is not used here} +@d stop_clip_code=6 {|type| of a node that stops clipping} +@d stop_bounds_size=2 {the second word is not used here} +@d stop_bounds_code=7 {|type| of a node that stops \&{setbounds}} +@# +@d stop_type(#)==(#+2) + {matching |type| for |start_clip_code| or |start_bounds_code|} +@d has_color(#)==(type(#)<start_clip_code) + {does a graphical object have color fields?} +@d has_pen(#)==(type(#)<text_code) + {does a graphical object have a |pen_p| field?} +@d is_start_or_stop(#)==(type(#)>=start_clip_code) +@d is_stop(#)==(type(#)>=stop_clip_code) + +@p function new_bounds_node(@!p:pointer; c:small_number):pointer; + {make a node of type |c| where |p| is the clipping or \&{setbounds} path} +var @!t:pointer; {the new node} +begin t:=get_node(gr_object_size[c]); + type(t):=c; + path_p(t):=p; + new_bounds_node:=t; +end; + +@ We need an array to keep track of the sizes of graphical objects. + +@<Glob...@>= +gr_object_size: array[fill_code..stop_bounds_code] of small_number; + +@ @<Set init...@>= +gr_object_size[fill_code]:=fill_node_size; +gr_object_size[stroked_code]:=stroked_node_size; +gr_object_size[text_code]:=text_node_size; +gr_object_size[start_clip_code]:=start_clip_size; +gr_object_size[stop_clip_code]:=stop_clip_size; +gr_object_size[start_bounds_code]:=start_bounds_size; +gr_object_size[stop_bounds_code]:=stop_bounds_size; + +@ All the essential information in an edge structure is encoded as a linked list +of graphical objects as we have just seen, but it is helpful to add some +redundant information. A single edge structure might be used as a dash pattern +many times, and it would be nice to avoid scanning the same structure +repeatedly. Thus, an edge structure known to be a suitable dash pattern +has a header that gives a list of dashes in a sorted order designed for rapid +translation into \ps. + +Each dash is represented by a three-word node containing the initial and final +$x$~coordinates as well as the usual |link| field. The |link| fields points to +the dash node with the next higher $x$-coordinates and the final link points +to a special location called |null_dash|. (There should be no overlap between +dashes). Since the $y$~coordinate of the dash pattern is needed to determine +the period of repetition, this needs to be stored in the edge header along +with a pointer to the list of dash nodes. + +@d start_x(#)==mem[#+1].sc {the starting $x$~coordinate in a dash node} +@d stop_x(#)==mem[#+2].sc {the ending $x$~coordinate in a dash node} +@d dash_node_size=3 +@d dash_list==link + {in an edge header this points to the first dash node} +@d dash_y(#)==mem[#+1].sc {$y$ value for the dash list in an edge header} + +@ It is also convenient for an edge header to contain the bounding +box information needed by the \&{llcorner} and \&{urcorner} operators +so that this does not have to be recomputed unnecessarily. This is done by +adding fields for the $x$~and $y$ extremes as well as a pointer that indicates +how far the bounding box computation has gotten. Thus if the user asks for +the bounding box and then adds some more text to the picture before asking +for more bounding box information, the second computation need only look at +the additional text. + +When the bounding box has not been computed, the |bblast| pointer points +to a dummy link at the head of the graphical object list while the |minx_val| +and |miny_val| fields contain |el_gordo| and the |maxx_val| and |maxy_val| +fields contain |-el_gordo|. + +Since the bounding box of pictures containing objects of type +|start_bounds_code| depends on the value of \&{truecorners}, the bounding box +@:true_corners_}{\&{truecorners} primitive@> +data might not be valid for all values of this parameter. Hence, the |bbtype| +field is needed to keep track of this. + +@d minx_val(#)==mem[#+2].sc +@d miny_val(#)==mem[#+3].sc +@d maxx_val(#)==mem[#+4].sc +@d maxy_val(#)==mem[#+5].sc +@d bblast(#)==link(#+6) {last item considered in bounding box computation} +@d bbtype(#)==info(#+6) {tells how bounding box data depends on \&{truecorners}} +@d dummy_loc(#)==#+7 {where the object list begins in an edge header} +@d no_bounds=0 + {|bbtype| value when bounding box data is valid for all \&{truecorners} values} +@d bounds_set=1 + {|bbtype| value when bounding box data is for \&{truecorners}${}\le 0$} +@d bounds_unset=2 + {|bbtype| value when bounding box data is for \&{truecorners}${}>0$} + +@p procedure init_bbox(@!h:pointer); + {Initialize the bounding box information in edge structure |h|} +begin bblast(h):=dummy_loc(h); +bbtype(h):=no_bounds; +minx_val(h):=el_gordo; +miny_val(h):=el_gordo; +maxx_val(h):=-el_gordo; +maxy_val(h):=-el_gordo; +end; + +@ The only other entries in an edge header are a reference count in the first +word and a pointer to the tail of the object list in the last word. + +@d obj_tail(#)==info(#+7) {points to the last entry in the object list} +@d edge_header_size=8 + +@p procedure init_edges(@!h:pointer); + {initialize an edge header to null values} +begin dash_list(h):=null_dash; +obj_tail(h):=dummy_loc(h); +link(dummy_loc(h)):=null; +ref_count(h):=null; +init_bbox(h); +end; + +@ Here is how edge structures are deleted. The process can be recursive because +of the need to dereference edge structures that are used as dash patterns. +@^recursion@> + +@d add_edge_ref(#)==incr(ref_count(#)) +@d delete_edge_ref(#)==if ref_count(#)=null then toss_edges(#) + else decr(ref_count(#)) + +@<Declare the recycling subroutines@>= +@<Declare subroutines needed by |toss_edges|@>@; +procedure toss_edges(@!h:pointer); +var @!p,@!q:pointer; {pointers that scan the list being recycled} +@!r:pointer; {an edge structure that object |p| refers to} +begin flush_dash_list(h); +q:=link(dummy_loc(h)); +while (q<>null) do + begin p:=q; q:=link(q); + r:=toss_gr_object(p); + if r<>null then delete_edge_ref(r); + end; +free_node(h,edge_header_size); +end; + +@ @<Declare subroutines needed by |toss_edges|@>= +procedure flush_dash_list(h:pointer); +var @!p,@!q:pointer; {pointers that scan the list being recycled} +begin q:=dash_list(h); +while q<>null_dash do + begin p:=q; q:=link(q); + free_node(p,dash_node_size); + end; +dash_list(h):=null_dash; +end; + +@ @<Declare subroutines needed by |toss_edges|@>= +function toss_gr_object(@!p:pointer):pointer; + {returns an edge structure that needs to be dereferenced} +var @!e:pointer; {the edge structure to return} +begin e:=null; +@<Prepare to recycle graphical object |p|@>; +free_node(p,gr_object_size[type(p)]);@/ +toss_gr_object:=e; +end; + +@ @<Prepare to recycle graphical object |p|@>= +case type(p) of +fill_code: begin toss_knot_list(path_p(p)); + if pen_p(p)<>null then toss_knot_list(pen_p(p)); + end; +stroked_code: begin toss_knot_list(path_p(p)); + if pen_p(p)<>null then toss_knot_list(pen_p(p)); + e:=dash_p(p); + end; +text_code: delete_str_ref(text_p(p)); +start_clip_code,start_bounds_code: toss_knot_list(path_p(p)); +stop_clip_code,stop_bounds_code: do_nothing; +end; {there are no other cases} + +@ If we use |add_edge_ref| to ``copy'' edge structures, the real copying needs +to be done before making a significant change to an edge structure. Much of +the work is done in a separate routine |copy_objects| that copies a list of +graphical objects into a new edge header. + +@p @<Declare a function called |copy_objects|@>@; +function private_edges(h:pointer):pointer; + {make a private copy of the edge structure headed by |h|} +var @!hh:pointer; {the edge header for the new copy} + @!p,@!pp: pointer; {pointers for copying the dash list} +begin if ref_count(h)=null then private_edges:=h +else begin decr(ref_count(h)); + hh:=copy_objects(link(dummy_loc(h)),null); + @<Copy the dash list from |h| to |hh|@>; + @<Copy the bounding box information from |h| to |hh| and make |bblast(hh)| + point into the new object list@>; + private_edges:=hh; + end; +end; + +@ Here we use the fact that |dash_list(hh)=link(hh)|. +@^data structure assumptions@> + +@<Copy the dash list from |h| to |hh|@>= +pp:=hh; p:=dash_list(h); +while (p<>null_dash) do + begin link(pp):=get_node(dash_node_size); + pp:=link(pp);@/ + start_x(pp):=start_x(p); + stop_x(pp):=stop_x(p); + p:=link(p); + end; +link(pp):=null_dash; +dash_y(hh):=dash_y(h) + +@ @<Copy the bounding box information from |h| to |hh|...@>= +minx_val(hh):=minx_val(h); +miny_val(hh):=miny_val(h); +maxx_val(hh):=maxx_val(h); +maxy_val(hh):=maxy_val(h);@/ +bbtype(hh):=bbtype(h); +p:=dummy_loc(h); pp:=dummy_loc(hh); +while(p<>bblast(h)) do + begin if p=null then confusion("bblast"); +@:this can't happen bblast}{\quad bblast@> + p:=link(p); pp:=link(pp); + end; +bblast(hh):=pp + +@ Here is the promised routine for copying graphical objects into a new edge +structure. It starts copying at object~|p| and stops just before object~|q|. +If |q| is null, it copies the entire sublist headed at |p|. The resulting edge +structure requires further initialization by |init_bbox|. + +@<Declare a function called |copy_objects|@>= +function copy_objects(p, q:pointer):pointer; +var @!hh: pointer; {the new edge header} + @!pp:pointer; {the last newly copied object} + @!k:small_number; {temporary register} +begin hh:=get_node(edge_header_size); +dash_list(hh):=null_dash; +ref_count(hh):=null;@/ +pp:=dummy_loc(hh); +while (p<>q) do + @<Make |link(pp)| point to a copy of object |p|, and update |p| and |pp|@>; +obj_tail(hh):=pp; +link(pp):=null; +copy_objects:=hh; +end; + +@ @<Make |link(pp)| point to a copy of object |p|, and update |p| and |pp|@>= +begin k:=gr_object_size[type(p)];@/ +link(pp):=get_node(k); +pp:=link(pp); +while (k>0) do + begin decr(k); mem[pp+k]:=mem[p+k]; @+end; +@<Fix anything in graphical object |pp| that should differ from the + corresponding field in |p|@>; +p:=link(p); +end + +@ @<Fix anything in graphical object |pp| that should differ from the...@>= +case type(p) of +start_clip_code,start_bounds_code: path_p(pp):=copy_path(path_p(p)); +fill_code: begin path_p(pp):=copy_path(path_p(p)); + if pen_p(p)<>null then pen_p(pp):=copy_pen(pen_p(p)); + end; +stroked_code: begin path_p(pp):=copy_path(path_p(p)); + pen_p(pp):=copy_pen(pen_p(p)); + if dash_p(p)<>null then add_edge_ref(dash_p(pp)); + end; +text_code: add_str_ref(text_p(pp)); +stop_clip_code,stop_bounds_code: do_nothing; +end {there are no other cases} + +@ Here is one way to find an acceptable value for the second argument to +|copy_objects|. Given a non-null graphical object list, |skip_1component| +skips past one picture component, where a ``picture component'' is a single +graphical object, or a start bounds or start clip object and everything up +through the matching stop bounds or stop clip object. The macro version avoids +procedure call overhead and error handling: |skip_component(p)(e)| advances |p| +unless |p| points to a stop bounds or stop clip node, in which case it executes +|e| instead. + +@d skip_component(#)==if not is_start_or_stop(#) then #:=link(#) + else if not is_stop(#) then #:=skip_1component(#) + else skipc_end +@d skipc_end(#)==# + +@p function skip_1component(p:pointer):pointer; +var @!lev:integer; {current nesting level} +begin lev:=0; +repeat if is_start_or_stop(p) then + if is_stop(p) then decr(lev) @+else incr(lev); +p:=link(p); +until lev=0; +skip_1component:=p; +end; + +@ Here is a diagnostic routine for printing an edge structure in symbolic form. + +@<Declare subroutines for printing expressions@>= +@<Declare subroutines needed by |print_edges|@>@; +procedure print_edges(@!h:pointer;@!s:str_number;@!nuline:boolean); +var @!p:pointer; {a graphical object to be printed} +@!hh,@!pp:pointer; {temporary pointers} +@!scf:scaled; {a scale factor for the dash pattern} +@!ok_to_dash:boolean; {|false| for polygonal pen strokes} +begin print_diagnostic("Edge structure",s,nuline); +p:=dummy_loc(h); +while link(p)<>null do + begin p:=link(p); + print_ln; + case type(p) of + @<Cases for printing graphical object node |p|@>@; + othercases begin print("[unknown object type!]"); + end + endcases;@/ + end; +print_nl("End edges"); +if p<>obj_tail(h) then print("?"); +@.End edges?@> +end_diagnostic(true); +end; + +@ @<Cases for printing graphical object node |p|@>= +fill_code: begin print("Filled contour "); + print_obj_color(p); + print_char(":"); print_ln; + pr_path(path_p(p)); print_ln; + if (pen_p(p)<>null) then + begin @<Print join type for graphical object |p|@>; + print(" with pen"); print_ln; + pr_pen(pen_p(p)); + end; + end; + +@ @<Print join type for graphical object |p|@>= +case ljoin_val(p) of +0:begin print("mitered joins limited "); + print_scaled(miterlim_val(p)); + end; +1:print("round joins"); +2:print("beveled joins"); +othercases print("?? joins"); +@.??@> +endcases + +@ For stroked nodes, we need to print |lcap_val(p)| as well. + +@<Print join and cap types for stroked node |p|@>= +case lcap_val(p) of +0:print("butt"); +1:print("round"); +2:print("square"); +othercases print("??") +@.??@> +endcases; +print(" ends, "); +@<Print join type for graphical object |p|@> + +@ Here is a routine that prints the color of a graphical object if it isn't +black (the default color). + +@<Declare subroutines needed by |print_edges|@>= +@<Declare a procedure called |print_compact_node|@>@; +procedure print_obj_color(@!p:pointer); +begin if (red_val(p)>0) or (green_val(p)>0) or (blue_val(p)>0) then + begin print("colored "); + print_compact_node(obj_red_loc(p),3); + end; +end; + +@ We also need a procedure for printing consecutive scaled values as if they +were a known big node. + +@<Declare a procedure called |print_compact_node|@>= +procedure print_compact_node(@!p:pointer;k:small_number); +var @!q:pointer; {last location to print} +begin q:=p+k-1; +print_char("("); +while p<=q do + begin print_scaled(mem[p].sc); + if p<q then print_char(","); + incr(p); + end; +print_char(")"); +end; + +@ @<Cases for printing graphical object node |p|@>= +stroked_code: begin print("Filled pen stroke "); + print_obj_color(p); + print_char(":"); print_ln; + pr_path(path_p(p)); + if dash_p(p)<>null then + begin print_nl("dashed ("); + @<Finish printing the dash pattern that |p| refers to@>; + end; + print_ln; + @<Print join and cap types for stroked node |p|@>; + print(" with pen"); print_ln; + if pen_p(p)=null then print("???") {shouldn't happen} +@.???@> + else pr_pen(pen_p(p)); + end; + +@ Normally, the |dash_list| field in an edge header is set to |null_dash| +when it is not known to define a suitable dash pattern. This is disallowed +here because the |dash_p| field should never point to such an edge header. +Note that memory is allocated for |start_x(null_dash)| and we are free to +give it any convenient value. + +@<Finish printing the dash pattern that |p| refers to@>= +ok_to_dash:=pen_is_elliptical(pen_p(p)); +if not ok_to_dash then scf:=unity +else scf:=dash_scale(p); +hh:=dash_p(p); +pp:=dash_list(hh); +if (pp=null_dash) or (dash_y(hh)<0) then print(" ??") +else begin start_x(null_dash):=start_x(pp)+dash_y(hh); + while pp<>null_dash do + begin print("on "); + print_scaled(take_scaled(stop_x(pp)-start_x(pp),scf)); + print(" off "); + print_scaled(take_scaled(start_x(link(pp))-stop_x(pp),scf)); + pp := link(pp); + if pp<>null_dash then print_char(" "); + end; + print(") shifted "); + print_scaled(-take_scaled(dash_offset(hh),scf)); + if not ok_to_dash or (dash_y(hh)=0) then print(" (this will be ignored)"); + end + +@ @<Declare subroutines needed by |print_edges|@>= +function dash_offset(h:pointer):scaled; +var @!x:scaled; {the answer} +begin if (dash_list(h)=null_dash) or (dash_y(h)<0) then confusion("dash0"); +@:this can't happen dash0}{\quad dash0@> +if dash_y(h)=0 then x:=0 +else begin x:=-(start_x(dash_list(h)) mod dash_y(h)); + if x<0 then x:=x+dash_y(h); + end; +dash_offset:=x; +end; + +@ @<Cases for printing graphical object node |p|@>= +text_code: begin print_char(""""); print(text_p(p)); + print(""" infont """); print(font_name[font_n(p)]); + print_char(""""); print_ln; + print_obj_color(p); + print("transformed "); + print_compact_node(text_tx_loc(p),6); +end; + +@ @<Cases for printing graphical object node |p|@>= +start_clip_code: begin print("clipping path:"); + print_ln; + pr_path(path_p(p)); + end; +stop_clip_code: print("stop clipping"); + +@ @<Cases for printing graphical object node |p|@>= +start_bounds_code: begin print("setbounds path:"); + print_ln; + pr_path(path_p(p)); + end; +stop_bounds_code: print("end of setbounds"); + +@ To initialize the |dash_list| field in an edge header~|h|, we need a +subroutine that scans an edge structure and tries to interpret it as a dash +pattern. This can only be done when there are no filled regions or clipping +paths and all the pen strokes have the same color. The first step is to let +$y_0$ be the initial $y$~coordinate of the first pen stroke. Then we implicitly +project all the pen stroke paths onto the line $y=y_0$ and require that there +be no retracing. If the resulting paths cover a range of $x$~coordinates of +length $\Delta x$, we set |dash_y(h)| to the length of the dash pattern by +finding the maximum of $\Delta x$ and the absolute value of~$y_0$. + +@p @<Declare a procedure called |x_retrace_error|@>@; +function make_dashes(h:pointer):pointer; {returns |h| or |null|} +label exit, found, not_found; +var @!p:pointer; {this scans the stroked nodes in the object list} +@!y0:scaled; {the initial $y$ coordinate} +@!p0:pointer; {if not |null| this points to the first stroked node} +@!pp,@!qq,@!rr:pointer; {pointers into |path_p(p)|} +@!d,@!dd:pointer; {pointers used to create the dash list} +@<Other local variables in |make_dashes|@>@; +begin if dash_list(h)<>null_dash then goto found; +p0:=null; +p:=link(dummy_loc(h)); +while p<>null do + begin if type(p)<>stroked_code then + @<Compain that the edge structure contains a node of the wrong type + and |goto not_found|@>; + pp:=path_p(p); + if p0=null then + begin p0:=p; y0:=y_coord(pp); @+end; + @<Make |d| point to a new dash node created from stroke |p| and path |pp| + or |goto not_found| if there is an error@>; + @<Insert |d| into the dash list and |goto not_found| if there is an error@>; + p:=link(p); + end; +if dash_list(h)=null_dash then goto not_found; {No error message} +@<Scan |dash_list(h)| and deal with any dashes that are themselves dashed@>; +@<Set |dash_y(h)| and merge the first and last dashes if necessary@>; +found:make_dashes:=h; return; +not_found: @<Flush the dash list, recycle |h| and return |null|@>; +exit:end; + +@ @<Compain that the edge structure contains a node of the wrong type...@>= +begin print_err("Picture is too complicated to use as a dash pattern"); +help3("When you say `dashed p', picture p should not contain any")@/ + ("text, filled regions, or clipping paths. This time it did")@/ + ("so I'll just make it a solid line instead.");@/ +put_get_error; +goto not_found; +end + +@ A similar error occurs when monotonicity fails. + +@<Declare a procedure called |x_retrace_error|@>= +procedure x_retrace_error; +begin print_err("Picture is too complicated to use as a dash pattern"); +help3("When you say `dashed p', every path in p should be monotone")@/ + ("in x and there must be no overlapping. This failed")@/ + ("so I'll just make it a solid line instead."); +put_get_error; +end; + +@ We stash |p| in |info(d)| if |dash_p(p)<>0| so that subsequent processing can +handle the case where the pen stroke |p| is itself dashed. + +@<Make |d| point to a new dash node created from stroke |p| and path...@>= +@<Make sure |p| and |p0| are the same color and |goto not_found| if there is + an error@>; +rr:=pp; +if link(pp)<>pp then + repeat qq:=rr; rr:=link(rr); + @<Check for retracing between knots |qq| and |rr| and |goto not_found| + if there is a problem@>; + until right_type(rr)=endpoint; +d:=get_node(dash_node_size); +if dash_p(p)=0 then info(d):=0 @+else info(d):=p; +if x_coord(pp)<x_coord(rr) then + begin start_x(d):=x_coord(pp); + stop_x(d):=x_coord(rr); + end +else begin start_x(d):=x_coord(rr); + stop_x(d):=x_coord(pp); + end; + +@ We also need to check for the case where the segment from |qq| to |rr| is +monotone in $x$ but is reversed relative to the path from |pp| to |qq|. + +@<Check for retracing between knots |qq| and |rr| and |goto not_found|...@>= +x0:=x_coord(qq); +x1:=right_x(qq); +x2:=left_x(rr); +x3:=x_coord(rr); +if (x0>x1) or (x1>x2) or (x2>x3) then + if (x0<x1) or (x1<x2) or (x2<x3) then + if ab_vs_cd(x2-x1,x2-x1,x1-x0,x3-x2)>0 then + begin x_retrace_error; goto not_found; + end; +if (x_coord(pp)>x0) or (x0>x3) then + if (x_coord(pp)<x0) or (x0<x3) then + begin x_retrace_error; goto not_found; + end + +@ @<Other local variables in |make_dashes|@>= +@!x0,@!x1,@!x2,@!x3:scaled; {$x$ coordinates of the segment from |qq| to |rr|} + +@ @<Make sure |p| and |p0| are the same color and |goto not_found|...@>= +if (red_val(p)<>red_val(p0)) or@| + (green_val(p)<>green_val(p0)) or (blue_val(p)<>blue_val(p0)) then + begin print_err("Picture is too complicated to use as a dash pattern"); + help3("When you say `dashed p', everything in picture p should")@/ + ("be the same color. I can't handle your color changes")@/ + ("so I'll just make it a solid line instead.");@/ + put_get_error; + goto not_found; + end + +@ @<Insert |d| into the dash list and |goto not_found| if there is an error@>= +start_x(null_dash):=stop_x(d); +dd:=h; {this makes |link(dd)=dash_list(h)|} +while start_x(link(dd))<stop_x(d) do + dd:=link(dd); +if dd<>h then + if (stop_x(dd)>start_x(d)) then + begin x_retrace_error; goto not_found; @+end; +link(d):=link(dd); +link(dd):=d + +@ @<Set |dash_y(h)| and merge the first and last dashes if necessary@>= +d:=dash_list(h); +while (link(d)<>null_dash) do + d:=link(d); +dd:=dash_list(h); +dash_y(h):=stop_x(d)-start_x(dd); +if abs(y0)>dash_y(h) then + dash_y(h):=abs(y0) +else if d<>dd then + begin dash_list(h):=link(dd); + stop_x(d):=stop_x(dd)+dash_y(h); + free_node(dd,dash_node_size); + end + +@ We get here when the argument is a null picture or when there is an error. +Recovering from an error involves making |dash_list(h)| empty to indicate +that |h| is not known to be a valid dash pattern. We also dereference |h| +since it is not being used for the return value. + +@<Flush the dash list, recycle |h| and return |null|@>= +flush_dash_list(h); +delete_edge_ref(h); +make_dashes:=null + +@ Having carefully saved the dashed stroked nodes in the +corresponding dash nodes, we must be prepared to break up these dashes into +smaller dashes. + +@<Scan |dash_list(h)| and deal with any dashes that are themselves dashed@>= +d:=h; {now |link(d)=dash_list(h)|} +while link(d)<>null_dash do + begin ds:=info(link(d)); + if ds=null then d:=link(d) + else begin + hh:=dash_p(ds); + hsf:=dash_scale(ds); + if (hh=null) then confusion("dash1"); +@:this can't happen dash0}{\quad dash1@> + if dash_y(hh)=0 then d:=link(d) + else begin if dash_list(hh)=null then confusion("dash1"); +@:this can't happen dash0}{\quad dash1@> + @<Replace |link(d)| by a dashed version as determined by edge header + |hh| and scale factor |ds|@>; + end; + end; + end + +@ @<Other local variables in |make_dashes|@>= +@!dln:pointer; {|link(d)|} +@!hh:pointer; {an edge header that tells how to break up |dln|} +@!hsf:scaled; {the dash pattern from |hh| gets scaled by this} +@!ds:pointer; {the stroked node from which |hh| and |hsf| are derived} +@!xoff:scaled; {added to $x$ values in |dash_list(hh)| to match |dln|} + +@ @<Replace |link(d)| by a dashed version as determined by edge header...@>= +dln:=link(d); +dd:=dash_list(hh); +xoff:=start_x(dln)-take_scaled(hsf,start_x(dd))- + take_scaled(hsf,dash_offset(hh)); +start_x(null_dash):=take_scaled(hsf,start_x(dd))+take_scaled(hsf,dash_y(hh)); +stop_x(null_dash):=start_x(null_dash); +@<Advance |dd| until finding the first dash that overlaps |dln| when + offset by |xoff|@>; +while start_x(dln)<=stop_x(dln) do + begin @<If |dd| has `fallen off the end', back up to the beginning and fix + |xoff|@>; + @<Insert a dash between |d| and |dln| for the overlap with the offset version + of |dd|@>; + dd:=link(dd); + start_x(dln):=xoff+take_scaled(hsf,start_x(dd)); + end; +link(d):=link(dln); +free_node(dln,dash_node_size) + +@ The name of this module is a bit of a lie because we actually just find the +first |dd| where |take_scaled(hsf,stop_x(dd))| is large enough to make an +overlap possible. It could be that the unoffset version of dash |dln| falls +in the gap between |dd| and its predecessor. + +@<Advance |dd| until finding the first dash that overlaps |dln| when...@>= +while xoff+take_scaled(hsf,stop_x(dd))<start_x(dln) do + dd:=link(dd) + +@ @<If |dd| has `fallen off the end', back up to the beginning and fix...@>= +if dd=null_dash then + begin dd:=dash_list(hh); + xoff:=xoff+take_scaled(hsf,dash_y(hh)); + end + +@ At this point we already know that +|start_x(dln)<=xoff+take_scaled(hsf,stop_x(dd))|. + +@<Insert a dash between |d| and |dln| for the overlap with the offset...@>= +if xoff+take_scaled(hsf,start_x(dd))<=stop_x(dln) then + begin link(d):=get_node(dash_node_size); + d:=link(d); + link(d):=dln; + if start_x(dln)>xoff+take_scaled(hsf,start_x(dd)) + then start_x(d):=start_x(dln) + else start_x(d):=xoff+take_scaled(hsf,start_x(dd)); + if stop_x(dln)<xoff+take_scaled(hsf,stop_x(dd)) then stop_x(d):=stop_x(dln) + else stop_x(d):=xoff+take_scaled(hsf,stop_x(dd)); + end + +@ The next major task is to update the bounding box information in an edge +header~|h|. This is done via a procedure |adjust_bbox| that enlarges an edge +header's bounding box to accommodate the box computed by |path_bbox| or +|pen_bbox|. (This is stored in global variables |minx|, |miny|, |maxx|, and +|maxy|.) + +@p procedure adjust_bbox(h:pointer); +begin if minx<minx_val(h) then minx_val(h):=minx; +if miny<miny_val(h) then miny_val(h):=miny; +if maxx>maxx_val(h) then maxx_val(h):=maxx; +if maxy>maxy_val(h) then maxy_val(h):=maxy; +end; + +@ Here is a special routine for updating the bounding box information in +edge header~|h| to account for the squared-off ends of a non-cyclic path~|p| +that is to be stroked with the pen~|pp|. + +@p procedure box_ends(@!p, @!pp, @!h:pointer); +label exit; +var @!q:pointer; {a knot node adjacent to knot |p|} +@!dx,@!dy:fraction; {a unit vector in the direction out of the path at~|p|} +@!d:scaled; {a factor for adjusting the length of |(dx,dy)|} +@!z:scaled; {a coordinate being tested against the bounding box} +@!xx,@!yy:scaled; {the extreme pen vertex in the |(dx,dy)| direction} +@!i:integer; {a loop counter} +begin if right_type(p)<>endpoint then + begin q:=link(p); + loop @+begin @<Make |(dx,dy)| the final direction for the path segment from + |q| to~|p|; set~|d|@>; + d:=pyth_add(dx,dy); + if d>0 then + begin @<Normalize the direction |(dx,dy)| and find the pen offset + |(xx,yy)|@>; + for i:=1 to 2 do + begin @<Use |(dx,dy)| to generate a vertex of the square end cap and + update the bounding box to accommodate it@>;@/ + dx:=-dx; dy:=-dy; + end; + end; + if right_type(p)=endpoint then return + else @<Advance |p| to the end of the path and make |q| the previous knot@>; + end; + end; +exit: ; +end; + +@ @<Make |(dx,dy)| the final direction for the path segment from...@>= +if q=link(p) then + begin dx:=x_coord(p)-right_x(p); + dy:=y_coord(p)-right_y(p); + if (dx=0)and(dy=0) then + begin dx:=x_coord(p)-left_x(q); + dy:=y_coord(p)-left_y(q); + end; + end +else begin dx:=x_coord(p)-left_x(p); + dy:=y_coord(p)-left_y(p); + if (dx=0)and(dy=0) then + begin dx:=x_coord(p)-right_x(q); + dy:=y_coord(p)-right_y(q); + end; + end; +dx:=x_coord(p)-x_coord(q); +dy:=y_coord(p)-y_coord(q) + +@ @<Normalize the direction |(dx,dy)| and find the pen offset |(xx,yy)|@>= +dx:=make_fraction(dx,d); +dy:=make_fraction(dy,d);@/ +find_offset(-dy,dx,pp); +xx:=cur_x; yy:=cur_y + +@ @<Use |(dx,dy)| to generate a vertex of the square end cap and...@>= +find_offset(dx,dy,pp); +d:=take_fraction(xx-cur_x,dx)+take_fraction(yy-cur_y,dy); +if (d<0)and(i=1) or (d>0)and(i=2) then confusion("box_ends"); +@:this can't happen box ends}{\quad\\{box_ends}@> +z:=x_coord(p)+cur_x+take_fraction(d,dx); +if z<minx_val(h) then minx_val(h):=z; +if z>maxx_val(h) then maxx_val(h):=z; +z:=y_coord(p)+cur_y+take_fraction(d,dy); +if z<miny_val(h) then miny_val(h):=z; +if z>maxy_val(h) then maxy_val(h):=z + +@ @<Advance |p| to the end of the path and make |q| the previous knot@>= +repeat q:=p; +p:=link(p); +until right_type(p)=endpoint + +@ The major difficulty in finding the bounding box of an edge structure is the +effect of clipping paths. We treat them conservatively by only clipping to the +clipping path's bounding box, but this still +requires recursive calls to |set_bbox| in order to find the bounding box of +@^recursion@> +the objects to be clipped. Such calls are distinguished by the fact that the +boolean parameter |top_level| is false. + +@p procedure set_bbox(@!h:pointer;top_level:boolean); +label exit; +var @!p:pointer; {a graphical object being considered} +@!sminx,@!sminy,@!smaxx,@!smaxy:scaled; + {for saving the bounding box during recursive calls} +@!x0,@!x1,@!y0,@!y1:scaled; {temporary registers} +@!lev:integer; {nesting level for |start_bounds_code| nodes} +begin @<Wipe out any existing bounding box information if |bbtype(h)| is + incompatible with |internal[true_corners]|@>; +while link(bblast(h))<>null do + begin p:=link(bblast(h)); + bblast(h):=p; + case type(p) of + stop_clip_code: if top_level then confusion("bbox") @+else return; +@:this can't happen bbox}{\quad bbox@> + @<Other cases for updating the bounding box based on the type of object |p|@>@; + end; {all cases are enumerated above} + end; +if not top_level then confusion("bbox"); +exit:end; + +@ @<Wipe out any existing bounding box information if |bbtype(h)| is...@>= +case bbtype(h) of +no_bounds: do_nothing; +bounds_set: if internal[true_corners]>0 then init_bbox(h); +bounds_unset: if internal[true_corners]<=0 then init_bbox(h); +end {there are no other cases} + +@ @<Other cases for updating the bounding box...@>= +fill_code: begin path_bbox(path_p(p)); + if pen_p(p)<>null then + begin x0:=minx; y0:=miny; + x1:=maxx; y1:=maxy; + pen_bbox(pen_p(p)); + minx:=minx+x0; + miny:=miny+y0; + maxx:=maxx+x1; + maxy:=maxy+y1; + end; + adjust_bbox(h); + end; + +@ @<Other cases for updating the bounding box...@>= +start_bounds_code: if internal[true_corners]>0 then bbtype(h):=bounds_unset + else begin bbtype(h):=bounds_set; + path_bbox(path_p(p)); + adjust_bbox(h); + @<Scan to the matching |stop_bounds_code| node and update |p| and + |bblast(h)|@>; + end; +stop_bounds_code: if internal[true_corners]<=0 then confusion("bbox2"); +@:this can't happen bbox2}{\quad bbox2@> + +@ @<Scan to the matching |stop_bounds_code| node and update |p| and...@>= +lev:=1; +while lev<>0 do + begin if link(p)=null then confusion("bbox2"); +@:this can't happen bbox2}{\quad bbox2@> + p:=link(p); + if type(p)=start_bounds_code then incr(lev) + else if type(p)=stop_bounds_code then decr(lev); + end; +bblast(h):=p + +@ It saves a lot of grief here to be slightly conservative and not account for +omitted parts of dashed lines. We also don't worry about the material omitted +when using butt end caps. The basic computation is for round end caps and +|box_ends| augments it for square end caps. + +@<Other cases for updating the bounding box...@>= +stroked_code: begin path_bbox(path_p(p)); + x0:=minx; y0:=miny; + x1:=maxx; y1:=maxy; + pen_bbox(pen_p(p)); + minx:=minx+x0; + miny:=miny+y0; + maxx:=maxx+x1; + maxy:=maxy+y1; + adjust_bbox(h); + if (left_type(path_p(p))=endpoint)and(lcap_val(p)=2) then + box_ends(path_p(p), pen_p(p), h); + end; + +@ The height width and depth information stored in a text node determines a +rectangle that needs to be transformed according to the transformation +parameters stored in the text node. + +@<Other cases for updating the bounding box...@>= +text_code: begin x1:=take_scaled(txx_val(p),width_val(p)); + y0:=take_scaled(txy_val(p),-depth_val(p)); + y1:=take_scaled(txy_val(p),height_val(p)); + minx:=tx_val(p); + maxx:=minx; + if y0<y1 then + begin minx:=minx+y0; maxx:=maxx+y1; @+end + else begin minx:=minx+y1; maxx:=maxx+y0; @+end; + if x1<0 then minx:=minx+x1 @+else maxx:=maxx+x1; + x1:=take_scaled(tyx_val(p),width_val(p)); + y0:=take_scaled(tyy_val(p),-depth_val(p)); + y1:=take_scaled(tyy_val(p),height_val(p)); + miny:=ty_val(p); + maxy:=miny; + if y0<y1 then + begin miny:=miny+y0; maxy:=maxy+y1; @+end + else begin miny:=miny+y1; maxy:=maxy+y0; @+end; + if x1<0 then miny:=miny+x1 @+else maxy:=maxy+x1; + adjust_bbox(h); + end; + +@ This case involves a recursive call that advances |bblast(h)| to the node of +type |stop_clip_code| that matches |p|. + +@<Other cases for updating the bounding box...@>= +start_clip_code: begin path_bbox(path_p(p));@/ + x0:=minx; y0:=miny; + x1:=maxx; y1:=maxy;@/ + sminx:=minx_val(h); sminy:=miny_val(h); + smaxx:=maxx_val(h); smaxy:=maxy_val(h);@/ + @<Reinitialize the bounding box in header |h| and call |set_bbox| recursively + starting at |link(p)|@>; + @<Clip the bounding box in |h| to the rectangle given by |x0|, |x1|, + |y0|, |y1|@>; + minx:=sminx; miny:=sminy; + maxx:=smaxx; maxy:=smaxy; + adjust_bbox(h); + end; + +@ @<Reinitialize the bounding box in header |h| and call |set_bbox|...@>= +minx_val(h):=el_gordo; +miny_val(h):=el_gordo; +maxx_val(h):=-el_gordo; +maxy_val(h):=-el_gordo;@/ +set_bbox(h,false) + +@ @<Clip the bounding box in |h| to the rectangle given by |x0|, |x1|,...@>= +if minx_val(h)<x0 then minx_val(h):=x0; +if miny_val(h)<y0 then miny_val(h):=y0; +if maxx_val(h)>x1 then maxx_val(h):=x1; +if maxy_val(h)>y1 then maxy_val(h):=y1 + +@* \[22] Finding an envelope. +When \MP\ has a path and a polygonal pen, it needs to express the desired +shape in terms of things \ps\ can understand. The present task is to compute +a new path that describes the region to be filled. It is convenient to +define this as a two step process where the first step is determining what +offset to use for each segment of the path. + +@ Given a pointer |c| to a cyclic path, +and a pointer~|h| to the first knot of a pen polygon, +the |offset_prep| routine changes the path into cubics that are +associated with particular pen offsets. Thus if the cubic between |p| +and~|q| is associated with the |k|th offset and the cubic between |q| and~|r| +has offset |l| then |info(q)=zero_off+l-k|. (The constant |zero_off| is added +to because |l-k| could be negative.) + +After overwriting the type information with offset differences, we no longer +have a true path so we refer to the knot list returned by |offset_prep| as an +``envelope spec.'' +@!@^envelope spec@> +Since an envelope spec only determines relative changes in pen offsets, +|offset_prep| sets a global variable |spec_offset| to the relative change from +|h| to the first offset. + +@d zero_off=16384 {added to offset changes to make them positive} + +@<Glob...@>= +spec_offset:integer; {number of pen edges between |h| and the initial offset} + +@ @p @t\4@>@<Declare subroutines needed by |offset_prep|@>@; +function offset_prep(@!c,@!h:pointer):pointer; +label not_found; +var @!n:halfword; {the number of vertices in the pen polygon} +@!p,@!q,@!r,@!w,@!ww:pointer; {for list manipulation} +@!k_needed:integer; {amount to be added to |info(p)| when it is computed} +@!w0:pointer; {a pointer to pen offset to use just before |p|} +@!dxin,@!dyin:scaled; {the direction into knot |p|} +@!turn_amt:integer; {change in pen offsets for the current cubic} +@<Other local variables for |offset_prep|@>@; +begin @<Initialize the pen size~|n|@>; +@<Initialize the incoming direction and pen offset at |c|@>; +p:=c; k_needed:=0; +repeat q:=link(p); +@<Split the cubic between |p| and |q|, if necessary, into cubics + associated with single offsets, after which |q| should + point to the end of the final such cubic@>; +@<Advance |p| to node |q|, removing any ``dead'' cubics that + might have been introduced by the splitting process@>; +until q=c; +@<Fix the offset change in |info(c)| and set the return value of + |offset_prep|@>; +end; + +@ We shall want to keep track of where certain knots on the cyclic path +wind up in the envelope spec. It doesn't suffice just to keep pointers to +knot nodes because some nodes are deleted while removing dead cubics. Thus +|offset_prep| updates the following pointers + +@<Glob...@>= +@!spec_p1,@!spec_p2:pointer; {pointers to distinguished knots} + +@ @<Set init...@>= +spec_p1:=null; spec_p2:=null; + +@ @<Initialize the pen size~|n|@>= +n:=0; p:=h; +repeat incr(n); +p:=link(p); +until p=h + +@ Since the true incoming direction isn't known yet, we just pick a direction +consistent with the pen offset~|h|. If this is wrong, it can be corrected +later. + +@<Initialize the incoming direction and pen offset at |c|@>= +dxin:=x_coord(link(h))-x_coord(knil(h)); +dyin:=y_coord(link(h))-y_coord(knil(h)); +if (dxin=0)and(dyin=0) then + begin dxin:=y_coord(knil(h))-y_coord(h); + dyin:=x_coord(h)-x_coord(knil(h)); + end; +w0:=h + +@ We must be careful not to remove the only cubic in a cycle. + +But we must also be careful for another reason. If the user-supplied +path starts with a set of degenerate cubics, these should not be removed +because at this point we cannot do so cleanly. The relevant bug is +tracker id 267, bugs 52c, reported by Boguslav. + +@<Advance |p| to node |q|, removing any ``dead'' cubics...@>= +repeat r:=link(p); +if x_coord(p)=right_x(p) then if y_coord(p)=right_y(p) then + if x_coord(p)=left_x(r) then if y_coord(p)=left_y(r) then + if x_coord(p)=x_coord(r) then if y_coord(p)=y_coord(r) then + if r<>p then if ((r<>q) or (originator(r)<>metapost_user)) then + @<Remove the cubic following |p| and update the data structures + to merge |r| into |p|@>; +p:=r; +until p=q + +@ @<Remove the cubic following |p| and update the data structures...@>= +begin k_needed:=info(p)-zero_off; +if r=q then q:=p +else begin info(p):=k_needed+info(r); + k_needed:=0; + end; +if r=c then + begin info(p):=info(c); c:=p; + end; +if r=spec_p1 then spec_p1:=p; +if r=spec_p2 then spec_p2:=p; +r:=p; remove_cubic(p); +end + +@ Not setting the |info| field of the newly created knot allows the splitting +routine to work for paths. + +@<Declare subroutines needed by |offset_prep|@>= +procedure split_cubic(@!p:pointer;@!t:fraction); {splits the cubic after |p|} +var @!v:scaled; {an intermediate value} +@!q,@!r:pointer; {for list manipulation} +begin q:=link(p); r:=get_node(knot_node_size); link(p):=r; link(r):=q;@/ +originator(r):=program_code;@/ +left_type(r):=explicit; right_type(r):=explicit;@# +v:=t_of_the_way(right_x(p))(left_x(q)); +right_x(p):=t_of_the_way(x_coord(p))(right_x(p)); +left_x(q):=t_of_the_way(left_x(q))(x_coord(q)); +left_x(r):=t_of_the_way(right_x(p))(v); +right_x(r):=t_of_the_way(v)(left_x(q)); +x_coord(r):=t_of_the_way(left_x(r))(right_x(r));@# +v:=t_of_the_way(right_y(p))(left_y(q)); +right_y(p):=t_of_the_way(y_coord(p))(right_y(p)); +left_y(q):=t_of_the_way(left_y(q))(y_coord(q)); +left_y(r):=t_of_the_way(right_y(p))(v); +right_y(r):=t_of_the_way(v)(left_y(q)); +y_coord(r):=t_of_the_way(left_y(r))(right_y(r)); +end; + +@ This does not set |info(p)| or |right_type(p)|. + +@<Declare subroutines needed by |offset_prep|@>= +procedure remove_cubic(@!p:pointer); {removes the dead cubic following~|p|} +var @!q:pointer; {the node that disappears} +begin q:=link(p); link(p):=link(q);@/ +right_x(p):=right_x(q); right_y(p):=right_y(q);@/ +free_node(q,knot_node_size); +end; + +@ Let $d\prec d'$ mean that the counter-clockwise angle from $d$ to~$d'$ is +strictly between zero and $180^\circ$. Then we can define $d\preceq d'$ to +mean that the angle could be zero or $180^\circ$. If $w_k=(u_k,v_k)$ is the +$k$th pen offset, the $k$th pen edge direction is defined by the formula +$$d_k=(u\k-u_k,\,v\k-v_k).$$ +When listed by increasing $k$, these directions occur in counter-clockwise +order so that $d_k\preceq d\k$ for all~$k$. +The goal of |offset_prep| is to find an offset index~|k| to associate with +each cubic, such that the direction $d(t)$ of the cubic satisfies +$$d_{k-1}\preceq d(t)\preceq d_k\qquad\hbox{for $0\le t\le 1$.}\eqno(*)$$ +We may have to split a cubic into many pieces before each +piece corresponds to a unique offset. + +@<Split the cubic between |p| and |q|, if necessary, into cubics...@>= +info(p):=zero_off+k_needed; +k_needed:=0;@/ +@<Prepare for derivative computations; + |goto not_found| if the current cubic is dead@>; +@<Find the initial direction |(dx,dy)|@>; +@<Update |info(p)| and find the offset $w_k$ such that + $d_{k-1}\preceq(\\{dx},\\{dy})\prec d_k$; also advance |w0| for + the direction change at |p|@>; +@<Find the final direction |(dxin,dyin)|@>; +@<Decide on the net change in pen offsets and set |turn_amt|@>; +@<Complete the offset splitting process@>;@/ +w0:=pen_walk(w0,turn_amt); +not_found: do_nothing + +@ @<Declare subroutines needed by |offset_prep|@>= +function pen_walk(@!w:pointer;@!k:integer):pointer; + {walk |k| steps around a pen from |w|} +begin while k>0 do begin w:=link(w); decr(k); @+end; +while k<0 do begin w:=knil(w); incr(k); @+end; +pen_walk:=w; +end; + +@ The direction of a cubic $B(z_0,z_1,z_2,z_3;t)=\bigl(x(t),y(t)\bigr)$ can be +calculated from the quadratic polynomials +${1\over3}x'(t)=B(x_1-x_0,x_2-x_1,x_3-x_2;t)$ and +${1\over3}y'(t)=B(y_1-y_0,y_2-y_1,y_3-y_2;t)$. +Since we may be calculating directions from several cubics +split from the current one, it is desirable to do these calculations +without losing too much precision. ``Scaled up'' values of the +derivatives, which will be less tainted by accumulated errors than +derivatives found from the cubics themselves, are maintained in +local variables |x0|, |x1|, and |x2|, representing $X_0=2^l(x_1-x_0)$, +$X_1=2^l(x_2-x_1)$, and $X_2=2^l(x_3-x_2)$; similarly |y0|, |y1|, and~|y2| +represent $Y_0=2^l(y_1-y_0)$, $Y_1=2^l(y_2-y_1)$, and $Y_2=2^l(y_3-y_2)$. + +@<Other local variables for |offset_prep|@>= +@!x0,@!x1,@!x2,@!y0,@!y1,@!y2:integer; {representatives of derivatives} +@!t0,@!t1,@!t2:integer; {coefficients of polynomial for slope testing} +@!du,@!dv,@!dx,@!dy:integer; {for directions of the pen and the curve} +@!dx0,@!dy0:integer; {initial direction for the first cubic in the curve} +@!max_coef:integer; {used while scaling} +@!x0a,@!x1a,@!x2a,@!y0a,@!y1a,@!y2a:integer; {intermediate values} +@!t:fraction; {where the derivative passes through zero} +@!s:fraction; {a temporary value} + +@ @<Prepare for derivative computations...@>= +x0:=right_x(p)-x_coord(p); +x2:=x_coord(q)-left_x(q); +x1:=left_x(q)-right_x(p); +y0:=right_y(p)-y_coord(p); y2:=y_coord(q)-left_y(q); +y1:=left_y(q)-right_y(p); +max_coef:=abs(x0); +if abs(x1)>max_coef then max_coef:=abs(x1); +if abs(x2)>max_coef then max_coef:=abs(x2); +if abs(y0)>max_coef then max_coef:=abs(y0); +if abs(y1)>max_coef then max_coef:=abs(y1); +if abs(y2)>max_coef then max_coef:=abs(y2); +if max_coef=0 then goto not_found; +while max_coef<fraction_half do + begin double(max_coef); + double(x0); double(x1); double(x2); + double(y0); double(y1); double(y2); + end + +@ Let us first solve a special case of the problem: Suppose we +know an index~$k$ such that either (i)~$d(t)\succeq d_{k-1}$ for all~$t$ +and $d(0)\prec d_k$, or (ii)~$d(t)\preceq d_k$ for all~$t$ and +$d(0)\succ d_{k-1}$. +Then, in a sense, we're halfway done, since one of the two relations +in $(*)$ is satisfied, and the other couldn't be satisfied for +any other value of~|k|. + +Actually, the conditions can be relaxed somewhat since a relation such as +$d(t)\succeq d_{k-1}$ restricts $d(t)$ to a half plane when all that really +matters is whether $d(t)$ crosses the ray in the $d_{k-1}$ direction from +the origin. The condition for case~(i) becomes $d_{k-1}\preceq d(0)\prec d_k$ +and $d(t)$ never crosses the $d_{k-1}$ ray in the clockwise direction. +Case~(ii) is similar except $d(t)$ cannot cross the $d_k$ ray in the +counterclockwise direction. + +The |fin_offset_prep| subroutine solves the stated subproblem. +It has a parameter called |rise| that is |1| in +case~(i), |-1| in case~(ii). Parameters |x0| through |y2| represent +the derivative of the cubic following |p|. +The |w| parameter should point to offset~$w_k$ and |info(p)| should already +be set properly. The |turn_amt| parameter gives the absolute value of the +overall net change in pen offsets. + +@<Declare subroutines needed by |offset_prep|@>= +procedure fin_offset_prep(@!p:pointer;@!w:pointer; + @!x0,@!x1,@!x2,@!y0,@!y1,@!y2:integer;@!rise,@!turn_amt:integer); +label exit; +var @!ww:pointer; {for list manipulation} +@!du,@!dv:scaled; {for slope calculation} +@!t0,@!t1,@!t2:integer; {test coefficients} +@!t:fraction; {place where the derivative passes a critical slope} +@!s:fraction; {slope or reciprocal slope} +@!v:integer; {intermediate value for updating |x0..y2|} +@!q:pointer; {original |link(p)|} +begin q:=link(p); +loop @+begin if rise>0 then ww:=link(w) {a pointer to $w\k$} + else ww:=knil(w); {a pointer to $w_{k-1}$} + @<Compute test coefficients |(t0,t1,t2)| + for $d(t)$ versus $d_k$ or $d_{k-1}$@>; + t:=crossing_point(t0,t1,t2); + if t>=fraction_one then + if turn_amt>0 then t:=fraction_one @+else return; + @<Split the cubic at $t$, + and split off another cubic if the derivative crosses back@>; + w:=ww; + end; +exit:end; + +@ We want $B(\\{t0},\\{t1},\\{t2};t)$ to be the dot product of $d(t)$ with a +$-90^\circ$ rotation of the vector from |w| to |ww|. This makes the resulting +function cross from positive to negative when $d_{k-1}\preceq d(t)\preceq d_k$ +begins to fail. + +@<Compute test coefficients |(t0,t1,t2)| for $d(t)$ versus...@>= +du:=x_coord(ww)-x_coord(w); dv:=y_coord(ww)-y_coord(w); +if abs(du)>=abs(dv) then + begin s:=make_fraction(dv,du); + t0:=take_fraction(x0,s)-y0; + t1:=take_fraction(x1,s)-y1; + t2:=take_fraction(x2,s)-y2; + if du<0 then begin negate(t0); negate(t1); negate(t2); @+end + end +else begin s:=make_fraction(du,dv); + t0:=x0-take_fraction(y0,s); + t1:=x1-take_fraction(y1,s); + t2:=x2-take_fraction(y2,s); + if dv<0 then begin negate(t0); negate(t1); negate(t2); @+end + end; +if t0<0 then t0:=0 {should be positive without rounding error} + +@ The curve has crossed $d_k$ or $d_{k-1}$; its initial segment satisfies +$(*)$, and it might cross again, yielding another solution of $(*)$. + +@<Split the cubic at $t$, and split off another...@>= +begin split_cubic(p,t); p:=link(p); info(p):=zero_off+rise; +decr(turn_amt);@/ +v:=t_of_the_way(x0)(x1); x1:=t_of_the_way(x1)(x2); +x0:=t_of_the_way(v)(x1);@/ +v:=t_of_the_way(y0)(y1); y1:=t_of_the_way(y1)(y2); +y0:=t_of_the_way(v)(y1);@/ +if turn_amt<0 then + begin t1:=t_of_the_way(t1)(t2); + if t1>0 then t1:=0; {without rounding error, |t1| would be |<=0|} + t:=crossing_point(0,-t1,-t2); + if t>fraction_one then t:=fraction_one; + incr(turn_amt); + if (t=fraction_one)and(link(p)<>q) then + info(link(p)):=info(link(p))-rise + else begin split_cubic(p,t); info(link(p)):=zero_off-rise;@/ + v:=t_of_the_way(x1)(x2); x1:=t_of_the_way(x0)(x1); + x2:=t_of_the_way(x1)(v);@/ + v:=t_of_the_way(y1)(y2); y1:=t_of_the_way(y0)(y1); + y2:=t_of_the_way(y1)(v);@/ + end; + end; +end + +@ Now we must consider the general problem of |offset_prep|, when +nothing is known about a given cubic. We start by finding its +direction in the vicinity of |t=0|. + +If $z'(t)=0$, the given cubic is numerically unstable but |offset_prep| +has not yet introduced any more numerical errors. Thus we can compute +the true initial direction for the given cubic, even if it is almost +degenerate. + +@<Find the initial direction |(dx,dy)|@>= +dx:=x0; dy:=y0; +if dx=0 then if dy=0 then + begin dx:=x1; dy:=y1; + if dx=0 then if dy=0 then + begin dx:=x2; dy:=y2; + end; + end; +if p=c then begin dx0:=dx; dy0:=dy; @+end + +@ @<Find the final direction |(dxin,dyin)|@>= +dxin:=x2; dyin:=y2; +if dxin=0 then if dyin=0 then + begin dxin:=x1; dyin:=y1; + if dxin=0 then if dyin=0 then + begin dxin:=x0; dyin:=y0; + end; + end + +@ The next step is to bracket the initial direction between consecutive +edges of the pen polygon. We must be careful to turn clockwise only if +this makes the turn less than $180^\circ$. (A $180^\circ$ turn must be +counter-clockwise in order to make \&{doublepath} envelopes come out +@:double_path_}{\&{doublepath} primitive@> +right.) This code depends on |w0| being the offset for |(dxin,dyin)|. + +@<Update |info(p)| and find the offset $w_k$ such that...@>= +turn_amt:=get_turn_amt(w0, dx, dy, ab_vs_cd(dy,dxin,dx,dyin)>=0); +w:=pen_walk(w0, turn_amt); +w0:=w; +info(p):=info(p)+turn_amt + +@ Decide how many pen offsets to go away from |w| in order to find the offset +for |(dx,dy)|, going counterclockwise if |ccw| is |true|. This assumes that +|w| is the offset for some direction $(x',y')$ from which the angle to |(dx,dy)| +in the sense determined by |ccw| is less than or equal to $180^\circ$. + +If the pen polygon has only two edges, they could both be parallel +to |(dx,dy)|. In this case, we must be careful to stop after crossing the first +such edge in order to avoid an infinite loop. + +@<Declare subroutines needed by |offset_prep|@>= +function get_turn_amt(@!w:pointer; @!dx,@!dy:scaled; ccw:boolean):integer; +label done; +var @!ww:pointer; {a neighbor of knot~|w|} +@!s:integer; {turn amount so far} +@!t:integer; {|ab_vs_cd| result} +begin s:=0; +if ccw then + begin ww:=link(w); + repeat t:=ab_vs_cd(dy,x_coord(ww)-x_coord(w),@| dx,y_coord(ww)-y_coord(w)); + if t<0 then goto done; + incr(s); + w:=ww; ww:=link(ww); + until t<=0; + done: end +else begin ww:=knil(w); + while ab_vs_cd(dy,x_coord(w)-x_coord(ww),@| + dx,y_coord(w)-y_coord(ww))<0 do + begin decr(s); + w:=ww; ww:=knil(ww); + end; + end; +get_turn_amt:=s; +end; + +@ When we're all done, the final offset is |w0| and the final curve direction +is |(dxin,dyin)|. With this knowledge of the incoming direction at |c|, we +can correct |info(c)| which was erroneously based on an incoming offset +of~|h|. + +@d fix_by(#)==info(c):=info(c)+# + +@<Fix the offset change in |info(c)| and set the return value of...@>= +spec_offset:=info(c)-zero_off; +if link(c)=c then info(c):=zero_off+n +else begin fix_by(k_needed); + while w0<>h do + begin fix_by(1); w0:=link(w0); @+end; + while info(c)<=zero_off-n do fix_by(n); + while info(c)>zero_off do fix_by(-n); + if (info(c)<>zero_off)and(ab_vs_cd(dy0,dxin,dx0,dyin)>=0) then fix_by(n); + end; +offset_prep:=c + +@ Finally we want to reduce the general problem to situations that +|fin_offset_prep| can handle. We split the cubic into at most three parts +with respect to $d_{k-1}$, and apply |fin_offset_prep| to each part. + +@<Complete the offset splitting process@>= +ww:=knil(w); +@<Compute test coeff...@>; +@<Find the first |t| where $d(t)$ crosses $d_{k-1}$ or set + |t:=fraction_one+1|@>; +if t>fraction_one then + fin_offset_prep(p,w,x0,x1,x2,y0,y1,y2,1,turn_amt) +else begin split_cubic(p,t); r:=link(p);@/ + x1a:=t_of_the_way(x0)(x1); x1:=t_of_the_way(x1)(x2); + x2a:=t_of_the_way(x1a)(x1);@/ + y1a:=t_of_the_way(y0)(y1); y1:=t_of_the_way(y1)(y2); + y2a:=t_of_the_way(y1a)(y1);@/ + fin_offset_prep(p,w,x0,x1a,x2a,y0,y1a,y2a,1,0); x0:=x2a; y0:=y2a; + info(r):=zero_off-1; + if turn_amt>=0 then + begin t1:=t_of_the_way(t1)(t2); + if t1>0 then t1:=0; + t:=crossing_point(0,-t1,-t2); + if t>fraction_one then t:=fraction_one; + @<Split off another rising cubic for |fin_offset_prep|@>; + fin_offset_prep(r,ww,x0,x1,x2,y0,y1,y2,-1,0); + end + else fin_offset_prep(r,ww,x0,x1,x2,y0,y1,y2,-1,-1-turn_amt); + end + +@ @<Split off another rising cubic for |fin_offset_prep|@>= +split_cubic(r,t); info(link(r)):=zero_off+1;@/ +x1a:=t_of_the_way(x1)(x2); x1:=t_of_the_way(x0)(x1); +x0a:=t_of_the_way(x1)(x1a);@/ +y1a:=t_of_the_way(y1)(y2); y1:=t_of_the_way(y0)(y1); +y0a:=t_of_the_way(y1)(y1a);@/ +fin_offset_prep(link(r),w,x0a,x1a,x2,y0a,y1a,y2,1,turn_amt); +x2:=x0a; y2:=y0a + +@ At this point, the direction of the incoming pen edge is |(-du,-dv)|. +When the component of $d(t)$ perpendicular to |(-du,-dv)| crosses zero, we +need to decide whether the directions are parallel or antiparallel. We +can test this by finding the dot product of $d(t)$ and |(-du,-dv)|, but this +should be avoided when the value of |turn_amt| already determines the +answer. If |t2<0|, there is one crossing and it is antiparallel only if +|turn_amt>=0|. If |turn_amt<0|, there should always be at least one +crossing and the first crossing cannot be antiparallel. + +@<Find the first |t| where $d(t)$ crosses $d_{k-1}$ or set...@>= +t:=crossing_point(t0,t1,t2); +if turn_amt>=0 then + if t2<0 then t:=fraction_one+1 + else begin u0:=t_of_the_way(x0)(x1); + u1:=t_of_the_way(x1)(x2); + ss:=take_fraction(-du,t_of_the_way(u0)(u1));@/ + v0:=t_of_the_way(y0)(y1); + v1:=t_of_the_way(y1)(y2); + ss:=ss+take_fraction(-dv,t_of_the_way(v0)(v1));@/ + if ss<0 then t:=fraction_one+1; + end +else if t>fraction_one then t:=fraction_one; + +@ @<Other local variables for |offset_prep|@>= +@!u0,@!u1,@!v0,@!v1:integer; {intermediate values for $d(t)$ calculation} +@!ss:integer; {the part of the dot product computed so far} +@!d_sign:-1..1; {sign of overall change in direction for this cubic} + +@ If the cubic almost has a cusp, it is a numerically ill-conditioned +problem to decide which way it loops around but that's OK as long we're +consistent. To make \&{doublepath} envelopes work properly, reversing +the path should always change the sign of |turn_amt|. + +@<Decide on the net change in pen offsets and set |turn_amt|@>= +d_sign:=ab_vs_cd(dx,dyin, dxin,dy); +if d_sign=0 then + if dx=0 then + if dy>0 then d_sign:=1 @+else d_sign:=-1 + else if dx>0 then d_sign:=1 @+else d_sign:=-1; +@<Make |ss| negative if and only if the total change in direction is + more than $180^\circ$@>; +turn_amt:=get_turn_amt(w, dxin, dyin, d_sign>0); +if ss<0 then turn_amt:=turn_amt-d_sign*n + +@ In order to be invariant under path reversal, the result of this computation +should not change when |x0|, |y0|, $\ldots$ are all negated and |(x0,y0)| is +then swapped with |(x2,y2)|. We make use of the identities +|take_fraction(-a,-b)=take_fraction(a,b)| and +|t_of_the_way(-a)(-b)=-(t_of_the_way(a)(b))|. + +@<Make |ss| negative if and only if the total change in direction is...@>= +t0:=half(take_fraction(x0,y2))-half(take_fraction(x2,y0));@/ +t1:=half(take_fraction(x1,y0+y2))-half(take_fraction(y1,x0+x2));@/ +if t0=0 then t0:=d_sign; {path reversal always negates |d_sign|} +if t0>0 then + begin t:=crossing_point(t0,t1,-t0); + u0:=t_of_the_way(x0)(x1); + u1:=t_of_the_way(x1)(x2);@/ + v0:=t_of_the_way(y0)(y1); + v1:=t_of_the_way(y1)(y2); + end +else begin t:=crossing_point(-t0,t1,t0); + u0:=t_of_the_way(x2)(x1); + u1:=t_of_the_way(x1)(x0);@/ + v0:=t_of_the_way(y2)(y1); + v1:=t_of_the_way(y1)(y0); + end; +ss:=take_fraction(x0+x2,t_of_the_way(u0)(u1))+@| + take_fraction(y0+y2,t_of_the_way(v0)(v1)) + +@ Here's a routine that prints an envelope spec in symbolic form. It assumes +that the |cur_pen| has not been walked around to the first offset. + +@p procedure print_spec(@!cur_spec,@!cur_pen:pointer;@!s:str_number); +var @!p,@!q:pointer; {list traversal} +@!w:pointer; {the current pen offset} +begin print_diagnostic("Envelope spec",s,true); +p:=cur_spec; w:=pen_walk(cur_pen,spec_offset); +print_ln;@/ +print_two(x_coord(cur_spec),y_coord(cur_spec)); +print(" % beginning with offset "); +print_two(x_coord(w),y_coord(w)); +repeat + repeat q:=link(p); + @<Print the cubic between |p| and |q|@>; + p:=q; + until (p=cur_spec) or (info(p)<>zero_off); + if info(p)<>zero_off then + @<Update |w| as indicated by |info(p)| and print an explanation@>; +until p=cur_spec; +print_nl(" & cycle"); +end_diagnostic(true); +end; + +@ @<Update |w| as indicated by |info(p)| and print an explanation@>= +begin w:=pen_walk(w,info(p)-zero_off); +print(" % "); +if info(p)>zero_off then print("counter"); +print("clockwise to offset "); +print_two(x_coord(w),y_coord(w)); +end + +@ @<Print the cubic between |p| and |q|@>= +begin print_nl(" ..controls "); +print_two(right_x(p),right_y(p)); +print(" and "); +print_two(left_x(q),left_y(q)); +print_nl(" .."); +print_two(x_coord(q),y_coord(q)); +end + +@ Once we have an envelope spec, the remaining task to construct the actual +envelope by offsetting each cubic as determined by the |info| fields in +the knots. First we use |offset_prep| to convert the |c| into an envelope +spec. Then we add the offsets so that |c| becomes a cyclic path that represents +the envelope. + +The |ljoin| and |miterlim| parameters control the treatment of points where the +pen offset changes, and |lcap| controls the endpoints of a \&{doublepath}. +The endpoints are easily located because |c| is given in undoubled form +and then doubled in this procedure. We use |spec_p1| and |spec_p2| to keep +track of the endpoints and treat them like very sharp corners. +Butt end caps are treated like beveled joins; round end caps are treated like +round joins; and square end caps are achieved by setting |join_type:=3|. + +None of these parameters apply to inside joins where the convolution tracing +has retrograde lines. In such cases we use a simple connect-the-endpoints +approach that is achieved by setting |join_type:=2|. + +@p @t\4@>@<Declare a function called |insert_knot|@>@; +function make_envelope(@!c,@!h:pointer;@!ljoin,@!lcap:small_number; + @!miterlim:scaled):pointer; +label done; +var @!p,@!q,@!r,@!q0:pointer; {for manipulating the path} +@!join_type:0..3; {codes |0..3| for mitered, round, beveled, or square} +@!w,@!w0:pointer; {the pen knot for the current offset} +@!qx,@!qy:scaled; {unshifted coordinates of |q|} +@!k,@!k0:halfword; {controls pen edge insertion} +@<Other local variables for |make_envelope|@>@; +begin spec_p1:=null; spec_p2:=null; +if left_type(c)=endpoint then + @<Double the path |c|, and set |spec_p1| and |spec_p2|@>; +@<Use |offset_prep| to compute the envelope spec then walk |h| around to + the initial offset@>; +w:=h; +p:=c; +repeat q:=link(p); q0:=q; +qx:=x_coord(q); qy:=y_coord(q); +k:=info(q);@/ +k0:=k; w0:=w; +if k<>zero_off then + @<Set |join_type| to indicate how to handle offset changes at~|q|@>; +@<Add offset |w| to the cubic from |p| to |q|@>; +while k<>zero_off do + begin @<Step |w| and move |k| one step closer to |zero_off|@>; + if (join_type=1)or(k=zero_off) then + q:=insert_knot(q,qx+x_coord(w),qy+y_coord(w)); + end; +if q<>link(p) then @<Set |p=link(p)| and add knots between |p| and |q| as + requred by |join_type|@>; +p:=q; +until q0=c; +make_envelope:=c; +end; + +@ @<Use |offset_prep| to compute the envelope spec then walk |h| around to...@>= +c:=offset_prep(c,h); +if internal[tracing_specs]>0 then print_spec(c,h,""); +h:=pen_walk(h,spec_offset) + +@ Mitered and squared-off joins depend on path directions that are difficult to +compute for degenerate cubics. The envelope spec computed by |offset_prep| can +have degenerate cubics only if the entire cycle collapses to a single +degenerate cubic. Setting |join_type:=2| in this case makes the computed +envelope degenerate as well. + +@<Set |join_type| to indicate how to handle offset changes at~|q|@>= +if k<zero_off then join_type:=2 +else begin if (q<>spec_p1)and(q<>spec_p2) then join_type:=ljoin + else if lcap=2 then join_type:=3 + else join_type:=2-lcap; + if (join_type=0)or(join_type=3) then + begin @<Set the incoming and outgoing directions at |q|; in case of + degeneracy set |join_type:=2|@>; + if join_type=0 then + @<If |miterlim| is less than the secant of half the angle at |q| + then set |join_type:=2|@>; + end; + end + +@ @<If |miterlim| is less than the secant of half the angle at |q|...@>= +begin tmp:=take_fraction(miterlim,fraction_half+@| + half(take_fraction(dxin,dxout)+take_fraction(dyin,dyout))); +if tmp<unity then + if take_scaled(miterlim,tmp)<unity then join_type:=2; +end + +@ @<Other local variables for |make_envelope|@>= +@!dxin,@!dyin,@!dxout,@!dyout:fraction; + {directions at |q| when square or mitered} +@!tmp:scaled; {a temporary value} + +@ The coordinates of |p| have already been shifted unless |p| is the first +knot in which case they get shifted at the very end. + +@<Add offset |w| to the cubic from |p| to |q|@>= +right_x(p):=right_x(p)+x_coord(w); +right_y(p):=right_y(p)+y_coord(w);@/ +left_x(q):=left_x(q)+x_coord(w); +left_y(q):=left_y(q)+y_coord(w);@/ +x_coord(q):=x_coord(q)+x_coord(w); +y_coord(q):=y_coord(q)+y_coord(w);@/ +left_type(q):=explicit; +right_type(q):=explicit + +@ @<Step |w| and move |k| one step closer to |zero_off|@>= +if k>zero_off then + begin w:=link(w); decr(k); @+end +else begin w:=knil(w); incr(k); @+end + +@ The cubic from |q| to the new knot at |(x,y)| becomes a line segment and +the |right_x| and |right_y| fields of |r| are set from |q|. This is done in +case the cubic containing these control points is ``yet to be examined.'' + +@<Declare a function called |insert_knot|@>= +function insert_knot(@!q:pointer;@!x,@!y:scaled):pointer; + {returns the inserted knot} +var @!r:pointer; {the new knot} +begin r:=get_node(knot_node_size); +link(r):=link(q); link(q):=r;@/ +right_x(r):=right_x(q); +right_y(r):=right_y(q);@/ +x_coord(r):=x; +y_coord(r):=y;@/ +right_x(q):=x_coord(q); +right_y(q):=y_coord(q);@/ +left_x(r):=x_coord(r); +left_y(r):=y_coord(r);@/ +left_type(r):=explicit; +right_type(r):=explicit; +originator(r):=program_code;@/ +insert_knot:=r; +end; + +@ After setting |p:=link(p)|, either |join_type=1| or |q=link(p)|. + +@<Set |p=link(p)| and add knots between |p| and |q| as...@>= +begin p:=link(p); +if (join_type=0)or(join_type=3) then + begin if join_type=0 then + @<Insert a new knot |r| between |p| and |q| as required for a mitered join@> + else @<Make |r| the last of two knots inserted between |p| and |q| to form a + squared join@>; + if r<>null then + begin right_x(r):=x_coord(r); + right_y(r):=y_coord(r); + end; + end; +end + +@ For very small angles, adding a knot is unnecessary and would cause numerical +problems, so we just set |r:=null| in that case. + +@<Insert a new knot |r| between |p| and |q| as required for a mitered join@>= +begin det:=take_fraction(dyout,dxin)-take_fraction(dxout,dyin); +if abs(det)<26844 then r:=null {sine $<10^{-4}$} +else begin tmp:=take_fraction(x_coord(q)-x_coord(p),dyout)-@| + take_fraction(y_coord(q)-y_coord(p),dxout); + tmp:=make_fraction(tmp,det); + r:=insert_knot(p,x_coord(p)+take_fraction(tmp,dxin),@| + y_coord(p)+take_fraction(tmp,dyin)); + end; +end + +@ @<Other local variables for |make_envelope|@>= +@!det:fraction; {a determinant used for mitered join calculations} + +@ @<Make |r| the last of two knots inserted between |p| and |q| to form a...@>= +begin ht_x:=y_coord(w)-y_coord(w0); +ht_y:=x_coord(w0)-x_coord(w); +while (abs(ht_x)<fraction_half)and(abs(ht_y)<fraction_half) do + begin double(ht_x); double(ht_y); + end; +@<Scan the pen polygon between |w0| and |w| and make |max_ht| the range dot + product with |(ht_x,ht_y)|@>; +tmp:=make_fraction(max_ht,take_fraction(dxin,ht_x)+take_fraction(dyin,ht_y)); +r:=insert_knot(p,x_coord(p)+take_fraction(tmp,dxin),@| + y_coord(p)+take_fraction(tmp,dyin)); +tmp:=make_fraction(max_ht,take_fraction(dxout,ht_x)+take_fraction(dyout,ht_y)); +r:=insert_knot(r,x_coord(q)+take_fraction(tmp,dxout),@| + y_coord(q)+take_fraction(tmp,dyout)); +end + +@ @<Other local variables for |make_envelope|@>= +@!ht_x,@!ht_y:fraction; {perpendicular to the segment from |p| to |q|} +@!max_ht:scaled; {maximum height of the pen polygon above the |w0|-|w| line} +@!kk:halfword; {keeps track of the pen vertices being scanned} +@!ww:pointer; {the pen vertex being tested} + +@ The dot product of the vector from |w0| to |ww| with |(ht_x,ht_y)| ranges +from zero to |max_ht|. + +@<Scan the pen polygon between |w0| and |w| and make |max_ht| the range...@>= +max_ht:=0; +kk:=zero_off; +ww:=w; +loop @+begin @<Step |ww| and move |kk| one step closer to |k0|@>; + if kk=k0 then goto done; + tmp:=take_fraction(x_coord(ww)-x_coord(w0),ht_x)+@| + take_fraction(y_coord(ww)-y_coord(w0),ht_y); + if tmp>max_ht then max_ht:=tmp; + end; +done:do_nothing + +@ @<Step |ww| and move |kk| one step closer to |k0|@>= +if kk>k0 then + begin ww:=link(ww); decr(kk); @+end +else begin ww:=knil(ww); incr(kk); @+end + +@ @<Double the path |c|, and set |spec_p1| and |spec_p2|@>= +begin spec_p1:=htap_ypoc(c); +spec_p2:=path_tail; +originator(spec_p1):=program_code; +link(spec_p2):=link(spec_p1); +link(spec_p1):=c;@/ +remove_cubic(spec_p1); +c:=spec_p1; +if c<>link(c) then begin +originator(spec_p2):=program_code; +remove_cubic(spec_p2); +end +else @<Make |c| look like a cycle of length one@>; +end + +@ @<Make |c| look like a cycle of length one@>= +begin left_type(c):=explicit; right_type(c):=explicit; +left_x(c):=x_coord(c); left_y(c):=y_coord(c); +right_x(c):=x_coord(c); right_y(c):=y_coord(c); +end; + +@ In degenerate situations we might have to look at the knot preceding~|q|. +That knot is |p| but if |p<>c|, its coordinates have already been offset by |w|. + +@<Set the incoming and outgoing directions at |q|; in case of...@>= +dxin:=x_coord(q)-left_x(q); +dyin:=y_coord(q)-left_y(q); +if (dxin=0)and(dyin=0) then + begin dxin:=x_coord(q)-right_x(p); + dyin:=y_coord(q)-right_y(p); + if (dxin=0)and(dyin=0) then + begin dxin:=x_coord(q)-x_coord(p); + dyin:=y_coord(q)-y_coord(p); + if p<>c then {the coordinates of |p| have been offset by |w|} + begin dxin:=dxin+x_coord(w); + dyin:=dyin+y_coord(w); + end; + end; + end; +tmp:=pyth_add(dxin,dyin); +if tmp=0 then join_type:=2 +else begin dxin:=make_fraction(dxin,tmp); + dyin:=make_fraction(dyin,tmp); + @<Set the outgoing direction at |q|@>; + end + +@ If |q=c| then the coordinates of |r| and the control points between |q| +and~|r| have already been offset by |h|. + +@<Set the outgoing direction at |q|@>= +dxout:=right_x(q)-x_coord(q); +dyout:=right_y(q)-y_coord(q); +if (dxout=0)and(dyout=0) then + begin r:=link(q); + dxout:=left_x(r)-x_coord(q); + dyout:=left_y(r)-y_coord(q); + if (dxout=0)and(dyout=0) then + begin dxout:=x_coord(r)-x_coord(q); + dyout:=y_coord(r)-y_coord(q); + end; + end; +if q=c then + begin dxout:=dxout-x_coord(h); + dyout:=dyout-y_coord(h); + end; +tmp:=pyth_add(dxout,dyout); +if tmp=0 then confusion("degenerate spec"); +@:this can't happen degerate spec}{\quad degenerate spec@> +dxout:=make_fraction(dxout,tmp); +dyout:=make_fraction(dyout,tmp) + +@* \[23] Direction and intersection times. +A path of length $n$ is defined parametrically by functions $x(t)$ and +$y(t)$, for |0<=t<=n|; we can regard $t$ as the ``time'' at which the path +reaches the point $\bigl(x(t),y(t)\bigr)$. In this section of the program +we shall consider operations that determine special times associated with +given paths: the first time that a path travels in a given direction, and +a pair of times at which two paths cross each other. + +@ Let's start with the easier task. The function |find_direction_time| is +given a direction |(x,y)| and a path starting at~|h|. If the path never +travels in direction |(x,y)|, the direction time will be~|-1|; otherwise +it will be nonnegative. + +Certain anomalous cases can arise: If |(x,y)=(0,0)|, so that the given +direction is undefined, the direction time will be~0. If $\bigl(x'(t), +y'(t)\bigr)=(0,0)$, so that the path direction is undefined, it will be +assumed to match any given direction at time~|t|. + +The routine solves this problem in nondegenerate cases by rotating the path +and the given direction so that |(x,y)=(1,0)|; i.e., the main task will be +to find when a given path first travels ``due east.'' + +@p function find_direction_time(@!x,@!y:scaled;@!h:pointer):scaled; +label exit,found,not_found,done; +var @!max:scaled; {$\max\bigl(\vert x\vert,\vert y\vert\bigr)$} +@!p,@!q:pointer; {for list traversal} +@!n:scaled; {the direction time at knot |p|} +@!tt:scaled; {the direction time within a cubic} +@<Other local variables for |find_direction_time|@>@; +begin @<Normalize the given direction for better accuracy; + but |return| with zero result if it's zero@>; +n:=0; p:=h; +loop@+ begin if right_type(p)=endpoint then goto not_found; + q:=link(p); + @<Rotate the cubic between |p| and |q|; then + |goto found| if the rotated cubic travels due east at some time |tt|; + but |goto not_found| if an entire cyclic path has been traversed@>; + p:=q; n:=n+unity; + end; +not_found: find_direction_time:=-unity; return; +found: find_direction_time:=n+tt; +exit:end; + +@ @<Normalize the given direction for better accuracy...@>= +if abs(x)<abs(y) then + begin x:=make_fraction(x,abs(y)); + if y>0 then y:=fraction_one@+else y:=-fraction_one; + end +else if x=0 then + begin find_direction_time:=0; return; + end +else begin y:=make_fraction(y,abs(x)); + if x>0 then x:=fraction_one@+else x:=-fraction_one; + end + +@ Since we're interested in the tangent directions, we work with the +derivative $${\textstyle1\over3}B'(x_0,x_1,x_2,x_3;t)= +B(x_1-x_0,x_2-x_1,x_3-x_2;t)$$ instead of +$B(x_0,x_1,x_2,x_3;t)$ itself. The derived coefficients are also scaled up +in order to achieve better accuracy. + +The given path may turn abruptly at a knot, and it might pass the critical +tangent direction at such a time. Therefore we remember the direction |phi| +in which the previous rotated cubic was traveling. (The value of |phi| will be +undefined on the first cubic, i.e., when |n=0|.) + +@<Rotate the cubic between |p| and |q|; then...@>= +tt:=0; +@<Set local variables |x1,x2,x3| and |y1,y2,y3| to multiples of the control + points of the rotated derivatives@>; +if y1=0 then if x1>=0 then goto found; +if n>0 then + begin @<Exit to |found| if an eastward direction occurs at knot |p|@>; + if p=h then goto not_found; + end; +if (x3<>0)or(y3<>0) then phi:=n_arg(x3,y3); +@<Exit to |found| if the curve whose derivatives are specified by + |x1,x2,x3,y1,y2,y3| travels eastward at some time~|tt|@> + +@ @<Other local variables for |find_direction_time|@>= +@!x1,@!x2,@!x3,@!y1,@!y2,@!y3:scaled; {multiples of rotated derivatives} +@!theta,@!phi:angle; {angles of exit and entry at a knot} +@!t:fraction; {temp storage} + +@ @<Set local variables |x1,x2,x3| and |y1,y2,y3| to multiples...@>= +x1:=right_x(p)-x_coord(p); x2:=left_x(q)-right_x(p); +x3:=x_coord(q)-left_x(q);@/ +y1:=right_y(p)-y_coord(p); y2:=left_y(q)-right_y(p); +y3:=y_coord(q)-left_y(q);@/ +max:=abs(x1); +if abs(x2)>max then max:=abs(x2); +if abs(x3)>max then max:=abs(x3); +if abs(y1)>max then max:=abs(y1); +if abs(y2)>max then max:=abs(y2); +if abs(y3)>max then max:=abs(y3); +if max=0 then goto found; +while max<fraction_half do + begin double(max); double(x1); double(x2); double(x3); + double(y1); double(y2); double(y3); + end; +t:=x1; x1:=take_fraction(x1,x)+take_fraction(y1,y); +y1:=take_fraction(y1,x)-take_fraction(t,y);@/ +t:=x2; x2:=take_fraction(x2,x)+take_fraction(y2,y); +y2:=take_fraction(y2,x)-take_fraction(t,y);@/ +t:=x3; x3:=take_fraction(x3,x)+take_fraction(y3,y); +y3:=take_fraction(y3,x)-take_fraction(t,y) + +@ @<Exit to |found| if an eastward direction occurs at knot |p|@>= +theta:=n_arg(x1,y1); +if theta>=0 then if phi<=0 then if phi>=theta-one_eighty_deg then goto found; +if theta<=0 then if phi>=0 then if phi<=theta+one_eighty_deg then goto found + +@ In this step we want to use the |crossing_point| routine to find the +roots of the quadratic equation $B(y_1,y_2,y_3;t)=0$. +Several complications arise: If the quadratic equation has a double root, +the curve never crosses zero, and |crossing_point| will find nothing; +this case occurs iff $y_1y_3=y_2^2$ and $y_1y_2<0$. If the quadratic +equation has simple roots, or only one root, we may have to negate it +so that $B(y_1,y_2,y_3;t)$ crosses from positive to negative at its first root. +And finally, we need to do special things if $B(y_1,y_2,y_3;t)$ is +identically zero. + +@ @<Exit to |found| if the curve whose derivatives are specified by...@>= +if x1<0 then if x2<0 then if x3<0 then goto done; +if ab_vs_cd(y1,y3,y2,y2)=0 then + @<Handle the test for eastward directions when $y_1y_3=y_2^2$; + either |goto found| or |goto done|@>; +if y1<=0 then + if y1<0 then + begin y1:=-y1; y2:=-y2; y3:=-y3; + end + else if y2>0 then + begin y2:=-y2; y3:=-y3; + end; +@<Check the places where $B(y_1,y_2,y_3;t)=0$ to see if + $B(x_1,x_2,x_3;t)\ge0$@>; +done: + +@ The quadratic polynomial $B(y_1,y_2,y_3;t)$ begins |>=0| and has at most +two roots, because we know that it isn't identically zero. + +It must be admitted that the |crossing_point| routine is not perfectly accurate; +rounding errors might cause it to find a root when $y_1y_3>y_2^2$, or to +miss the roots when $y_1y_3<y_2^2$. The rotation process is itself +subject to rounding errors. Yet this code optimistically tries to +do the right thing. + +@d we_found_it==begin tt:=(t+@'4000) div @'10000; goto found; + end + +@<Check the places where $B(y_1,y_2,y_3;t)=0$...@>= +t:=crossing_point(y1,y2,y3); +if t>fraction_one then goto done; +y2:=t_of_the_way(y2)(y3); +x1:=t_of_the_way(x1)(x2); +x2:=t_of_the_way(x2)(x3); +x1:=t_of_the_way(x1)(x2); +if x1>=0 then we_found_it; +if y2>0 then y2:=0; +tt:=t; t:=crossing_point(0,-y2,-y3); +if t>fraction_one then goto done; +x1:=t_of_the_way(x1)(x2); +x2:=t_of_the_way(x2)(x3); +if t_of_the_way(x1)(x2)>=0 then + begin t:=t_of_the_way(tt)(fraction_one); we_found_it; + end + +@ @<Handle the test for eastward directions when $y_1y_3=y_2^2$; + either |goto found| or |goto done|@>= +begin if ab_vs_cd(y1,y2,0,0)<0 then + begin t:=make_fraction(y1,y1-y2); + x1:=t_of_the_way(x1)(x2); + x2:=t_of_the_way(x2)(x3); + if t_of_the_way(x1)(x2)>=0 then we_found_it; + end +else if y3=0 then + if y1=0 then + @<Exit to |found| if the derivative $B(x_1,x_2,x_3;t)$ becomes |>=0|@> + else if x3>=0 then + begin tt:=unity; goto found; + end; +goto done; +end + +@ At this point we know that the derivative of |y(t)| is identically zero, +and that |x1<0|; but either |x2>=0| or |x3>=0|, so there's some hope of +traveling east. + +@<Exit to |found| if the derivative $B(x_1,x_2,x_3;t)$ becomes |>=0|...@>= +begin t:=crossing_point(-x1,-x2,-x3); +if t<=fraction_one then we_found_it; +if ab_vs_cd(x1,x3,x2,x2)<=0 then + begin t:=make_fraction(x1,x1-x2); we_found_it; + end; +end + +@ The intersection of two cubics can be found by an interesting variant +of the general bisection scheme described in the introduction to +|crossing_point|.\ +Given $w(t)=B(w_0,w_1,w_2,w_3;t)$ and $z(t)=B(z_0,z_1,z_2,z_3;t)$, +we wish to find a pair of times $(t_1,t_2)$ such that $w(t_1)=z(t_2)$, +if an intersection exists. First we find the smallest rectangle that +encloses the points $\{w_0,w_1,w_2,w_3\}$ and check that it overlaps +the smallest rectangle that encloses +$\{z_0,z_1,z_2,z_3\}$; if not, the cubics certainly don't intersect. +But if the rectangles do overlap, we bisect the intervals, getting +new cubics $w'$ and~$w''$, $z'$~and~$z''$; the intersection routine first +tries for an intersection between $w'$ and~$z'$, then (if unsuccessful) +between $w'$ and~$z''$, then (if still unsuccessful) between $w''$ and~$z'$, +finally (if thrice unsuccessful) between $w''$ and~$z''$. After $l$~successful +levels of bisection we will have determined the intersection times $t_1$ +and~$t_2$ to $l$~bits of accuracy. + +\def\submin{_{\rm min}} \def\submax{_{\rm max}} +As before, it is better to work with the numbers $W_k=2^l(w_k-w_{k-1})$ +and $Z_k=2^l(z_k-z_{k-1})$ rather than the coefficients $w_k$ and $z_k$ +themselves. We also need one other quantity, $\Delta=2^l(w_0-z_0)$, +to determine when the enclosing rectangles overlap. Here's why: +The $x$~coordinates of~$w(t)$ are between $u\submin$ and $u\submax$, +and the $x$~coordinates of~$z(t)$ are between $x\submin$ and $x\submax$, +if we write $w_k=(u_k,v_k)$ and $z_k=(x_k,y_k)$ and $u\submin= +\min(u_0,u_1,u_2,u_3)$, etc. These intervals of $x$~coordinates +overlap if and only if $u\submin\L x\submax$ and +$x\submin\L u\submax$. Letting +$$U\submin=\min(0,U_1,U_1+U_2,U_1+U_2+U_3),\; + U\submax=\max(0,U_1,U_1+U_2,U_1+U_2+U_3),$$ +we have $u\submin=2^lu_0+U\submin$, etc.; the condition for overlap +reduces to +$$X\submin-U\submax\L 2^l(u_0-x_0)\L X\submax-U\submin.$$ +Thus we want to maintain the quantity $2^l(u_0-x_0)$; similarly, +the quantity $2^l(v_0-y_0)$ accounts for the $y$~coordinates. The +coordinates of $\Delta=2^l(w_0-z_0)$ must stay bounded as $l$ increases, +because of the overlap condition; i.e., we know that $X\submin$, +$X\submax$, and their relatives are bounded, hence $X\submax- +U\submin$ and $X\submin-U\submax$ are bounded. + +@ Incidentally, if the given cubics intersect more than once, the process +just sketched will not necessarily find the lexicographically smallest pair +$(t_1,t_2)$. The solution actually obtained will be smallest in ``shuffled +order''; i.e., if $t_1=(.a_1a_2\ldots a_{16})_2$ and +$t_2=(.b_1b_2\ldots b_{16})_2$, then we will minimize +$a_1b_1a_2b_2\ldots a_{16}b_{16}$, not +$a_1a_2\ldots a_{16}b_1b_2\ldots b_{16}$. +Shuffled order agrees with lexicographic order if all pairs of solutions +$(t_1,t_2)$ and $(t_1',t_2')$ have the property that $t_1<t_1'$ iff +$t_2<t_2'$; but in general, lexicographic order can be quite different, +and the bisection algorithm would be substantially less efficient if it were +constrained by lexicographic order. + +For example, suppose that an overlap has been found for $l=3$ and +$(t_1,t_2)= (.101,.011)$ in binary, but that no overlap is produced by +either of the alternatives $(.1010,.0110)$, $(.1010,.0111)$ at level~4. +Then there is probably an intersection in one of the subintervals +$(.1011,.011x)$; but lexicographic order would require us to explore +$(.1010,.1xxx)$ and $(.1011,.00xx)$ and $(.1011,.010x)$ first. We wouldn't +want to store all of the subdivision data for the second path, so the +subdivisions would have to be regenerated many times. Such inefficiencies +would be associated with every `1' in the binary representation of~$t_1$. + +@ The subdivision process introduces rounding errors, hence we need to +make a more liberal test for overlap. It is not hard to show that the +computed values of $U_i$ differ from the truth by at most~$l$, on +level~$l$, hence $U\submin$ and $U\submax$ will be at most $3l$ in error. +If $\beta$ is an upper bound on the absolute error in the computed +components of $\Delta=(|delx|,|dely|)$ on level~$l$, we will replace +the test `$X\submin-U\submax\L|delx|$' by the more liberal test +`$X\submin-U\submax\L|delx|+|tol|$', where $|tol|=6l+\beta$. + +More accuracy is obtained if we try the algorithm first with |tol=0|; +the more liberal tolerance is used only if an exact approach fails. +It is convenient to do this double-take by letting `3' in the preceding +paragraph be a parameter, which is first 0, then 3. + +@<Glob...@>= +@!tol_step:0..6; {either 0 or 3, usually} + +@ We shall use an explicit stack to implement the recursive bisection +method described above. The |bisect_stack| array will contain numerous 5-word +packets like $(U_1,U_2,U_3,U\submin,U\submax)$, as well as 20-word packets +comprising the 5-word packets for $U$, $V$, $X$, and~$Y$. + +The following macros define the allocation of stack positions to +the quantities needed for bisection-intersection. + +@d stack_1(#)==bisect_stack[#] {$U_1$, $V_1$, $X_1$, or $Y_1$} +@d stack_2(#)==bisect_stack[#+1] {$U_2$, $V_2$, $X_2$, or $Y_2$} +@d stack_3(#)==bisect_stack[#+2] {$U_3$, $V_3$, $X_3$, or $Y_3$} +@d stack_min(#)==bisect_stack[#+3] + {$U\submin$, $V\submin$, $X\submin$, or $Y\submin$} +@d stack_max(#)==bisect_stack[#+4] + {$U\submax$, $V\submax$, $X\submax$, or $Y\submax$} +@d int_packets=20 {number of words to represent $U_k$, $V_k$, $X_k$, and $Y_k$} +@# +@d u_packet(#)==#-5 +@d v_packet(#)==#-10 +@d x_packet(#)==#-15 +@d y_packet(#)==#-20 +@d l_packets==bisect_ptr-int_packets +@d r_packets==bisect_ptr +@d ul_packet==u_packet(l_packets) {base of $U'_k$ variables} +@d vl_packet==v_packet(l_packets) {base of $V'_k$ variables} +@d xl_packet==x_packet(l_packets) {base of $X'_k$ variables} +@d yl_packet==y_packet(l_packets) {base of $Y'_k$ variables} +@d ur_packet==u_packet(r_packets) {base of $U''_k$ variables} +@d vr_packet==v_packet(r_packets) {base of $V''_k$ variables} +@d xr_packet==x_packet(r_packets) {base of $X''_k$ variables} +@d yr_packet==y_packet(r_packets) {base of $Y''_k$ variables} +@# +@d u1l==stack_1(ul_packet) {$U'_1$} +@d u2l==stack_2(ul_packet) {$U'_2$} +@d u3l==stack_3(ul_packet) {$U'_3$} +@d v1l==stack_1(vl_packet) {$V'_1$} +@d v2l==stack_2(vl_packet) {$V'_2$} +@d v3l==stack_3(vl_packet) {$V'_3$} +@d x1l==stack_1(xl_packet) {$X'_1$} +@d x2l==stack_2(xl_packet) {$X'_2$} +@d x3l==stack_3(xl_packet) {$X'_3$} +@d y1l==stack_1(yl_packet) {$Y'_1$} +@d y2l==stack_2(yl_packet) {$Y'_2$} +@d y3l==stack_3(yl_packet) {$Y'_3$} +@d u1r==stack_1(ur_packet) {$U''_1$} +@d u2r==stack_2(ur_packet) {$U''_2$} +@d u3r==stack_3(ur_packet) {$U''_3$} +@d v1r==stack_1(vr_packet) {$V''_1$} +@d v2r==stack_2(vr_packet) {$V''_2$} +@d v3r==stack_3(vr_packet) {$V''_3$} +@d x1r==stack_1(xr_packet) {$X''_1$} +@d x2r==stack_2(xr_packet) {$X''_2$} +@d x3r==stack_3(xr_packet) {$X''_3$} +@d y1r==stack_1(yr_packet) {$Y''_1$} +@d y2r==stack_2(yr_packet) {$Y''_2$} +@d y3r==stack_3(yr_packet) {$Y''_3$} +@# +@d stack_dx==bisect_stack[bisect_ptr] {stacked value of |delx|} +@d stack_dy==bisect_stack[bisect_ptr+1] {stacked value of |dely|} +@d stack_tol==bisect_stack[bisect_ptr+2] {stacked value of |tol|} +@d stack_uv==bisect_stack[bisect_ptr+3] {stacked value of |uv|} +@d stack_xy==bisect_stack[bisect_ptr+4] {stacked value of |xy|} +@d int_increment=int_packets+int_packets+5 {number of stack words per level} + +@<Glob...@>= +bisect_stack:array[0..bistack_size] of integer; +bisect_ptr:0..bistack_size; + +@ @<Check the ``constant''...@>= +if int_packets+17*int_increment>bistack_size then bad:=19; + +@ Computation of the min and max is a tedious but fairly fast sequence of +instructions; exactly four comparisons are made in each branch. + +@d set_min_max(#)== + if stack_1(#)<0 then + if stack_3(#)>=0 then + begin if stack_2(#)<0 then stack_min(#):=stack_1(#)+stack_2(#) + else stack_min(#):=stack_1(#); + stack_max(#):=stack_1(#)+stack_2(#)+stack_3(#); + if stack_max(#)<0 then stack_max(#):=0; + end + else begin stack_min(#):=stack_1(#)+stack_2(#)+stack_3(#); + if stack_min(#)>stack_1(#) then stack_min(#):=stack_1(#); + stack_max(#):=stack_1(#)+stack_2(#); + if stack_max(#)<0 then stack_max(#):=0; + end + else if stack_3(#)<=0 then + begin if stack_2(#)>0 then stack_max(#):=stack_1(#)+stack_2(#) + else stack_max(#):=stack_1(#); + stack_min(#):=stack_1(#)+stack_2(#)+stack_3(#); + if stack_min(#)>0 then stack_min(#):=0; + end + else begin stack_max(#):=stack_1(#)+stack_2(#)+stack_3(#); + if stack_max(#)<stack_1(#) then stack_max(#):=stack_1(#); + stack_min(#):=stack_1(#)+stack_2(#); + if stack_min(#)>0 then stack_min(#):=0; + end + +@ It's convenient to keep the current values of $l$, $t_1$, and $t_2$ in +the integer form $2^l+2^lt_1$ and $2^l+2^lt_2$. The |cubic_intersection| +routine uses global variables |cur_t| and |cur_tt| for this purpose; +after successful completion, |cur_t| and |cur_tt| will contain |unity| +plus the |scaled| values of $t_1$ and~$t_2$. + +The values of |cur_t| and |cur_tt| will be set to zero if |cubic_intersection| +finds no intersection. The routine gives up and gives an approximate answer +if it has backtracked +more than 5000 times (otherwise there are cases where several minutes +of fruitless computation would be possible). + +@d max_patience=5000 + +@<Glob...@>= +@!cur_t,@!cur_tt:integer; {controls and results of |cubic_intersection|} +@!time_to_go:integer; {this many backtracks before giving up} +@!max_t:integer; {maximum of $2^{l+1}$ so far achieved} + +@ The given cubics $B(w_0,w_1,w_2,w_3;t)$ and +$B(z_0,z_1,z_2,z_3;t)$ are specified in adjacent knot nodes |(p,link(p))| +and |(pp,link(pp))|, respectively. + +@p procedure cubic_intersection(@!p,@!pp:pointer); +label continue, not_found, exit; +var @!q,@!qq:pointer; {|link(p)|, |link(pp)|} +begin time_to_go:=max_patience; max_t:=2; +@<Initialize for intersections at level zero@>; +loop@+ begin continue: + if delx-tol<=stack_max(x_packet(xy))-stack_min(u_packet(uv)) then + if delx+tol>=stack_min(x_packet(xy))-stack_max(u_packet(uv)) then + if dely-tol<=stack_max(y_packet(xy))-stack_min(v_packet(uv)) then + if dely+tol>=stack_min(y_packet(xy))-stack_max(v_packet(uv)) then + begin if cur_t>=max_t then + begin if max_t=two then {we've done 17 bisections} + begin cur_t:=halfp(cur_t+1); cur_tt:=halfp(cur_tt+1); return; + end; + double(max_t); appr_t:=cur_t; appr_tt:=cur_tt; + end; + @<Subdivide for a new level of intersection@>; + goto continue; + end; + if time_to_go>0 then decr(time_to_go) + else begin while appr_t<unity do + begin double(appr_t); double(appr_tt); + end; + cur_t:=appr_t; cur_tt:=appr_tt; return; + end; + @<Advance to the next pair |(cur_t,cur_tt)|@>; + end; +exit:end; + +@ The following variables are global, although they are used only by +|cubic_intersection|, because it is necessary on some machines to +split |cubic_intersection| up into two procedures. + +@<Glob...@>= +@!delx,@!dely:integer; {the components of $\Delta=2^l(w_0-z_0)$} +@!tol:integer; {bound on the uncertainly in the overlap test} +@!uv,@!xy:0..bistack_size; {pointers to the current packets of interest} +@!three_l:integer; {|tol_step| times the bisection level} +@!appr_t,@!appr_tt:integer; {best approximations known to the answers} + +@ We shall assume that the coordinates are sufficiently non-extreme that +integer overflow will not occur. + +@<Initialize for intersections at level zero@>= +q:=link(p); qq:=link(pp); bisect_ptr:=int_packets;@/ +u1r:=right_x(p)-x_coord(p); u2r:=left_x(q)-right_x(p); +u3r:=x_coord(q)-left_x(q); set_min_max(ur_packet);@/ +v1r:=right_y(p)-y_coord(p); v2r:=left_y(q)-right_y(p); +v3r:=y_coord(q)-left_y(q); set_min_max(vr_packet);@/ +x1r:=right_x(pp)-x_coord(pp); x2r:=left_x(qq)-right_x(pp); +x3r:=x_coord(qq)-left_x(qq); set_min_max(xr_packet);@/ +y1r:=right_y(pp)-y_coord(pp); y2r:=left_y(qq)-right_y(pp); +y3r:=y_coord(qq)-left_y(qq); set_min_max(yr_packet);@/ +delx:=x_coord(p)-x_coord(pp); dely:=y_coord(p)-y_coord(pp);@/ +tol:=0; uv:=r_packets; xy:=r_packets; three_l:=0; cur_t:=1; cur_tt:=1 + +@ @<Subdivide for a new level of intersection@>= +stack_dx:=delx; stack_dy:=dely; stack_tol:=tol; stack_uv:=uv; stack_xy:=xy; +bisect_ptr:=bisect_ptr+int_increment;@/ +double(cur_t); double(cur_tt);@/ +u1l:=stack_1(u_packet(uv)); u3r:=stack_3(u_packet(uv)); +u2l:=half(u1l+stack_2(u_packet(uv))); +u2r:=half(u3r+stack_2(u_packet(uv))); +u3l:=half(u2l+u2r); u1r:=u3l; +set_min_max(ul_packet); set_min_max(ur_packet);@/ +v1l:=stack_1(v_packet(uv)); v3r:=stack_3(v_packet(uv)); +v2l:=half(v1l+stack_2(v_packet(uv))); +v2r:=half(v3r+stack_2(v_packet(uv))); +v3l:=half(v2l+v2r); v1r:=v3l; +set_min_max(vl_packet); set_min_max(vr_packet);@/ +x1l:=stack_1(x_packet(xy)); x3r:=stack_3(x_packet(xy)); +x2l:=half(x1l+stack_2(x_packet(xy))); +x2r:=half(x3r+stack_2(x_packet(xy))); +x3l:=half(x2l+x2r); x1r:=x3l; +set_min_max(xl_packet); set_min_max(xr_packet);@/ +y1l:=stack_1(y_packet(xy)); y3r:=stack_3(y_packet(xy)); +y2l:=half(y1l+stack_2(y_packet(xy))); +y2r:=half(y3r+stack_2(y_packet(xy))); +y3l:=half(y2l+y2r); y1r:=y3l; +set_min_max(yl_packet); set_min_max(yr_packet);@/ +uv:=l_packets; xy:=l_packets; +double(delx); double(dely);@/ +tol:=tol-three_l+tol_step; double(tol); three_l:=three_l+tol_step + +@ @<Advance to the next pair |(cur_t,cur_tt)|@>= +not_found: if odd(cur_tt) then + if odd(cur_t) then @<Descend to the previous level and |goto not_found|@> + else begin incr(cur_t); + delx:=delx+stack_1(u_packet(uv))+stack_2(u_packet(uv)) + +stack_3(u_packet(uv)); + dely:=dely+stack_1(v_packet(uv))+stack_2(v_packet(uv)) + +stack_3(v_packet(uv)); + uv:=uv+int_packets; {switch from |l_packet| to |r_packet|} + decr(cur_tt); xy:=xy-int_packets; {switch from |r_packet| to |l_packet|} + delx:=delx+stack_1(x_packet(xy))+stack_2(x_packet(xy)) + +stack_3(x_packet(xy)); + dely:=dely+stack_1(y_packet(xy))+stack_2(y_packet(xy)) + +stack_3(y_packet(xy)); + end +else begin incr(cur_tt); tol:=tol+three_l; + delx:=delx-stack_1(x_packet(xy))-stack_2(x_packet(xy)) + -stack_3(x_packet(xy)); + dely:=dely-stack_1(y_packet(xy))-stack_2(y_packet(xy)) + -stack_3(y_packet(xy)); + xy:=xy+int_packets; {switch from |l_packet| to |r_packet|} + end + +@ @<Descend to the previous level...@>= +begin cur_t:=halfp(cur_t); cur_tt:=halfp(cur_tt); +if cur_t=0 then return; +bisect_ptr:=bisect_ptr-int_increment; three_l:=three_l-tol_step; +delx:=stack_dx; dely:=stack_dy; tol:=stack_tol; uv:=stack_uv; xy:=stack_xy;@/ +goto not_found; +end + +@ The |path_intersection| procedure is much simpler. +It invokes |cubic_intersection| in lexicographic order until finding a +pair of cubics that intersect. The final intersection times are placed in +|cur_t| and~|cur_tt|. + +@p procedure path_intersection(@!h,@!hh:pointer); +label exit; +var @!p,@!pp:pointer; {link registers that traverse the given paths} +@!n,@!nn:integer; {integer parts of intersection times, minus |unity|} +begin @<Change one-point paths into dead cycles@>; +tol_step:=0; +repeat n:=-unity; p:=h; + repeat if right_type(p)<>endpoint then + begin nn:=-unity; pp:=hh; + repeat if right_type(pp)<>endpoint then + begin cubic_intersection(p,pp); + if cur_t>0 then + begin cur_t:=cur_t+n; cur_tt:=cur_tt+nn; return; + end; + end; + nn:=nn+unity; pp:=link(pp); + until pp=hh; + end; + n:=n+unity; p:=link(p); + until p=h; +tol_step:=tol_step+3; +until tol_step>3; +cur_t:=-unity; cur_tt:=-unity; +exit:end; + +@ @<Change one-point paths...@>= +if right_type(h)=endpoint then + begin right_x(h):=x_coord(h); left_x(h):=x_coord(h); + right_y(h):=y_coord(h); left_y(h):=y_coord(h); right_type(h):=explicit; + end; +if right_type(hh)=endpoint then + begin right_x(hh):=x_coord(hh); left_x(hh):=x_coord(hh); + right_y(hh):=y_coord(hh); left_y(hh):=y_coord(hh); right_type(hh):=explicit; + end; + +@* \[24] Dynamic linear equations. +\MP\ users define variables implicitly by stating equations that should be +satisfied; the computer is supposed to be smart enough to solve those equations. +And indeed, the computer tries valiantly to do so, by distinguishing five +different types of numeric values: + +\smallskip\hang +|type(p)=known| is the nice case, when |value(p)| is the |scaled| value +of the variable whose address is~|p|. + +\smallskip\hang +|type(p)=dependent| means that |value(p)| is not present, but |dep_list(p)| +points to a {\sl dependency list\/} that expresses the value of variable~|p| +as a |scaled| number plus a sum of independent variables with |fraction| +coefficients. + +\smallskip\hang +|type(p)=independent| means that |value(p)=64s+m|, where |s>0| is a ``serial +number'' reflecting the time this variable was first used in an equation; +also |0<=m<64|, and each dependent variable +that refers to this one is actually referring to the future value of +this variable times~$2^m$. (Usually |m=0|, but higher degrees of +scaling are sometimes needed to keep the coefficients in dependency lists +from getting too large. The value of~|m| will always be even.) + +\smallskip\hang +|type(p)=numeric_type| means that variable |p| hasn't appeared in an +equation before, but it has been explicitly declared to be numeric. + +\smallskip\hang +|type(p)=undefined| means that variable |p| hasn't appeared before. + +\smallskip\noindent +We have actually discussed these five types in the reverse order of their +history during a computation: Once |known|, a variable never again +becomes |dependent|; once |dependent|, it almost never again becomes +|independent|; once |independent|, it never again becomes |numeric_type|; +and once |numeric_type|, it never again becomes |undefined| (except +of course when the user specifically decides to scrap the old value +and start again). A backward step may, however, take place: Sometimes +a |dependent| variable becomes |independent| again, when one of the +independent variables it depends on is reverting to |undefined|. + + +The next patch detects overflow of independent-variable serial +numbers. Diagnosed and patched by Thorsten Dahlheimer. + +@d s_scale=64 {the serial numbers are multiplied by this factor} +@d max_indep_vars==@'177777777 {$2^{25}-1$} +@d max_serial_no==@'17777777700 {|max_indep_vars*s_scale|} +@d new_indep(#)== {create a new independent variable} + begin if serial_no=max_serial_no then + overflow("independent variables",max_indep_vars); + type(#):=independent; serial_no:=serial_no+s_scale; + value(#):=serial_no; + end + +@<Glob...@>= +@!serial_no:integer; {the most recent serial number, times |s_scale|} + +@ @<Make variable |q+s| newly independent@>=new_indep(q+s) + +@ But how are dependency lists represented? It's simple: The linear combination +$\alpha_1v_1+\cdots+\alpha_kv_k+\beta$ appears in |k+1| value nodes. If +|q=dep_list(p)| points to this list, and if |k>0|, then |value(q)= +@t$\alpha_1$@>| (which is a |fraction|); |info(q)| points to the location +of $\alpha_1$; and |link(p)| points to the dependency list +$\alpha_2v_2+\cdots+\alpha_kv_k+\beta$. On the other hand if |k=0|, +then |value(q)=@t$\beta$@>| (which is |scaled|) and |info(q)=null|. +The independent variables $v_1$, \dots,~$v_k$ have been sorted so that +they appear in decreasing order of their |value| fields (i.e., of +their serial numbers). \ (It is convenient to use decreasing order, +since |value(null)=0|. If the independent variables were not sorted by +serial number but by some other criterion, such as their location in |mem|, +the equation-solving mechanism would be too system-dependent, because +the ordering can affect the computed results.) + +The |link| field in the node that contains the constant term $\beta$ is +called the {\sl final link\/} of the dependency list. \MP\ maintains +a doubly-linked master list of all dependency lists, in terms of a permanently +allocated node +in |mem| called |dep_head|. If there are no dependencies, we have +|link(dep_head)=dep_head| and |prev_dep(dep_head)=dep_head|; +otherwise |link(dep_head)| points to the first dependent variable, say~|p|, +and |prev_dep(p)=dep_head|. We have |type(p)=dependent|, and |dep_list(p)| +points to its dependency list. If the final link of that dependency list +occurs in location~|q|, then |link(q)| points to the next dependent +variable (say~|r|); and we have |prev_dep(r)=q|, etc. + +@d dep_list(#)==link(value_loc(#)) + {half of the |value| field in a |dependent| variable} +@d prev_dep(#)==info(value_loc(#)) + {the other half; makes a doubly linked list} +@d dep_node_size=2 {the number of words per dependency node} + +@<Initialize table entries...@>= serial_no:=0; +link(dep_head):=dep_head; prev_dep(dep_head):=dep_head; +info(dep_head):=null; dep_list(dep_head):=null; + +@ Actually the description above contains a little white lie. There's +another kind of variable called |proto_dependent|, which is +just like a |dependent| one except that the $\alpha$ coefficients +in its dependency list are |scaled| instead of being fractions. +Proto-dependency lists are mixed with dependency lists in the +nodes reachable from |dep_head|. + +@ Here is a procedure that prints a dependency list in symbolic form. +The second parameter should be either |dependent| or |proto_dependent|, +to indicate the scaling of the coefficients. + +@<Declare subroutines for printing expressions@>= +procedure print_dependency(@!p:pointer;@!t:small_number); +label exit; +var @!v:integer; {a coefficient} +@!pp,@!q:pointer; {for list manipulation} +begin pp:=p; +loop@+ begin v:=abs(value(p)); q:=info(p); + if q=null then {the constant term} + begin if (v<>0)or(p=pp) then + begin if value(p)>0 then if p<>pp then print_char("+"); + print_scaled(value(p)); + end; + return; + end; + @<Print the coefficient, unless it's $\pm1.0$@>; + if type(q)<>independent then confusion("dep"); +@:this can't happen dep}{\quad dep@> + print_variable_name(q); v:=value(q) mod s_scale; + while v>0 do + begin print("*4"); v:=v-2; + end; + p:=link(p); + end; +exit:end; + +@ @<Print the coefficient, unless it's $\pm1.0$@>= +if value(p)<0 then print_char("-") +else if p<>pp then print_char("+"); +if t=dependent then v:=round_fraction(v); +if v<>unity then print_scaled(v) + +@ The maximum absolute value of a coefficient in a given dependency list +is returned by the following simple function. + +@p function max_coef(@!p:pointer):fraction; +var @!x:fraction; {the maximum so far} +begin x:=0; +while info(p)<>null do + begin if abs(value(p))>x then x:=abs(value(p)); + p:=link(p); + end; +max_coef:=x; +end; + +@ One of the main operations needed on dependency lists is to add a multiple +of one list to the other; we call this |p_plus_fq|, where |p| and~|q| point +to dependency lists and |f| is a fraction. + +If the coefficient of any independent variable becomes |coef_bound| or +more, in absolute value, this procedure changes the type of that variable +to `|independent_needing_fix|', and sets the global variable |fix_needed| +to~|true|. The value of $|coef_bound|=\mu$ is chosen so that +$\mu^2+\mu<8$; this means that the numbers we deal with won't +get too large. (Instead of the ``optimum'' $\mu=(\sqrt{33}-1)/2\approx +2.3723$, the safer value 7/3 is taken as the threshold.) + +The changes mentioned in the preceding paragraph are actually done only if +the global variable |watch_coefs| is |true|. But it usually is; in fact, +it is |false| only when \MP\ is making a dependency list that will soon +be equated to zero. + +Several procedures that act on dependency lists, including |p_plus_fq|, +set the global variable |dep_final| to the final (constant term) node of +the dependency list that they produce. + +@d coef_bound==@'4525252525 {|fraction| approximation to 7/3} +@d independent_needing_fix=0 + +@<Glob...@>= +@!fix_needed:boolean; {does at least one |independent| variable need scaling?} +@!watch_coefs:boolean; {should we scale coefficients that exceed |coef_bound|?} +@!dep_final:pointer; {location of the constant term and final link} + +@ @<Set init...@>= +fix_needed:=false; watch_coefs:=true; + +@ The |p_plus_fq| procedure has a fourth parameter, |t|, that should be +set to |proto_dependent| if |p| is a proto-dependency list. In this +case |f| will be |scaled|, not a |fraction|. Similarly, the fifth parameter~|tt| +should be |proto_dependent| if |q| is a proto-dependency list. + +List |q| is unchanged by the operation; but list |p| is totally destroyed. + +The final link of the dependency list or proto-dependency list returned +by |p_plus_fq| is the same as the original final link of~|p|. Indeed, the +constant term of the result will be located in the same |mem| location +as the original constant term of~|p|. + +Coefficients of the result are assumed to be zero if they are less than +a certain threshold. This compensates for inevitable rounding errors, +and tends to make more variables `|known|'. The threshold is approximately +$10^{-5}$ in the case of normal dependency lists, $10^{-4}$ for +proto-dependencies. + +@d fraction_threshold=2685 {a |fraction| coefficient less than this is zeroed} +@d half_fraction_threshold=1342 {half of |fraction_threshold|} +@d scaled_threshold=8 {a |scaled| coefficient less than this is zeroed} +@d half_scaled_threshold=4 {half of |scaled_threshold|} + +@<Declare basic dependency-list subroutines@>= +function p_plus_fq(@!p:pointer;@!f:integer;@!q:pointer; + @!t,@!tt:small_number):pointer; +label done; +var @!pp,@!qq:pointer; {|info(p)| and |info(q)|, respectively} +@!r,@!s:pointer; {for list manipulation} +@!threshold:integer; {defines a neighborhood of zero} +@!v:integer; {temporary register} +begin if t=dependent then threshold:=fraction_threshold +else threshold:=scaled_threshold; +r:=temp_head; pp:=info(p); qq:=info(q); +loop@+ if pp=qq then + if pp=null then goto done + else @<Contribute a term from |p|, plus |f| times the + corresponding term from |q|@> + else if value(pp)<value(qq) then + @<Contribute a term from |q|, multiplied by~|f|@> + else begin link(r):=p; r:=p; p:=link(p); pp:=info(p); + end; +done: if t=dependent then + value(p):=slow_add(value(p),take_fraction(value(q),f)) +else value(p):=slow_add(value(p),take_scaled(value(q),f)); +link(r):=p; dep_final:=p; p_plus_fq:=link(temp_head); +end; + +@ @<Contribute a term from |p|, plus |f|...@>= +begin if tt=dependent then v:=value(p)+take_fraction(f,value(q)) +else v:=value(p)+take_scaled(f,value(q)); +value(p):=v; s:=p; p:=link(p); +if abs(v)<threshold then free_node(s,dep_node_size) +else begin if abs(v)>=coef_bound then if watch_coefs then + begin type(qq):=independent_needing_fix; fix_needed:=true; + end; + link(r):=s; r:=s; + end; +pp:=info(p); q:=link(q); qq:=info(q); +end + +@ @<Contribute a term from |q|, multiplied by~|f|@>= +begin if tt=dependent then v:=take_fraction(f,value(q)) +else v:=take_scaled(f,value(q)); +if abs(v)>halfp(threshold) then + begin s:=get_node(dep_node_size); info(s):=qq; value(s):=v; + if abs(v)>=coef_bound then if watch_coefs then + begin type(qq):=independent_needing_fix; fix_needed:=true; + end; + link(r):=s; r:=s; + end; +q:=link(q); qq:=info(q); +end + +@ It is convenient to have another subroutine for the special case +of |p_plus_fq| when |f=1.0|. In this routine lists |p| and |q| are +both of the same type~|t| (either |dependent| or |proto_dependent|). + +@p function p_plus_q(@!p:pointer;@!q:pointer;@!t:small_number):pointer; +label done; +var @!pp,@!qq:pointer; {|info(p)| and |info(q)|, respectively} +@!r,@!s:pointer; {for list manipulation} +@!threshold:integer; {defines a neighborhood of zero} +@!v:integer; {temporary register} +begin if t=dependent then threshold:=fraction_threshold +else threshold:=scaled_threshold; +r:=temp_head; pp:=info(p); qq:=info(q); +loop@+ if pp=qq then + if pp=null then goto done + else @<Contribute a term from |p|, plus the + corresponding term from |q|@> + else if value(pp)<value(qq) then + begin s:=get_node(dep_node_size); info(s):=qq; value(s):=value(q); + q:=link(q); qq:=info(q); link(r):=s; r:=s; + end + else begin link(r):=p; r:=p; p:=link(p); pp:=info(p); + end; +done: value(p):=slow_add(value(p),value(q)); +link(r):=p; dep_final:=p; p_plus_q:=link(temp_head); +end; + +@ @<Contribute a term from |p|, plus the...@>= +begin v:=value(p)+value(q); +value(p):=v; s:=p; p:=link(p); pp:=info(p); +if abs(v)<threshold then free_node(s,dep_node_size) +else begin if abs(v)>=coef_bound then if watch_coefs then + begin type(qq):=independent_needing_fix; fix_needed:=true; + end; + link(r):=s; r:=s; + end; +q:=link(q); qq:=info(q); +end + +@ A somewhat simpler routine will multiply a dependency list +by a given constant~|v|. The constant is either a |fraction| less than +|fraction_one|, or it is |scaled|. In the latter case we might be forced to +convert a dependency list to a proto-dependency list. +Parameters |t0| and |t1| are the list types before and after; +they should agree unless |t0=dependent| and |t1=proto_dependent| +and |v_is_scaled=true|. + +@p function p_times_v(@!p:pointer;@!v:integer; + @!t0,@!t1:small_number;@!v_is_scaled:boolean):pointer; +var @!r,@!s:pointer; {for list manipulation} +@!w:integer; {tentative coefficient} +@!threshold:integer; +@!scaling_down:boolean; +begin if t0<>t1 then scaling_down:=true@+else scaling_down:=not v_is_scaled; +if t1=dependent then threshold:=half_fraction_threshold +else threshold:=half_scaled_threshold; +r:=temp_head; +while info(p)<>null do + begin if scaling_down then w:=take_fraction(v,value(p)) + else w:=take_scaled(v,value(p)); + if abs(w)<=threshold then + begin s:=link(p); free_node(p,dep_node_size); p:=s; + end + else begin if abs(w)>=coef_bound then + begin fix_needed:=true; type(info(p)):=independent_needing_fix; + end; + link(r):=p; r:=p; value(p):=w; p:=link(p); + end; + end; +link(r):=p; +if v_is_scaled then value(p):=take_scaled(value(p),v) +else value(p):=take_fraction(value(p),v); +p_times_v:=link(temp_head); +end; + +@ Similarly, we sometimes need to divide a dependency list +by a given |scaled| constant. + +@<Declare basic dependency-list subroutines@>= +function p_over_v(@!p:pointer;@!v:scaled; + @!t0,@!t1:small_number):pointer; +var @!r,@!s:pointer; {for list manipulation} +@!w:integer; {tentative coefficient} +@!threshold:integer; +@!scaling_down:boolean; +begin if t0<>t1 then scaling_down:=true@+else scaling_down:=false; +if t1=dependent then threshold:=half_fraction_threshold +else threshold:=half_scaled_threshold; +r:=temp_head; +while info(p)<>null do + begin if scaling_down then + if abs(v)<@'2000000 then w:=make_scaled(value(p),v*@'10000) + else w:=make_scaled(round_fraction(value(p)),v) + else w:=make_scaled(value(p),v); + if abs(w)<=threshold then + begin s:=link(p); free_node(p,dep_node_size); p:=s; + end + else begin if abs(w)>=coef_bound then + begin fix_needed:=true; type(info(p)):=independent_needing_fix; + end; + link(r):=p; r:=p; value(p):=w; p:=link(p); + end; + end; +link(r):=p; value(p):=make_scaled(value(p),v); +p_over_v:=link(temp_head); +end; + +@ Here's another utility routine for dependency lists. When an independent +variable becomes dependent, we want to remove it from all existing +dependencies. The |p_with_x_becoming_q| function computes the +dependency list of~|p| after variable~|x| has been replaced by~|q|. + +This procedure has basically the same calling conventions as |p_plus_fq|: +List~|q| is unchanged; list~|p| is destroyed; the constant node and the +final link are inherited from~|p|; and the fourth parameter tells whether +or not |p| is |proto_dependent|. However, the global variable |dep_final| +is not altered if |x| does not occur in list~|p|. + +@p function p_with_x_becoming_q(@!p,@!x,@!q:pointer;@!t:small_number):pointer; +var @!r,@!s:pointer; {for list manipulation} +@!v:integer; {coefficient of |x|} +@!sx:integer; {serial number of |x|} +begin s:=p; r:=temp_head; sx:=value(x); +while value(info(s))>sx do + begin r:=s; s:=link(s); + end; +if info(s)<>x then p_with_x_becoming_q:=p +else begin link(temp_head):=p; link(r):=link(s); v:=value(s); + free_node(s,dep_node_size); + p_with_x_becoming_q:=p_plus_fq(link(temp_head),v,q,t,dependent); + end; +end; + +@ Here's a simple procedure that reports an error when a variable +has just received a known value that's out of the required range. + +@<Declare basic dependency-list subroutines@>= +procedure val_too_big(@!x:scaled); +begin if internal[warning_check]>0 then + begin print_err("Value is too large ("); print_scaled(x); print_char(")"); +@.Value is too large@> + help4("The equation I just processed has given some variable")@/ + ("a value of 4096 or more. Continue and I'll try to cope")@/ + ("with that big value; but it might be dangerous.")@/ + ("(Set warningcheck:=0 to suppress this message.)"); + error; + end; +end; + +@ When a dependent variable becomes known, the following routine +removes its dependency list. Here |p| points to the variable, and +|q| points to the dependency list (which is one node long). + +@<Declare basic dependency-list subroutines@>= +procedure make_known(@!p,@!q:pointer); +var @!t:dependent..proto_dependent; {the previous type} +begin prev_dep(link(q)):=prev_dep(p); +link(prev_dep(p)):=link(q); t:=type(p); +type(p):=known; value(p):=value(q); free_node(q,dep_node_size); +if abs(value(p))>=fraction_one then val_too_big(value(p)); +if internal[tracing_equations]>0 then if interesting(p) then + begin begin_diagnostic; print_nl("#### "); +@:]]]\#\#\#\#_}{\.{\#\#\#\#}@> + print_variable_name(p); print_char("="); print_scaled(value(p)); + end_diagnostic(false); + end; +if cur_exp=p then if cur_type=t then + begin cur_type:=known; cur_exp:=value(p); + free_node(p,value_node_size); + end; +end; + +@ The |fix_dependencies| routine is called into action when |fix_needed| +has been triggered. The program keeps a list~|s| of independent variables +whose coefficients must be divided by~4. + +In unusual cases, this fixup process might reduce one or more coefficients +to zero, so that a variable will become known more or less by default. + +@<Declare basic dependency-list subroutines@>= +procedure fix_dependencies; +label done; +var @!p,@!q,@!r,@!s,@!t:pointer; {list manipulation registers} +@!x:pointer; {an independent variable} +begin r:=link(dep_head); s:=null; +while r<>dep_head do + begin t:=r; + @<Run through the dependency list for variable |t|, fixing + all nodes, and ending with final link~|q|@>; + r:=link(q); + if q=dep_list(t) then make_known(t,q); + end; +while s<>null do + begin p:=link(s); x:=info(s); free_avail(s); s:=p; + type(x):=independent; value(x):=value(x)+2; + end; +fix_needed:=false; +end; + +@ @d independent_being_fixed=1 {this variable already appears in |s|} + +@<Run through the dependency list for variable |t|...@>= +r:=value_loc(t); {|link(r)=dep_list(t)|} +loop@+ begin q:=link(r); x:=info(q); + if x=null then goto done; + if type(x)<=independent_being_fixed then + begin if type(x)<independent_being_fixed then + begin p:=get_avail; link(p):=s; s:=p; + info(s):=x; type(x):=independent_being_fixed; + end; + value(q):=value(q) div 4; + if value(q)=0 then + begin link(r):=link(q); free_node(q,dep_node_size); q:=r; + end; + end; + r:=q; + end; +done: + +@ The |new_dep| routine installs a dependency list~|p| into the value node~|q|, +linking it into the list of all known dependencies. We assume that +|dep_final| points to the final node of list~|p|. + +@p procedure new_dep(@!q,@!p:pointer); +var @!r:pointer; {what used to be the first dependency} +begin dep_list(q):=p; prev_dep(q):=dep_head; +r:=link(dep_head); link(dep_final):=r; prev_dep(r):=dep_final; +link(dep_head):=q; +end; + +@ Here is one of the ways a dependency list gets started. +The |const_dependency| routine produces a list that has nothing but +a constant term. + +@p function const_dependency(@!v:scaled):pointer; +begin dep_final:=get_node(dep_node_size); +value(dep_final):=v; info(dep_final):=null; +const_dependency:=dep_final; +end; + +@ And here's a more interesting way to start a dependency list from scratch: +The parameter to |single_dependency| is the location of an +independent variable~|x|, and the result is the simple dependency list +`|x+0|'. + +In the unlikely event that the given independent variable has been doubled so +often that we can't refer to it with a nonzero coefficient, +|single_dependency| returns the simple list `0'. This case can be +recognized by testing that the returned list pointer is equal to +|dep_final|. + +@p function single_dependency(@!p:pointer):pointer; +var @!q:pointer; {the new dependency list} +@!m:integer; {the number of doublings} +begin m:=value(p) mod s_scale; +if m>28 then single_dependency:=const_dependency(0) +else begin q:=get_node(dep_node_size); + value(q):=two_to_the[28-m]; info(q):=p;@/ + link(q):=const_dependency(0); single_dependency:=q; + end; +end; + +@ We sometimes need to make an exact copy of a dependency list. + +@p function copy_dep_list(@!p:pointer):pointer; +label done; +var @!q:pointer; {the new dependency list} +begin q:=get_node(dep_node_size); dep_final:=q; +loop@+ begin info(dep_final):=info(p); value(dep_final):=value(p); + if info(dep_final)=null then goto done; + link(dep_final):=get_node(dep_node_size); + dep_final:=link(dep_final); p:=link(p); + end; +done:copy_dep_list:=q; +end; + +@ But how do variables normally become known? Ah, now we get to the heart of the +equation-solving mechanism. The |linear_eq| procedure is given a |dependent| +or |proto_dependent| list,~|p|, in which at least one independent variable +appears. It equates this list to zero, by choosing an independent variable +with the largest coefficient and making it dependent on the others. The +newly dependent variable is eliminated from all current dependencies, +thereby possibly making other dependent variables known. + +The given list |p| is, of course, totally destroyed by all this processing. + +@p procedure linear_eq(@!p:pointer;@!t:small_number); +var @!q,@!r,@!s:pointer; {for link manipulation} +@!x:pointer; {the variable that loses its independence} +@!n:integer; {the number of times |x| had been halved} +@!v:integer; {the coefficient of |x| in list |p|} +@!prev_r:pointer; {lags one step behind |r|} +@!final_node:pointer; {the constant term of the new dependency list} +@!w:integer; {a tentative coefficient} +begin @<Find a node |q| in list |p| whose coefficient |v| is largest@>; +x:=info(q); n:=value(x) mod s_scale;@/ +@<Divide list |p| by |-v|, removing node |q|@>; +if internal[tracing_equations]>0 then @<Display the new dependency@>; +@<Simplify all existing dependencies by substituting for |x|@>; +@<Change variable |x| from |independent| to |dependent| or |known|@>; +if fix_needed then fix_dependencies; +end; + +@ @<Find a node |q| in list |p| whose coefficient |v| is largest@>= +q:=p; r:=link(p); v:=value(q); +while info(r)<>null do + begin if abs(value(r))>abs(v) then + begin q:=r; v:=value(r); + end; + r:=link(r); + end + +@ Here we want to change the coefficients from |scaled| to |fraction|, +except in the constant term. In the common case of a trivial equation +like `\.{x=3.14}', we will have |v=-fraction_one|, |q=p|, and |t=dependent|. + +@<Divide list |p| by |-v|, removing node |q|@>= +s:=temp_head; link(s):=p; r:=p; +repeat if r=q then + begin link(s):=link(r); free_node(r,dep_node_size); + end +else begin w:=make_fraction(value(r),v); + if abs(w)<=half_fraction_threshold then + begin link(s):=link(r); free_node(r,dep_node_size); + end + else begin value(r):=-w; s:=r; + end; + end; +r:=link(s); +until info(r)=null; +if t=proto_dependent then value(r):=-make_scaled(value(r),v) +else if v<>-fraction_one then value(r):=-make_fraction(value(r),v); +final_node:=r; p:=link(temp_head) + +@ @<Display the new dependency@>= +if interesting(x) then + begin begin_diagnostic; print_nl("## "); print_variable_name(x); +@:]]]\#\#_}{\.{\#\#}@> + w:=n; + while w>0 do + begin print("*4"); w:=w-2; + end; + print_char("="); print_dependency(p,dependent); end_diagnostic(false); + end + +@ @<Simplify all existing dependencies by substituting for |x|@>= +prev_r:=dep_head; r:=link(dep_head); +while r<>dep_head do + begin s:=dep_list(r); q:=p_with_x_becoming_q(s,x,p,type(r)); + if info(q)=null then make_known(r,q) + else begin dep_list(r):=q; + repeat q:=link(q); + until info(q)=null; + prev_r:=q; + end; + r:=link(prev_r); + end + +@ @<Change variable |x| from |independent| to |dependent| or |known|@>= +if n>0 then @<Divide list |p| by $2^n$@>; +if info(p)=null then + begin type(x):=known; + value(x):=value(p); + if abs(value(x))>=fraction_one then val_too_big(value(x)); + free_node(p,dep_node_size); + if cur_exp=x then if cur_type=independent then + begin cur_exp:=value(x); cur_type:=known; + free_node(x,value_node_size); + end; + end +else begin type(x):=dependent; dep_final:=final_node; new_dep(x,p); + if cur_exp=x then if cur_type=independent then cur_type:=dependent; + end + +@ @<Divide list |p| by $2^n$@>= +begin s:=temp_head; link(temp_head):=p; r:=p; +repeat if n>30 then w:=0 +else w:=value(r) div two_to_the[n]; +if (abs(w)<=half_fraction_threshold)and(info(r)<>null) then + begin link(s):=link(r); + free_node(r,dep_node_size); + end +else begin value(r):=w; s:=r; + end; +r:=link(s); +until info(s)=null; +p:=link(temp_head); +end + +@ The |check_mem| procedure, which is used only when \MP\ is being +debugged, makes sure that the current dependency lists are well formed. + +@<Check the list of linear dependencies@>= +q:=dep_head; p:=link(q); +while p<>dep_head do + begin if prev_dep(p)<>q then + begin print_nl("Bad PREVDEP at "); print_int(p); +@.Bad PREVDEP...@> + end; + p:=dep_list(p); + loop @+begin r:=info(p); q:=p; p:=link(q); + if r=null then goto done3; + if value(info(p))>=value(r) then + begin print_nl("Out of order at "); print_int(p); +@.Out of order...@> + end; + end; +done3: do_nothing; + end + +@* \[25] Dynamic nonlinear equations. +Variables of numeric type are maintained by the general scheme of +independent, dependent, and known values that we have just studied; +and the components of pair and transform variables are handled in the +same way. But \MP\ also has five other types of values: \&{boolean}, +\&{string}, \&{pen}, \&{path}, and \&{picture}; what about them? + +Equations are allowed between nonlinear quantities, but only in a +simple form. Two variables that haven't yet been assigned values are +either equal to each other, or they're not. + +Before a boolean variable has received a value, its type is |unknown_boolean|; +similarly, there are variables whose type is |unknown_string|, |unknown_pen|, +|unknown_path|, and |unknown_picture|. In such cases the value is either +|null| (which means that no other variables are equivalent to this one), or +it points to another variable of the same undefined type. The pointers in the +latter case form a cycle of nodes, which we shall call a ``ring.'' +Rings of undefined variables may include capsules, which arise as +intermediate results within expressions or as \&{expr} parameters to macros. + +When one member of a ring receives a value, the same value is given to +all the other members. In the case of paths and pictures, this implies +making separate copies of a potentially large data structure; users should +restrain their enthusiasm for such generality, unless they have lots and +lots of memory space. + +@ The following procedure is called when a capsule node is being +added to a ring (e.g., when an unknown variable is mentioned in an expression). + +@p function new_ring_entry(@!p:pointer):pointer; +var q:pointer; {the new capsule node} +begin q:=get_node(value_node_size); name_type(q):=capsule; +type(q):=type(p); +if value(p)=null then value(q):=p@+else value(q):=value(p); +value(p):=q; +new_ring_entry:=q; +end; + +@ Conversely, we might delete a capsule or a variable before it becomes known. +The following procedure simply detaches a quantity from its ring, +without recycling the storage. + +@<Declare the recycling subroutines@>= +procedure ring_delete(@!p:pointer); +var @!q:pointer; +begin q:=value(p); +if q<>null then if q<>p then + begin while value(q)<>p do q:=value(q); + value(q):=value(p); + end; +end; + +@ Eventually there might be an equation that assigns values to all of the +variables in a ring. The |nonlinear_eq| subroutine does the necessary +propagation of values. + +If the parameter |flush_p| is |true|, node |p| itself needn't receive a +value, it will soon be recycled. + +@p procedure nonlinear_eq(@!v:integer;@!p:pointer;@!flush_p:boolean); +var @!t:small_number; {the type of ring |p|} +@!q,@!r:pointer; {link manipulation registers} +begin t:=type(p)-unknown_tag; q:=value(p); +if flush_p then type(p):=vacuous@+else p:=q; +repeat r:=value(q); type(q):=t; +case t of +boolean_type: value(q):=v; +string_type: begin value(q):=v; add_str_ref(v); + end; +pen_type: value(q):=copy_pen(v); +path_type: value(q):=copy_path(v); +picture_type: begin value(q):=v; add_edge_ref(v); + end; +end; {there ain't no more cases} +q:=r; +until q=p; +end; + +@ If two members of rings are equated, and if they have the same type, +the |ring_merge| procedure is called on to make them equivalent. + +@p procedure ring_merge(@!p,@!q:pointer); +label exit; +var @!r:pointer; {traverses one list} +begin r:=value(p); +while r<>p do + begin if r=q then + begin @<Exclaim about a redundant equation@>; + return; + end; + r:=value(r); + end; +r:=value(p); value(p):=value(q); value(q):=r; +exit:end; + +@ @<Exclaim about a redundant equation@>= +begin print_err("Redundant equation");@/ +@.Redundant equation@> +help2("I already knew that this equation was true.")@/ + ("But perhaps no harm has been done; let's continue.");@/ +put_get_error; +end + +@* \[26] Introduction to the syntactic routines. +Let's pause a moment now and try to look at the Big Picture. +The \MP\ program consists of three main parts: syntactic routines, +semantic routines, and output routines. The chief purpose of the +syntactic routines is to deliver the user's input to the semantic routines, +while parsing expressions and locating operators and operands. The +semantic routines act as an interpreter responding to these operators, +which may be regarded as commands. And the output routines are +periodically called on to produce compact font descriptions that can be +used for typesetting or for making interim proof drawings. We have +discussed the basic data structures and many of the details of semantic +operations, so we are good and ready to plunge into the part of \MP\ that +actually controls the activities. + +Our current goal is to come to grips with the |get_next| procedure, +which is the keystone of \MP's input mechanism. Each call of |get_next| +sets the value of three variables |cur_cmd|, |cur_mod|, and |cur_sym|, +representing the next input token. +$$\vbox{\halign{#\hfil\cr + \hbox{|cur_cmd| denotes a command code from the long list of codes + given earlier;}\cr + \hbox{|cur_mod| denotes a modifier of the command code;}\cr + \hbox{|cur_sym| is the hash address of the symbolic token that was + just scanned,}\cr + \hbox{\qquad or zero in the case of a numeric or string + or capsule token.}\cr}}$$ +Underlying this external behavior of |get_next| is all the machinery +necessary to convert from character files to tokens. At a given time we +may be only partially finished with the reading of several files (for +which \&{input} was specified), and partially finished with the expansion +of some user-defined macros and/or some macro parameters, and partially +finished reading some text that the user has inserted online, +and so on. When reading a character file, the characters must be +converted to tokens; comments and blank spaces must +be removed, numeric and string tokens must be evaluated. + +To handle these situations, which might all be present simultaneously, +\MP\ uses various stacks that hold information about the incomplete +activities, and there is a finite state control for each level of the +input mechanism. These stacks record the current state of an implicitly +recursive process, but the |get_next| procedure is not recursive. + +@<Glob...@>= +@!cur_cmd: eight_bits; {current command set by |get_next|} +@!cur_mod: integer; {operand of current command} +@!cur_sym: halfword; {hash address of current symbol} + +@ The |print_cmd_mod| routine prints a symbolic interpretation of a +command code and its modifier. +It consists of a rather tedious sequence of print +commands, and most of it is essentially an inverse to the |primitive| +routine that enters a \MP\ primitive into |hash| and |eqtb|. Therefore almost +all of this procedure appears elsewhere in the program, together with the +corresponding |primitive| calls. + +@<Declare the procedure called |print_cmd_mod|@>= +procedure print_cmd_mod(@!c,@!m:integer); +begin case c of +@t\4@>@<Cases of |print_cmd_mod| for symbolic printing of primitives@>@/ +othercases print("[unknown command code!]") +endcases; +end; + +@ Here is a procedure that displays a given command in braces, in the +user's transcript file. + +@d show_cur_cmd_mod==show_cmd_mod(cur_cmd,cur_mod) + +@p procedure show_cmd_mod(@!c,@!m:integer); +begin begin_diagnostic; print_nl("{"); +print_cmd_mod(c,m); print_char("}"); +end_diagnostic(false); +end; + +@* \[27] Input stacks and states. +The state of \MP's input mechanism appears in the input stack, whose +entries are records with five fields, called |index|, |start|, |loc|, +|limit|, and |name|. The top element of this stack is maintained in a +global variable for which no subscripting needs to be done; the other +elements of the stack appear in an array. Hence the stack is declared thus: + +@<Types...@>= +@!in_state_record = record + @!index_field: quarterword; + @!start_field,@!loc_field, @!limit_field, @!name_field: halfword; + end; + +@ @<Glob...@>= +@!input_stack : array[0..stack_size] of in_state_record; +@!input_ptr : 0..stack_size; {first unused location of |input_stack|} +@!max_in_stack: 0..stack_size; {largest value of |input_ptr| when pushing} +@!cur_input : in_state_record; {the ``top'' input state} + +@ We've already defined the special variable |@!loc==cur_input.loc_field| +in our discussion of basic input-output routines. The other components of +|cur_input| are defined in the same way: + +@d index==cur_input.index_field {reference for buffer information} +@d start==cur_input.start_field {starting position in |buffer|} +@d limit==cur_input.limit_field {end of current line in |buffer|} +@d name==cur_input.name_field {name of the current file} + +@ Let's look more closely now at the five control variables +(|index|,~|start|,~|loc|,~|limit|,~|name|), +assuming that \MP\ is reading a line of characters that have been input +from some file or from the user's terminal. There is an array called +|buffer| that acts as a stack of all lines of characters that are +currently being read from files, including all lines on subsidiary +levels of the input stack that are not yet completed. \MP\ will return to +the other lines when it is finished with the present input file. + +(Incidentally, on a machine with byte-oriented addressing, it would be +appropriate to combine |buffer| with the |str_pool| array, +letting the buffer entries grow downward from the top of the string pool +and checking that these two tables don't bump into each other.) + +The line we are currently working on begins in position |start| of the +buffer; the next character we are about to read is |buffer[loc]|; and +|limit| is the location of the last character present. We always have +|loc<=limit|. For convenience, |buffer[limit]| has been set to |"%"|, so +that the end of a line is easily sensed. + +The |name| variable is a string number that designates the name of +the current file, if we are reading an ordinary text file. Special codes +|is_term..max_spec_src| indicate other sources of input text. + +@d is_term=0 {|name| value when reading from the terminal for normal input} +@d is_read=1 {|name| value when executing a \&{readstring} or \&{readfrom}} +@d is_scantok=2 {|name| value when reading text generated by \&{scantokens}} +@d max_spec_src=is_scantok + +@ Additional information about the current line is available via the +|index| variable, which counts how many lines of characters are present +in the buffer below the current level. We have |index=0| when reading +from the terminal and prompting the user for each line; then if the user types, +e.g., `\.{input figs}', we will have |index=1| while reading +the file \.{figs.mp}. However, it does not follow that |index| is the +same as the input stack pointer, since many of the levels on the input +stack may come from token lists and some |index| values may correspond +to \.{MPX} files that are not currently on the stack. + +The global variable |in_open| is equal to the highest |index| value counting +\.{MPX} files but excluding token-list input levels. Thus, the number of +partially read lines in the buffer is |in_open+1| and we have |in_open>=index| +when we are not reading a token list. + +If we are not currently reading from the terminal, +we are reading from the file variable |input_file[index]|. We use +the notation |terminal_input| as a convenient abbreviation for |name=is_term|, +and |cur_file| as an abbreviation for |input_file[index]|. + +When \MP\ is not reading from the terminal, the global variable |line| contains +the line number in the current file, for use in error messages. More precisely, +|line| is a macro for |line_stack[index]| and the |line_stack| array gives +the line number for each file in the |input_file| array. + +When an \.{MPX} file is opened the file name is stored in the |mpx_name| +array so that the name doesn't get lost when the file is temporarily removed +from the input stack. +Thus when |input_file[k]| is an \.{MPX} file, its name is |mpx_name[k]| +and it contains translated \TeX\ pictures for |input_file[k-1]|. +Since this is not an \.{MPX} file, we have +$$ \hbox{|mpx_name[k-1]<=absent|}. $$ +This |name| field is set to |finished| when |input_file[k]| is completely +read. + +If more information about the input state is needed, it can be +included in small arrays like those shown here. For example, +the current page or segment number in the input file might be put +into a variable |@!page|, that is really a macro for the current entry +in `\ignorespaces|@!page_stack:array[0..max_in_open] of integer|\unskip' +by analogy with |line_stack|. +@^system dependencies@> + +@d terminal_input==(name=is_term) {are we reading from the terminal?} +@d cur_file==input_file[index] {the current |alpha_file| variable} +@d line==line_stack[index] {current line number in the current source file} +@d in_name==iname_stack[index] {a string used to construct \.{MPX} file names} +@d in_area==iarea_stack[index] {another string for naming \.{MPX} files} +@d absent=1 {|name_field| value for unused |mpx_in_stack| entries} +@d mpx_reading==(mpx_name[index]>absent) + {when reading a file, is it an \.{MPX} file?} +@d finished=0 + {|name_field| value when the corresponding \.{MPX} file is finished} + +@<Glob...@>= +@!in_open : 0..max_in_open; {the number of lines in the buffer, less one} +@!open_parens : 0..max_in_open; {the number of open text files} +@!input_file : array[1..max_in_open] of alpha_file; +@!line_stack : array[0..max_in_open] of integer; {the line number for each file} +@!iname_stack : array[0..max_in_open] of str_number; + {used for naming \.{MPX} files} +@!iarea_stack : array[0..max_in_open] of str_number; + {used for naming \.{MPX} files} +@!mpx_name : array[0..max_in_open] of halfword; + +@ However, all this discussion about input state really applies only to the +case that we are inputting from a file. There is another important case, +namely when we are currently getting input from a token list. In this case +|index>max_in_open|, and the conventions about the other state variables +are different: + +\yskip\hang|loc| is a pointer to the current node in the token list, i.e., +the node that will be read next. If |loc=null|, the token list has been +fully read. + +\yskip\hang|start| points to the first node of the token list; this node +may or may not contain a reference count, depending on the type of token +list involved. + +\yskip\hang|token_type|, which takes the place of |index| in the +discussion above, is a code number that explains what kind of token list +is being scanned. + +\yskip\hang|name| points to the |eqtb| address of the control sequence +being expanded, if the current token list is a macro not defined by +\&{vardef}. Macros defined by \&{vardef} have |name=null|; their name +can be deduced by looking at their first two parameters. + +\yskip\hang|param_start|, which takes the place of |limit|, tells where +the parameters of the current macro or loop text begin in the |param_stack|. + +\yskip\noindent The |token_type| can take several values, depending on +where the current token list came from: + +\yskip +\indent|forever_text|, if the token list being scanned is the body of +a \&{forever} loop; + +\indent|loop_text|, if the token list being scanned is the body of +a \&{for} or \&{forsuffixes} loop; + +\indent|parameter|, if a \&{text} or \&{suffix} parameter is being scanned; + +\indent|backed_up|, if the token list being scanned has been inserted as +`to be read again'. + +\indent|inserted|, if the token list being scanned has been inserted as +part of error recovery; + +\indent|macro|, if the expansion of a user-defined symbolic token is being +scanned. + +\yskip\noindent +The token list begins with a reference count if and only if |token_type= +macro|. +@^reference counts@> + +@d token_type==index {type of current token list} +@d token_state==(index>max_in_open) {are we scanning a token list?} +@d file_state==(index<=max_in_open) {are we scanning a file line?} +@d param_start==limit {base of macro parameters in |param_stack|} +@d forever_text=max_in_open+1 {|token_type| code for loop texts} +@d loop_text=max_in_open+2 {|token_type| code for loop texts} +@d parameter=max_in_open+3 {|token_type| code for parameter texts} +@d backed_up=max_in_open+4 {|token_type| code for texts to be reread} +@d inserted=max_in_open+5 {|token_type| code for inserted texts} +@d macro=max_in_open+6 {|token_type| code for macro replacement texts} + +@ The |param_stack| is an auxiliary array used to hold pointers to the token +lists for parameters at the current level and subsidiary levels of input. +This stack grows at a different rate from the others. + +@<Glob...@>= +@!param_stack:array [0..param_size] of pointer; + {token list pointers for parameters} +@!param_ptr:0..param_size; {first unused entry in |param_stack|} +@!max_param_stack:integer; + {largest value of |param_ptr|} + +@ Notice that the |line| isn't valid when |token_state| is true because it +depends on |index|. If we really need to know the line number for the +topmost file in the index stack we use the following function. If a page +number or other information is needed, this routine should be modified to +compute it as well. +@^system dependencies@> + +@<Declare a function called |true_line|@>= +function true_line: integer; +var @!k:0..stack_size; {an index into the input stack} +begin if file_state and (name>max_spec_src) then true_line:=line +else begin k:=input_ptr; + while (k>0)and(input_stack[k].index_field>max_in_open)or@| + (input_stack[k].name_field<=max_spec_src) do + decr(k); + true_line:=line_stack[k]; + end; +end; + +@ Thus, the ``current input state'' can be very complicated indeed; there +can be many levels and each level can arise in a variety of ways. The +|show_context| procedure, which is used by \MP's error-reporting routine to +print out the current input state on all levels down to the most recent +line of characters from an input file, illustrates most of these conventions. +The global variable |file_ptr| contains the lowest level that was +displayed by this procedure. + +@<Glob...@>= +@!file_ptr:0..stack_size; {shallowest level shown by |show_context|} + +@ The status at each level is indicated by printing two lines, where the first +line indicates what was read so far and the second line shows what remains +to be read. The context is cropped, if necessary, so that the first line +contains at most |half_error_line| characters, and the second contains +at most |error_line|. Non-current input levels whose |token_type| is +`|backed_up|' are shown only if they have not been fully read. + +@p procedure show_context; {prints where the scanner is} +label done; +var @!old_setting:0..max_selector; {saved |selector| setting} +@<Local variables for formatting calculations@>@/ +begin file_ptr:=input_ptr; input_stack[file_ptr]:=cur_input; + {store current state} +loop@+begin cur_input:=input_stack[file_ptr]; {enter into the context} + @<Display the current context@>; + if file_state then + if (name>max_spec_src) or (file_ptr=0) then goto done; + decr(file_ptr); + end; +done: cur_input:=input_stack[input_ptr]; {restore original state} +end; + +@ @<Display the current context@>= +if (file_ptr=input_ptr) or file_state or + (token_type<>backed_up) or (loc<>null) then + {we omit backed-up token lists that have already been read} + begin tally:=0; {get ready to count characters} + old_setting:=selector; + if file_state then + begin @<Print location of current line@>; + @<Pseudoprint the line@>; + end + else begin @<Print type of token list@>; + @<Pseudoprint the token list@>; + end; + selector:=old_setting; {stop pseudoprinting} + @<Print two lines using the tricky pseudoprinted information@>; + end + +@ This routine should be changed, if necessary, to give the best possible +indication of where the current line resides in the input file. +For example, on some systems it is best to print both a page and line number. +@^system dependencies@> + +@<Print location of current line@>= +if name>max_spec_src then + begin print_nl("l."); print_int(true_line); + end +else if terminal_input then + if file_ptr=0 then print_nl("<*>") @+else print_nl("<insert>") +else if name=is_scantok then print_nl("<scantokens>") +else print_nl("<read>"); +print_char(" ") + +@ @<Print type of token list@>= +case token_type of +forever_text: print_nl("<forever> "); +loop_text: @<Print the current loop value@>; +parameter: print_nl("<argument> "); +backed_up: if loc=null then print_nl("<recently read> ") + else print_nl("<to be read again> "); +inserted: print_nl("<inserted text> "); +macro: begin print_ln; + if name<>null then print(text(name)) + else @<Print the name of a \&{vardef}'d macro@>; + print("->"); + end; +othercases print_nl("?") {this should never happen} +@.?\relax@> +endcases + +@ The parameter that corresponds to a loop text is either a token list +(in the case of \&{forsuffixes}) or a ``capsule'' (in the case of \&{for}). +We'll discuss capsules later; for now, all we need to know is that +the |link| field in a capsule parameter is |void| and that +|print_exp(p,0)| displays the value of capsule~|p| in abbreviated form. + +@d void==null+1 {a null pointer different from |null|} + +@<Print the current loop value@>= +begin print_nl("<for("); p:=param_stack[param_start]; +if p<>null then + if link(p)=void then print_exp(p,0) {we're in a \&{for} loop} + else show_token_list(p,null,20,tally); +print(")> "); +end + +@ The first two parameters of a macro defined by \&{vardef} will be token +lists representing the macro's prefix and ``at point.'' By putting these +together, we get the macro's full name. + +@<Print the name of a \&{vardef}'d macro@>= +begin p:=param_stack[param_start]; +if p=null then show_token_list(param_stack[param_start+1],null,20,tally) +else begin q:=p; + while link(q)<>null do q:=link(q); + link(q):=param_stack[param_start+1]; + show_token_list(p,null,20,tally); + link(q):=null; + end; +end + +@ Now it is necessary to explain a little trick. We don't want to store a long +string that corresponds to a token list, because that string might take up +lots of memory; and we are printing during a time when an error message is +being given, so we dare not do anything that might overflow one of \MP's +tables. So `pseudoprinting' is the answer: We enter a mode of printing +that stores characters into a buffer of length |error_line|, where character +$k+1$ is placed into \hbox{|trick_buf[k mod error_line]|} if +|k<trick_count|, otherwise character |k| is dropped. Initially we set +|tally:=0| and |trick_count:=1000000|; then when we reach the +point where transition from line 1 to line 2 should occur, we +set |first_count:=tally| and |trick_count:=@tmax@>(error_line, +tally+1+error_line-half_error_line)|. At the end of the +pseudoprinting, the values of |first_count|, |tally|, and +|trick_count| give us all the information we need to print the two lines, +and all of the necessary text is in |trick_buf|. + +Namely, let |l| be the length of the descriptive information that appears +on the first line. The length of the context information gathered for that +line is |k=first_count|, and the length of the context information +gathered for line~2 is $m=\min(|tally|, |trick_count|)-k$. If |l+k<=h|, +where |h=half_error_line|, we print |trick_buf[0..k-1]| after the +descriptive information on line~1, and set |n:=l+k|; here |n| is the +length of line~1. If $l+k>h$, some cropping is necessary, so we set |n:=h| +and print `\.{...}' followed by +$$\hbox{|trick_buf[(l+k-h+3)..k-1]|,}$$ +where subscripts of |trick_buf| are circular modulo |error_line|. The +second line consists of |n|~spaces followed by |trick_buf[k..(k+m-1)]|, +unless |n+m>error_line|; in the latter case, further cropping is done. +This is easier to program than to explain. + +@<Local variables for formatting...@>= +@!i:0..buf_size; {index into |buffer|} +@!l:integer; {length of descriptive information on line 1} +@!m:integer; {context information gathered for line 2} +@!n:0..error_line; {length of line 1} +@!p: integer; {starting or ending place in |trick_buf|} +@!q: integer; {temporary index} + +@ The following code tells the print routines to gather +the desired information. + +@d begin_pseudoprint== + begin l:=tally; tally:=0; selector:=pseudo; + trick_count:=1000000; + end +@d set_trick_count== + begin first_count:=tally; + trick_count:=tally+1+error_line-half_error_line; + if trick_count<error_line then trick_count:=error_line; + end + +@ And the following code uses the information after it has been gathered. + +@<Print two lines using the tricky pseudoprinted information@>= +if trick_count=1000000 then set_trick_count; + {|set_trick_count| must be performed} +if tally<trick_count then m:=tally-first_count +else m:=trick_count-first_count; {context on line 2} +if l+first_count<=half_error_line then + begin p:=0; n:=l+first_count; + end +else begin print("..."); p:=l+first_count-half_error_line+3; + n:=half_error_line; + end; +for q:=p to first_count-1 do print_char(trick_buf[q mod error_line]); +print_ln; +for q:=1 to n do print_char(" "); {print |n| spaces to begin line~2} +if m+n<=error_line then p:=first_count+m else p:=first_count+(error_line-n-3); +for q:=first_count to p-1 do print_char(trick_buf[q mod error_line]); +if m+n>error_line then print("...") + +@ But the trick is distracting us from our current goal, which is to +understand the input state. So let's concentrate on the data structures that +are being pseudoprinted as we finish up the |show_context| procedure. + +@<Pseudoprint the line@>= +begin_pseudoprint; +if limit>0 then for i:=start to limit-1 do + begin if i=loc then set_trick_count; + print(buffer[i]); + end + +@ @<Pseudoprint the token list@>= +begin_pseudoprint; +if token_type<>macro then show_token_list(start,loc,100000,0) +else show_macro(start,loc,100000) + +@ Here is the missing piece of |show_token_list| that is activated when the +token beginning line~2 is about to be shown: + +@<Do magic computation@>=set_trick_count + +@* \[28] Maintaining the input stacks. +The following subroutines change the input status in commonly needed ways. + +First comes |push_input|, which stores the current state and creates a +new level (having, initially, the same properties as the old). + +@d push_input==@t@> {enter a new input level, save the old} + begin if input_ptr>max_in_stack then + begin max_in_stack:=input_ptr; + if input_ptr=stack_size then overflow("input stack size",stack_size); +@:MetaPost capacity exceeded input stack size}{\quad input stack size@> + end; + input_stack[input_ptr]:=cur_input; {stack the record} + incr(input_ptr); + end + +@ And of course what goes up must come down. + +@d pop_input==@t@> {leave an input level, re-enter the old} + begin decr(input_ptr); cur_input:=input_stack[input_ptr]; + end + +@ Here is a procedure that starts a new level of token-list input, given +a token list |p| and its type |t|. If |t=macro|, the calling routine should +set |name|, reset~|loc|, and increase the macro's reference count. + +@d back_list(#)==begin_token_list(#,backed_up) {backs up a simple token list} + +@p procedure begin_token_list(@!p:pointer;@!t:quarterword); +begin push_input; start:=p; token_type:=t; +param_start:=param_ptr; loc:=p; +end; + +@ When a token list has been fully scanned, the following computations +should be done as we leave that level of input. +@^inner loop@> + +@p procedure end_token_list; {leave a token-list input level} +label done; +var @!p:pointer; {temporary register} +begin if token_type>=backed_up then {token list to be deleted} + if token_type<=inserted then + begin flush_token_list(start); goto done; + end + else delete_mac_ref(start); {update reference count} +while param_ptr>param_start do {parameters must be flushed} + begin decr(param_ptr); + p:=param_stack[param_ptr]; + if p<>null then + if link(p)=void then {it's an \&{expr} parameter} + begin recycle_value(p); free_node(p,value_node_size); + end + else flush_token_list(p); {it's a \&{suffix} or \&{text} parameter} + end; +done: pop_input; check_interrupt; +end; + +@ The contents of |cur_cmd,cur_mod,cur_sym| are placed into an equivalent +token by the |cur_tok| routine. +@^inner loop@> + +@p @t\4@>@<Declare the procedure called |make_exp_copy|@>@;@/ +function cur_tok:pointer; +var @!p:pointer; {a new token node} +@!save_type:small_number; {|cur_type| to be restored} +@!save_exp:integer; {|cur_exp| to be restored} +begin if cur_sym=0 then + if cur_cmd=capsule_token then + begin save_type:=cur_type; save_exp:=cur_exp; + make_exp_copy(cur_mod); p:=stash_cur_exp; link(p):=null; + cur_type:=save_type; cur_exp:=save_exp; + end + else begin p:=get_node(token_node_size); + value(p):=cur_mod; name_type(p):=token; + if cur_cmd=numeric_token then type(p):=known + else type(p):=string_type; + end +else begin fast_get_avail(p); info(p):=cur_sym; + end; +cur_tok:=p; +end; + +@ Sometimes \MP\ has read too far and wants to ``unscan'' what it has +seen. The |back_input| procedure takes care of this by putting the token +just scanned back into the input stream, ready to be read again. +If |cur_sym<>0|, the values of |cur_cmd| and |cur_mod| are irrelevant. + +@p procedure back_input; {undoes one token of input} +var @!p:pointer; {a token list of length one} +begin p:=cur_tok; +while token_state and(loc=null) do end_token_list; {conserve stack space} +back_list(p); +end; + +@ The |back_error| routine is used when we want to restore or replace an +offending token just before issuing an error message. We disable interrupts +during the call of |back_input| so that the help message won't be lost. + +@p procedure back_error; {back up one token and call |error|} +begin OK_to_interrupt:=false; back_input; OK_to_interrupt:=true; error; +end; +@# +procedure ins_error; {back up one inserted token and call |error|} +begin OK_to_interrupt:=false; back_input; token_type:=inserted; +OK_to_interrupt:=true; error; +end; + +@ The |begin_file_reading| procedure starts a new level of input for lines +of characters to be read from a file, or as an insertion from the +terminal. It does not take care of opening the file, nor does it set |loc| +or |limit| or |line|. +@^system dependencies@> + +@p procedure begin_file_reading; +begin if in_open=max_in_open then overflow("text input levels",max_in_open); +@:MetaPost capacity exceeded text input levels}{\quad text input levels@> +if first=buf_size then overflow("buffer size",buf_size); +@:MetaPost capacity exceeded buffer size}{\quad buffer size@> +incr(in_open); push_input; index:=in_open; +mpx_name[index]:=absent; +start:=first; +name:=is_term; {|terminal_input| is now |true|} +end; + +@ Conversely, the variables must be downdated when such a level of input +is finished. Any associated \.{MPX} file must also be closed and popped +off the file stack. + +@p procedure end_file_reading; +begin if in_open>index then + if (mpx_name[in_open]=absent)or(name<=max_spec_src) then confusion("endinput") +@:this can't happen endinput}{\quad endinput@> + else begin a_close(input_file[in_open]); {close an \.{MPX} file} + delete_str_ref(mpx_name[in_open]); + decr(in_open); + end; +first:=start; +if index<>in_open then confusion("endinput"); +if name>max_spec_src then + begin a_close(cur_file); + delete_str_ref(name); + delete_str_ref(in_name); delete_str_ref(in_area); + end; +pop_input; decr(in_open); +end; + +@ Here is a function that tries to resume input from an \.{MPX} file already +associated with the current input file. It returns |false| if this doesn't +work. + +@p function begin_mpx_reading:boolean; +begin if in_open<>index+1 then begin_mpx_reading:=false +else begin if mpx_name[in_open]<=absent then confusion("mpx"); +@:this can't happen mpx}{\quad mpx@> + if first=buf_size then overflow("buffer size",buf_size); +@:MetaPost capacity exceeded buffer size}{\quad buffer size@> + push_input; index:=in_open; + start:=first; + name:=mpx_name[in_open]; add_str_ref(name); + @<Put an empty line in the input buffer@>; + begin_mpx_reading:=true; + end; +end; + +@ This procedure temporarily stops reading an \.{MPX} file. + +@p procedure end_mpx_reading; +begin if in_open<>index then confusion("mpx"); +@:this can't happen mpx}{\quad mpx@> +if loc<limit then + @<Complain that we are not at the end of a line in the \.{MPX} file@>; +first:=start; +pop_input; +end; + +@ Here we enforce a restriction that simplifies the input stacks considerably. +This should not inconvenience the user because \.{MPX} files are generated +by an auxiliary program called \.{DVItoMP}. + +@ @<Complain that we are not at the end of a line in the \.{MPX} file@>= +begin print_err("`mpxbreak' must be at the end of a line"); +help4("This file contains picture expressions for btex...etex")@/ + ("blocks. Such files are normally generated automatically")@/ + ("but this one seems to be messed up. I'm going to ignore")@/ + ("the rest of this line.");@/ +error; +end + +@ In order to keep the stack from overflowing during a long sequence of +inserted `\.{show}' commands, the following routine removes completed +error-inserted lines from memory. + +@p procedure clear_for_error_prompt; +begin while file_state and terminal_input and@| + (input_ptr>0)and(loc=limit) do end_file_reading; +print_ln; clear_terminal; +end; + +@ To get \MP's whole input mechanism going, we perform the following +actions. + +@<Initialize the input routines@>= +begin input_ptr:=0; max_in_stack:=0; +in_open:=0; open_parens:=0; max_buf_stack:=0; +param_ptr:=0; max_param_stack:=0; +first:=1; +start:=1; index:=0; line:=0; name:=is_term; +mpx_name[0]:=absent; +force_eof:=false; +if not init_terminal then goto final_end; +limit:=last; first:=last+1; {|init_terminal| has set |loc| and |last|} +end; + +@* \[29] Getting the next token. +The heart of \MP's input mechanism is the |get_next| procedure, which +we shall develop in the next few sections of the program. Perhaps we +shouldn't actually call it the ``heart,'' however; it really acts as \MP's +eyes and mouth, reading the source files and gobbling them up. And it also +helps \MP\ to regurgitate stored token lists that are to be processed again. + +The main duty of |get_next| is to input one token and to set |cur_cmd| +and |cur_mod| to that token's command code and modifier. Furthermore, if +the input token is a symbolic token, that token's |hash| address +is stored in |cur_sym|; otherwise |cur_sym| is set to zero. + +Underlying this simple description is a certain amount of complexity +because of all the cases that need to be handled. +However, the inner loop of |get_next| is reasonably short and fast. + +@ Before getting into |get_next|, we need to consider a mechanism by which +\MP\ helps keep errors from propagating too far. Whenever the program goes +into a mode where it keeps calling |get_next| repeatedly until a certain +condition is met, it sets |scanner_status| to some value other than |normal|. +Then if an input file ends, or if an `\&{outer}' symbol appears, +an appropriate error recovery will be possible. + +The global variable |warning_info| helps in this error recovery by providing +additional information. For example, |warning_info| might indicate the +name of a macro whose replacement text is being scanned. + +@d normal=0 {|scanner_status| at ``quiet times''} +@d skipping=1 {|scanner_status| when false conditional text is being skipped} +@d flushing=2 {|scanner_status| when junk after a statement is being ignored} +@d absorbing=3 {|scanner_status| when a \&{text} parameter is being scanned} +@d var_defining=4 {|scanner_status| when a \&{vardef} is being scanned} +@d op_defining=5 {|scanner_status| when a macro \&{def} is being scanned} +@d loop_defining=6 {|scanner_status| when a \&{for} loop is being scanned} +@d tex_flushing=7 {|scanner_status| when skipping \TeX\ material} + +@<Glob...@>= +@!scanner_status:normal..tex_flushing; {are we scanning at high speed?} +@!warning_info:integer; {if so, what else do we need to know, + in case an error occurs?} + +@ @<Initialize the input routines@>= +scanner_status:=normal; + +@ The following subroutine +is called when an `\&{outer}' symbolic token has been scanned or +when the end of a file has been reached. These two cases are distinguished +by |cur_sym|, which is zero at the end of a file. + +@p function check_outer_validity:boolean; +var @!p:pointer; {points to inserted token list} +begin if scanner_status=normal then check_outer_validity:=true +else if scanner_status=tex_flushing then + @<Check if the file has ended while flushing \TeX\ material and set the + result value for |check_outer_validity|@> +else begin deletions_allowed:=false; + @<Back up an outer symbolic token so that it can be reread@>; + if scanner_status>skipping then + @<Tell the user what has run away and try to recover@> + else begin print_err("Incomplete if; all text was ignored after line "); +@.Incomplete if...@> + print_int(warning_info);@/ + help3("A forbidden `outer' token occurred in skipped text.")@/ + ("This kind of error happens when you say `if...' and forget")@/ + ("the matching `fi'. I've inserted a `fi'; this might work."); + if cur_sym=0 then help_line[2]:=@| + "The file ended while I was skipping conditional text."; + cur_sym:=frozen_fi; ins_error; + end; + deletions_allowed:=true; check_outer_validity:=false; + end; +end; + +@ @<Check if the file has ended while flushing \TeX\ material and set...@>= +if cur_sym<>0 then check_outer_validity:=true +else begin deletions_allowed:=false; + print_err("TeX mode didn't end; all text was ignored after line "); + print_int(warning_info); + help2("The file ended while I was looking for the `etex' to")@/ + ("finish this TeX material. I've inserted `etex' now.");@/ + cur_sym := frozen_etex; + ins_error;@/ + deletions_allowed:=true; check_outer_validity:=false; + end + +@ @<Back up an outer symbolic token so that it can be reread@>= +if cur_sym<>0 then + begin p:=get_avail; info(p):=cur_sym; + back_list(p); {prepare to read the symbolic token again} + end + +@ @<Tell the user what has run away...@>= +begin runaway; {print the definition-so-far} +if cur_sym=0 then print_err("File ended") +@.File ended while scanning...@> +else begin print_err("Forbidden token found"); +@.Forbidden token found...@> + end; +print(" while scanning "); +help4("I suspect you have forgotten an `enddef',")@/ +("causing me to read past where you wanted me to stop.")@/ +("I'll try to recover; but if the error is serious,")@/ +("you'd better type `E' or `X' now and fix your file.");@/ +case scanner_status of +@t\4@>@<Complete the error message, + and set |cur_sym| to a token that might help recover from the error@>@; +end; {there are no other cases} +ins_error; +end + +@ As we consider various kinds of errors, it is also appropriate to +change the first line of the help message just given; |help_line[3]| +points to the string that might be changed. + +@<Complete the error message,...@>= +flushing: begin print("to the end of the statement"); + help_line[3]:="A previous error seems to have propagated,"; + cur_sym:=frozen_semicolon; + end; +absorbing: begin print("a text argument"); + help_line[3]:="It seems that a right delimiter was left out,"; + if warning_info=0 then cur_sym:=frozen_end_group + else begin cur_sym:=frozen_right_delimiter; + equiv(frozen_right_delimiter):=warning_info; + end; + end; +var_defining, op_defining: begin print("the definition of "); + if scanner_status=op_defining then print(text(warning_info)) + else print_variable_name(warning_info); + cur_sym:=frozen_end_def; + end; +loop_defining: begin print("the text of a "); print(text(warning_info)); + print(" loop"); + help_line[3]:="I suspect you have forgotten an `endfor',"; + cur_sym:=frozen_end_for; + end; + +@ The |runaway| procedure displays the first part of the text that occurred +when \MP\ began its special |scanner_status|, if that text has been saved. + +@<Declare the procedure called |runaway|@>= +procedure runaway; +begin if scanner_status>flushing then + begin print_nl("Runaway "); + case scanner_status of + absorbing: print("text?"); + var_defining,op_defining: print("definition?"); + loop_defining: print("loop?"); + end; {there are no other cases} + print_ln; show_token_list(link(hold_head),null,error_line-10,0); + end; +end; + +@ We need to mention a procedure that may be called by |get_next|. + +@p procedure@?firm_up_the_line; forward; + +@ And now we're ready to take the plunge into |get_next| itself. +Note that the behavior depends on the |scanner_status| because percent signs +and double quotes need to be passed over when skipping TeX material. + +@d switch=25 {a label in |get_next|} +@d start_numeric_token=85 {another} +@d start_decimal_token=86 {and another} +@d fin_numeric_token=87 + {and still another, although |goto| is considered harmful} + +@p procedure get_next; {sets |cur_cmd|, |cur_mod|, |cur_sym| to next token} +@^inner loop@> +label restart, {go here to get the next input token} + exit, {go here when the next input token has been got} + common_ending, {go here to finish getting a symbolic token} + found, {go here when the end of a symbolic token has been found} + switch, {go here to branch on the class of an input character} + start_numeric_token,start_decimal_token,fin_numeric_token,done; + {go here at crucial stages when scanning a number} +var @!k:0..buf_size; {an index into |buffer|} +@!c:ASCII_code; {the current character in the buffer} +@!class:ASCII_code; {its class number} +@!n,@!f:integer; {registers for decimal-to-binary conversion} +begin restart: cur_sym:=0; +if file_state then +@<Input from external file; |goto restart| if no input found, + or |return| if a non-symbolic token is found@> +else @<Input from token list; |goto restart| if end of list or + if a parameter needs to be expanded, + or |return| if a non-symbolic token is found@>; +common_ending: @<Finish getting the symbolic token in |cur_sym|; + |goto restart| if it is illegal@>; +exit:end; + +@ When a symbolic token is declared to be `\&{outer}', its command code +is increased by |outer_tag|. +@^inner loop@> + +@<Finish getting the symbolic token in |cur_sym|...@>= +cur_cmd:=eq_type(cur_sym); cur_mod:=equiv(cur_sym); +if cur_cmd>=outer_tag then + if check_outer_validity then cur_cmd:=cur_cmd-outer_tag + else goto restart + +@ A percent sign appears in |buffer[limit]|; this makes it unnecessary +to have a special test for end-of-line. +@^inner loop@> + +@<Input from external file;...@>= +begin switch: c:=buffer[loc]; incr(loc); class:=char_class[c]; +case class of +digit_class: goto start_numeric_token; +period_class: begin class:=char_class[buffer[loc]]; + if class>period_class then goto switch + else if class<period_class then {|class=digit_class|} + begin n:=0; goto start_decimal_token; + end; +@:. }{\..\ token@> + end; +space_class: goto switch; +percent_class: begin if scanner_status=tex_flushing then + if loc<limit then goto switch; + @<Move to next line of file, or |goto restart| if there is no next line@>; + check_interrupt; + goto switch; + end; +string_class: if scanner_status=tex_flushing then goto switch + else @<Get a string token and |return|@>; +isolated_classes: begin k:=loc-1; goto found; + end; +invalid_class: if scanner_status=tex_flushing then goto switch + else @<Decry the invalid character and |goto restart|@>; +othercases do_nothing {letters, etc.} +endcases;@/ +k:=loc-1; +while char_class[buffer[loc]]=class do incr(loc); +goto found; +start_numeric_token:@<Get the integer part |n| of a numeric token; + set |f:=0| and |goto fin_numeric_token| if there is no decimal point@>; +start_decimal_token:@<Get the fraction part |f| of a numeric token@>; +fin_numeric_token:@<Pack the numeric and fraction parts of a numeric token + and |return|@>; +found: cur_sym:=id_lookup(k,loc-k); +end + +@ We go to |restart| instead of to |switch|, because |state| might equal +|token_list| after the error has been dealt with +(cf.\ |clear_for_error_prompt|). + +@<Decry the invalid...@>= +begin print_err("Text line contains an invalid character"); +@.Text line contains...@> +help2("A funny symbol that I can't read has just been input.")@/ +("Continue, and I'll forget that it ever happened.");@/ +deletions_allowed:=false; error; deletions_allowed:=true; +goto restart; +end + +@ @<Get a string token and |return|@>= +begin if buffer[loc]="""" then cur_mod:="" +else begin k:=loc; buffer[limit+1]:=""""; + repeat incr(loc); + until buffer[loc]=""""; + if loc>limit then @<Decry the missing string delimiter and |goto restart|@>; + if loc=k+1 then cur_mod:=buffer[k] + else begin str_room(loc-k); + repeat append_char(buffer[k]); incr(k); + until k=loc; + cur_mod:=make_string; + end; + end; +incr(loc); cur_cmd:=string_token; return; +end + +@ We go to |restart| after this error message, not to |switch|, +because the |clear_for_error_prompt| routine might have reinstated +|token_state| after |error| has finished. + +@<Decry the missing string delimiter and |goto restart|@>= +begin loc:=limit; {the next character to be read on this line will be |"%"|} +print_err("Incomplete string token has been flushed"); +@.Incomplete string token...@> +help3("Strings should finish on the same line as they began.")@/ + ("I've deleted the partial string; you might want to")@/ + ("insert another by typing, e.g., `I""new string""'.");@/ +deletions_allowed:=false; error; deletions_allowed:=true; goto restart; +end + +@ @<Get the integer part |n| of a numeric token...@>= +n:=c-"0"; +while char_class[buffer[loc]]=digit_class do + begin if n<32768 then n:=10*n+buffer[loc]-"0"; + incr(loc); + end; +if buffer[loc]="." then if char_class[buffer[loc+1]]=digit_class then goto done; +f:=0; goto fin_numeric_token; +done: incr(loc) + +@ @<Get the fraction part |f| of a numeric token@>= +k:=0; +repeat if k<17 then {digits for |k>=17| cannot affect the result} + begin dig[k]:=buffer[loc]-"0"; incr(k); + end; +incr(loc); +until char_class[buffer[loc]]<>digit_class; +f:=round_decimals(k); +if f=unity then + begin incr(n); f:=0; + end + +@ @<Pack the numeric and fraction parts of a numeric token and |return|@>= +if n<32768 then @<Set |cur_mod:=n*unity+f| and check if it is uncomfortably + large@> +else if scanner_status<>tex_flushing then + begin print_err("Enormous number has been reduced"); +@.Enormous number...@> + help2("I can't handle numbers bigger than 32767.99998;")@/ + ("so I've changed your constant to that maximum amount.");@/ + deletions_allowed:=false; error; deletions_allowed:=true; + cur_mod:=el_gordo; + end; +cur_cmd:=numeric_token; return + +@ @<Set |cur_mod:=n*unity+f| and check if it is uncomfortably large@>= +begin cur_mod:=n*unity+f; +if cur_mod>=fraction_one then + if (internal[warning_check]>0) and (scanner_status<>tex_flushing) then + begin print_err("Number is too large ("); + print_scaled(cur_mod); + print_char(")"); + help3("It is at least 4096. Continue and I'll try to cope")@/ + ("with that big value; but it might be dangerous.")@/ + ("(Set warningcheck:=0 to suppress this message.)"); + error; + end; +end + +@ Let's consider now what happens when |get_next| is looking at a token list. +@^inner loop@> + +@<Input from token list;...@>= +if loc>=hi_mem_min then {one-word token} + begin cur_sym:=info(loc); loc:=link(loc); {move to next} + if cur_sym>=expr_base then + if cur_sym>=suffix_base then + @<Insert a suffix or text parameter and |goto restart|@> + else begin cur_cmd:=capsule_token; + cur_mod:=param_stack[param_start+cur_sym-(expr_base)]; + cur_sym:=0; return; + end; + end +else if loc>null then + @<Get a stored numeric or string or capsule token and |return|@> +else begin {we are done with this token list} + end_token_list; goto restart; {resume previous level} + end + +@ @<Insert a suffix or text parameter...@>= +begin if cur_sym>=text_base then cur_sym:=cur_sym-param_size; + {|param_size=text_base-suffix_base|} +begin_token_list(param_stack[param_start+cur_sym-(suffix_base)],parameter); +goto restart; +end + +@ @<Get a stored numeric or string or capsule token...@>= +begin if name_type(loc)=token then + begin cur_mod:=value(loc); + if type(loc)=known then cur_cmd:=numeric_token + else begin cur_cmd:=string_token; add_str_ref(cur_mod); + end; + end +else begin cur_mod:=loc; cur_cmd:=capsule_token; + end; +loc:=link(loc); return; +end + +@ All of the easy branches of |get_next| have now been taken care of. +There is one more branch. + +@<Move to next line of file, or |goto restart|...@>= +if name>max_spec_src then @<Read next line of file into |buffer|, or + |goto restart| if the file has ended@> +else begin if input_ptr>0 then + {text was inserted during error recovery or by \&{scantokens}} + begin end_file_reading; goto restart; {resume previous level} + end; + if selector<log_only then open_log_file; + if interaction>nonstop_mode then + begin if limit=start then {previous line was empty} + print_nl("(Please type a command or say `end')"); +@.Please type...@> + print_ln; first:=start; + prompt_input("*"); {input on-line into |buffer|} +@.*\relax@> + limit:=last; buffer[limit]:="%"; + first:=limit+1; loc:=start; + end + else fatal_error("*** (job aborted, no legal end found)"); +@.job aborted@> + {nonstop mode, which is intended for overnight batch processing, + never waits for on-line input} + end + +@ The global variable |force_eof| is normally |false|; it is set |true| +by an \&{endinput} command. + +@<Glob...@>= +@!force_eof:boolean; {should the next \&{input} be aborted early?} + +@ We must decrement |loc| in order to leave the buffer in a valid state +when an error condition causes us to |goto restart| without calling +|end_file_reading|. + +@<Read next line of file into |buffer|, or + |goto restart| if the file has ended@>= +begin incr(line); first:=start; +if not force_eof then + begin if input_ln(cur_file,true) then {not end of file} + firm_up_the_line {this sets |limit|} + else force_eof:=true; + end; +if force_eof then + begin force_eof:=false; + decr(loc); + if mpx_reading then + @<Complain that the \.{MPX} file ended unexpectly; then set + |cur_sym:=frozen_mpx_break| and |goto comon_ending|@> + else begin print_char(")"); decr(open_parens); + update_terminal; {show user that file has been read} + end_file_reading; {resume previous level} + if check_outer_validity then goto restart @+else goto restart; + end + end; +buffer[limit]:="%"; first:=limit+1; loc:=start; {ready to read} +end + +@ We should never actually come to the end of an \.{MPX} file because such +files should have an \&{mpxbreak} after the translation of the last +\&{btex}$\,\ldots\,$\&{etex} block. + +@<Complain that the \.{MPX} file ended unexpectly; then set...@>= +begin mpx_name[index]:=finished; +print_err("mpx file ended unexpectedly"); +help4("The file had too few picture expressions for btex...etex")@/ + ("blocks. Such files are normally generated automatically")@/ + ("but this one got messed up. You might want to insert a")@/ + ("picture expression now.");@/ +deletions_allowed:=false; error; deletions_allowed:=true; +cur_sym:=frozen_mpx_break; goto common_ending; +end + +@ Sometimes we want to make it look as though we have just read a blank line +without really doing so. + +@<Put an empty line in the input buffer@>= +last:=first; limit:=last; {simulate |input_ln| and |firm_up_the_line|} +buffer[limit]:="%"; first:=limit+1; loc:=start + +@ If the user has set the |pausing| parameter to some positive value, +and if nonstop mode has not been selected, each line of input is displayed +on the terminal and the transcript file, followed by `\.{=>}'. +\MP\ waits for a response. If the response is null (i.e., if nothing is +typed except perhaps a few blank spaces), the original +line is accepted as it stands; otherwise the line typed is +used instead of the line in the file. + +@p procedure firm_up_the_line; +var @!k:0..buf_size; {an index into |buffer|} +begin limit:=last; +if internal[pausing]>0 then if interaction>nonstop_mode then + begin wake_up_terminal; print_ln; + if start<limit then for k:=start to limit-1 do print(buffer[k]); + first:=limit; prompt_input("=>"); {wait for user response} +@.=>@> + if last>first then + begin for k:=first to last-1 do {move line down in buffer} + buffer[k+start-first]:=buffer[k]; + limit:=start+last-first; + end; + end; +end; + +@* \[30] Dealing with \TeX\ material. +The \&{btex}$\,\ldots\,$\&{etex} and \&{verbatimtex}$\,\ldots\,$\&{etex} +features need to be implemented at a low level in the scanning process +so that \MP\ can stay in synch with the a preprocessor that treats +blocks of \TeX\ material as they occur in the input file without trying +to expand \MP\ macros. Thus we need a special version of |get_next| +that does not expand macros and such but does handle \&{btex}, +\&{verbatimtex}, etc. + +The special version of |get_next| is called |get_t_next|. It works by flushing +\&{btex}$\,\ldots\,$\&{etex} and \&{verbatimtex}\allowbreak +$\,\ldots\,$\&{etex} blocks, switching to the \.{MPX} file when it sees +\&{btex}, and switching back when it sees \&{mpxbreak}. + +@d btex_code=0 +@d verbatim_code=1 + +@ @<Put each...@>= +primitive("btex",start_tex,btex_code);@/ +@!@:btex_}{\&{btex} primitive@> +primitive("verbatimtex",start_tex,verbatim_code); +@!@:verbatimtex_}{\&{verbatimtex} primitive@> +primitive("etex",etex_marker,0); eqtb[frozen_etex]:=eqtb[cur_sym];@/ +@!@:etex_}{\&{etex} primitive@> +primitive("mpxbreak",mpx_break,0); eqtb[frozen_mpx_break]:=eqtb[cur_sym];@/ +@!@:mpx_break_}{\&{mpxbreak} primitive@> + +@ @<Cases of |print_cmd...@>= +start_tex: if m=btex_code then print("btex") + else print("verbatimtex"); +etex_marker: print("etex"); +mpx_break: print("mpxbreak"); + +@ Actually, |get_t_next| is a macro that avoids procedure overhead except +in the unusual case where \&{btex}, \&{verbatimtex}, \&{etex}, or \&{mpxbreak} +is encountered. + +@d get_t_next==begin get_next; + if cur_cmd<=max_pre_command then t_next; + end +@d TeX_flush=65 {go here to flush to the next ``\&{etex}''} + +@p procedure@?start_mpx_input; forward;@t\2@> +procedure t_next; +label TeX_flush, common_ending; +var @!old_status:normal..loop_defining; {saves the |scanner_status|} +@!old_info:integer; {saves the |warning_info|} +begin while cur_cmd<=max_pre_command do + begin if cur_cmd=mpx_break then + if not file_state or (mpx_name[index]=absent) then + @<Complain about a misplaced \&{mpxbreak}@> + else begin end_mpx_reading; goto TeX_flush; + end + else if cur_cmd=start_tex then + if token_state or (name<=max_spec_src) then + @<Complain that we are not reading a file@> + else if mpx_reading then + @<Complain that \.{MPX} files cannot contain \TeX\ material@> + else if (cur_mod<>verbatim_code)and(mpx_name[index]<>finished) then + begin if not begin_mpx_reading then start_mpx_input; + end + else goto TeX_flush + else @<Complain about a misplaced \&{etex}@>; + goto common_ending; +TeX_flush: @<Flush the \TeX\ material@>; +common_ending: get_next; + end; +end; + +@ We could be in the middle of an operation such as skipping false conditional +text when \TeX\ material is encountered, so we must be careful to save the +|scanner_status|. + +@<Flush the \TeX\ material@>= +old_status:=scanner_status; +old_info:=warning_info; +scanner_status:=tex_flushing; +warning_info:=line; +repeat get_next; +until cur_cmd=etex_marker; +scanner_status:=old_status; +warning_info:=old_info + +@ @<Complain that \.{MPX} files cannot contain \TeX\ material@>= +begin print_err("An mpx file cannot contain btex or verbatimtex blocks"); +help4("This file contains picture expressions for btex...etex")@/ + ("blocks. Such files are normally generated automatically")@/ + ("but this one seems to be messed up. I'll just keep going")@/ + ("and hope for the best.");@/ +error; +end + +@ @<Complain that we are not reading a file@>= +begin print_err("You can only use `btex' or `verbatimtex' in a file"); +help3("I'll have to ignore this preprocessor command because it")@/ + ("only works when there is a file to preprocess. You might")@/ + ("want to delete everything up to the next `etex`.");@/ +error; +end + +@ @<Complain about a misplaced \&{mpxbreak}@>= +begin print_err("Misplaced mpxbreak"); +help2("I'll ignore this preprocessor command because it")@/ + ("doesn't belong here");@/ +error; +end + +@ @<Complain about a misplaced \&{etex}@>= +begin print_err("Extra etex will be ignored"); +help1("There is no btex or verbatimtex for this to match");@/ +error; +end + +@* \[31] Scanning macro definitions. +\MP\ has a variety of ways to tuck tokens away into token lists for later +use: Macros can be defined with \&{def}, \&{vardef}, \&{primarydef}, etc.; +repeatable code can be defined with \&{for}, \&{forever}, \&{forsuffixes}. +All such operations are handled by the routines in this part of the program. + +The modifier part of each command code is zero for the ``ending delimiters'' +like \&{enddef} and \&{endfor}. + +@d start_def=1 {command modifier for \&{def}} +@d var_def=2 {command modifier for \&{vardef}} +@d end_def=0 {command modifier for \&{enddef}} +@d start_forever=1 {command modifier for \&{forever}} +@d end_for=0 {command modifier for \&{endfor}} + +@<Put each...@>= +primitive("def",macro_def,start_def);@/ +@!@:def_}{\&{def} primitive@> +primitive("vardef",macro_def,var_def);@/ +@!@:var_def_}{\&{vardef} primitive@> +primitive("primarydef",macro_def,secondary_primary_macro);@/ +@!@:primary_def_}{\&{primarydef} primitive@> +primitive("secondarydef",macro_def,tertiary_secondary_macro);@/ +@!@:secondary_def_}{\&{secondarydef} primitive@> +primitive("tertiarydef",macro_def,expression_tertiary_macro);@/ +@!@:tertiary_def_}{\&{tertiarydef} primitive@> +primitive("enddef",macro_def,end_def); eqtb[frozen_end_def]:=eqtb[cur_sym];@/ +@!@:end_def_}{\&{enddef} primitive@> +@# +primitive("for",iteration,expr_base);@/ +@!@:for_}{\&{for} primitive@> +primitive("forsuffixes",iteration,suffix_base);@/ +@!@:for_suffixes_}{\&{forsuffixes} primitive@> +primitive("forever",iteration,start_forever);@/ +@!@:forever_}{\&{forever} primitive@> +primitive("endfor",iteration,end_for); eqtb[frozen_end_for]:=eqtb[cur_sym];@/ +@!@:end_for_}{\&{endfor} primitive@> + +@ @<Cases of |print_cmd...@>= +macro_def:if m<=var_def then + if m=start_def then print("def") + else if m<start_def then print("enddef") + else print("vardef") + else if m=secondary_primary_macro then print("primarydef") + else if m=tertiary_secondary_macro then print("secondarydef") + else print("tertiarydef"); +iteration: if m<=start_forever then + if m=start_forever then print("forever")@+else print("endfor") + else if m=expr_base then print("for")@+else print("forsuffixes"); + +@ Different macro-absorbing operations have different syntaxes, but they +also have a lot in common. There is a list of special symbols that are to +be replaced by parameter tokens; there is a special command code that +ends the definition; the quotation conventions are identical. Therefore +it makes sense to have most of the work done by a single subroutine. That +subroutine is called |scan_toks|. + +The first parameter to |scan_toks| is the command code that will +terminate scanning (either |macro_def|, |loop_repeat|, or |iteration|). + +The second parameter, |subst_list|, points to a (possibly empty) list +of two-word nodes whose |info| and |value| fields specify symbol tokens +before and after replacement. The list will be returned to free storage +by |scan_toks|. + +The third parameter is simply appended to the token list that is built. +And the final parameter tells how many of the special operations +\.{\#\AT!}, \.{\AT!}, and \.{\AT!\#} are to be replaced by suffix parameters. +When such parameters are present, they are called \.{(SUFFIX0)}, +\.{(SUFFIX1)}, and \.{(SUFFIX2)}. + +@p function scan_toks(@!terminator:command_code; + @!subst_list,@!tail_end:pointer;@!suffix_count:small_number):pointer; +label done,found; +var @!p:pointer; {tail of the token list being built} +@!q:pointer; {temporary for link management} +@!balance:integer; {left delimiters minus right delimiters} +begin p:=hold_head; balance:=1; link(hold_head):=null; +loop@+ begin get_t_next; + if cur_sym>0 then + begin @<Substitute for |cur_sym|, if it's on the |subst_list|@>; + if cur_cmd=terminator then + @<Adjust the balance; |goto done| if it's zero@> + else if cur_cmd=macro_special then + @<Handle quoted symbols, \.{\#\AT!}, \.{\AT!}, or \.{\AT!\#}@>; + end; + link(p):=cur_tok; p:=link(p); + end; +done: link(p):=tail_end; flush_node_list(subst_list); +scan_toks:=link(hold_head); +end; + +@ @<Substitute for |cur_sym|...@>= +begin q:=subst_list; +while q<>null do + begin if info(q)=cur_sym then + begin cur_sym:=value(q); cur_cmd:=relax; goto found; + end; + q:=link(q); + end; +found:end + +@ @<Adjust the balance; |goto done| if it's zero@>= +if cur_mod>0 then incr(balance) +else begin decr(balance); + if balance=0 then goto done; + end + +@ Four commands are intended to be used only within macro texts: \&{quote}, +\.{\#\AT!}, \.{\AT!}, and \.{\AT!\#}. They are variants of a single command +code called |macro_special|. + +@d quote=0 {|macro_special| modifier for \&{quote}} +@d macro_prefix=1 {|macro_special| modifier for \.{\#\AT!}} +@d macro_at=2 {|macro_special| modifier for \.{\AT!}} +@d macro_suffix=3 {|macro_special| modifier for \.{\AT!\#}} + +@<Put each...@>= +primitive("quote",macro_special,quote);@/ +@!@:quote_}{\&{quote} primitive@> +primitive("#@@",macro_special,macro_prefix);@/ +@!@:]]]\#\AT!_}{\.{\#\AT!} primitive@> +primitive("@@",macro_special,macro_at);@/ +@!@:]]]\AT!_}{\.{\AT!} primitive@> +primitive("@@#",macro_special,macro_suffix);@/ +@!@:]]]\AT!\#_}{\.{\AT!\#} primitive@> + +@ @<Cases of |print_cmd...@>= +macro_special: case m of + macro_prefix: print("#@@"); + macro_at: print_char("@@"); + macro_suffix: print("@@#"); + othercases print("quote") + endcases; + +@ @<Handle quoted...@>= +begin if cur_mod=quote then get_t_next +else if cur_mod<=suffix_count then cur_sym:=suffix_base-1+cur_mod; +end + +@ Here is a routine that's used whenever a token will be redefined. If +the user's token is unredefinable, the `|frozen_inaccessible|' token is +substituted; the latter is redefinable but essentially impossible to use, +hence \MP's tables won't get fouled up. + +@p procedure get_symbol; {sets |cur_sym| to a safe symbol} +label restart; +begin restart: get_t_next; +if (cur_sym=0)or(cur_sym>frozen_inaccessible) then + begin print_err("Missing symbolic token inserted"); +@.Missing symbolic token...@> + help3("Sorry: You can't redefine a number, string, or expr.")@/ + ("I've inserted an inaccessible symbol so that your")@/ + ("definition will be completed without mixing me up too badly."); + if cur_sym>0 then + help_line[2]:="Sorry: You can't redefine my error-recovery tokens." + else if cur_cmd=string_token then delete_str_ref(cur_mod); + cur_sym:=frozen_inaccessible; ins_error; goto restart; + end; +end; + +@ Before we actually redefine a symbolic token, we need to clear away its +former value, if it was a variable. The following stronger version of +|get_symbol| does that. + +@p procedure get_clear_symbol; +begin get_symbol; clear_symbol(cur_sym,false); +end; + +@ Here's another little subroutine; it checks that an equals sign +or assignment sign comes along at the proper place in a macro definition. + +@p procedure check_equals; +begin if cur_cmd<>equals then if cur_cmd<>assignment then + begin missing_err("=");@/ +@.Missing `='@> + help5("The next thing in this `def' should have been `=',")@/ + ("because I've already looked at the definition heading.")@/ + ("But don't worry; I'll pretend that an equals sign")@/ + ("was present. Everything from here to `enddef'")@/ + ("will be the replacement text of this macro."); + back_error; + end; +end; + +@ A \&{primarydef}, \&{secondarydef}, or \&{tertiarydef} is rather easily +handled now that we have |scan_toks|. In this case there are +two parameters, which will be \.{EXPR0} and \.{EXPR1} (i.e., +|expr_base| and |expr_base+1|). + +@p procedure make_op_def; +var @!m:command_code; {the type of definition} +@!p,@!q,@!r:pointer; {for list manipulation} +begin m:=cur_mod;@/ +get_symbol; q:=get_node(token_node_size); +info(q):=cur_sym; value(q):=expr_base;@/ +get_clear_symbol; warning_info:=cur_sym;@/ +get_symbol; p:=get_node(token_node_size); +info(p):=cur_sym; value(p):=expr_base+1; link(p):=q;@/ +get_t_next; check_equals;@/ +scanner_status:=op_defining; q:=get_avail; ref_count(q):=null; +r:=get_avail; link(q):=r; info(r):=general_macro; +link(r):=scan_toks(macro_def,p,null,0); +scanner_status:=normal; eq_type(warning_info):=m; +equiv(warning_info):=q; get_x_next; +end; + +@ Parameters to macros are introduced by the keywords \&{expr}, +\&{suffix}, \&{text}, \&{primary}, \&{secondary}, and \&{tertiary}. + +@<Put each...@>= +primitive("expr",param_type,expr_base);@/ +@!@:expr_}{\&{expr} primitive@> +primitive("suffix",param_type,suffix_base);@/ +@!@:suffix_}{\&{suffix} primitive@> +primitive("text",param_type,text_base);@/ +@!@:text_}{\&{text} primitive@> +primitive("primary",param_type,primary_macro);@/ +@!@:primary_}{\&{primary} primitive@> +primitive("secondary",param_type,secondary_macro);@/ +@!@:secondary_}{\&{secondary} primitive@> +primitive("tertiary",param_type,tertiary_macro);@/ +@!@:tertiary_}{\&{tertiary} primitive@> + +@ @<Cases of |print_cmd...@>= +param_type:if m>=expr_base then + if m=expr_base then print("expr") + else if m=suffix_base then print("suffix") + else print("text") + else if m<secondary_macro then print("primary") + else if m=secondary_macro then print("secondary") + else print("tertiary"); + +@ Let's turn next to the more complex processing associated with \&{def} +and \&{vardef}. When the following procedure is called, |cur_mod| +should be either |start_def| or |var_def|. + +@p @t\4@>@<Declare the procedure called |check_delimiter|@>@; +@t\4@>@<Declare the function called |scan_declared_variable|@>@; +procedure scan_def; +var @!m:start_def..var_def; {the type of definition} +@!n:0..3; {the number of special suffix parameters} +@!k:0..param_size; {the total number of parameters} +@!c:general_macro..text_macro; {the kind of macro we're defining} +@!r:pointer; {parameter-substitution list} +@!q:pointer; {tail of the macro token list} +@!p:pointer; {temporary storage} +@!base:halfword; {|expr_base|, |suffix_base|, or |text_base|} +@!l_delim,@!r_delim:pointer; {matching delimiters} +begin m:=cur_mod; c:=general_macro; link(hold_head):=null;@/ +q:=get_avail; ref_count(q):=null; r:=null;@/ +@<Scan the token or variable to be defined; + set |n|, |scanner_status|, and |warning_info|@>; +k:=n; +if cur_cmd=left_delimiter then + @<Absorb delimited parameters, putting them into lists |q| and |r|@>; +if cur_cmd=param_type then + @<Absorb undelimited parameters, putting them into list |r|@>; +check_equals; +p:=get_avail; info(p):=c; link(q):=p; +@<Attach the replacement text to the tail of node |p|@>; +scanner_status:=normal; get_x_next; +end; + +@ We don't put `|frozen_end_group|' into the replacement text of +a \&{vardef}, because the user may want to redefine `\.{endgroup}'. + +@<Attach the replacement text to the tail of node |p|@>= +if m=start_def then link(p):=scan_toks(macro_def,r,null,n) +else begin q:=get_avail; info(q):=bg_loc; link(p):=q; + p:=get_avail; info(p):=eg_loc; + link(q):=scan_toks(macro_def,r,p,n); + end; +if warning_info=bad_vardef then flush_token_list(value(bad_vardef)) + +@ @<Glob...@>= +@!bg_loc,@!eg_loc:1..hash_end; + {hash addresses of `\.{begingroup}' and `\.{endgroup}'} + +@ @<Scan the token or variable to be defined;...@>= +if m=start_def then + begin get_clear_symbol; warning_info:=cur_sym; get_t_next; + scanner_status:=op_defining; n:=0; + eq_type(warning_info):=defined_macro; equiv(warning_info):=q; + end +else begin p:=scan_declared_variable; + flush_variable(equiv(info(p)),link(p),true); + warning_info:=find_variable(p); flush_list(p); + if warning_info=null then @<Change to `\.{a bad variable}'@>; + scanner_status:=var_defining; n:=2; + if cur_cmd=macro_special then if cur_mod=macro_suffix then {\.{\AT!\#}} + begin n:=3; get_t_next; + end; + type(warning_info):=unsuffixed_macro-2+n; value(warning_info):=q; + end {|suffixed_macro=unsuffixed_macro+1|} + +@ @<Change to `\.{a bad variable}'@>= +begin print_err("This variable already starts with a macro"); +@.This variable already...@> +help2("After `vardef a' you can't say `vardef a.b'.")@/ + ("So I'll have to discard this definition.");@/ +error; warning_info:=bad_vardef; +end + +@ @<Initialize table entries...@>= +name_type(bad_vardef):=root; link(bad_vardef):=frozen_bad_vardef; +equiv(frozen_bad_vardef):=bad_vardef; eq_type(frozen_bad_vardef):=tag_token; + +@ @<Absorb delimited parameters, putting them into lists |q| and |r|@>= +repeat l_delim:=cur_sym; r_delim:=cur_mod; get_t_next; +if (cur_cmd=param_type)and(cur_mod>=expr_base) then base:=cur_mod +else begin print_err("Missing parameter type; `expr' will be assumed"); +@.Missing parameter type@> + help1("You should've had `expr' or `suffix' or `text' here."); + back_error; base:=expr_base; + end; +@<Absorb parameter tokens for type |base|@>; +check_delimiter(l_delim,r_delim); +get_t_next; +until cur_cmd<>left_delimiter + +@ @<Absorb parameter tokens for type |base|@>= +repeat link(q):=get_avail; q:=link(q); info(q):=base+k;@/ +get_symbol; p:=get_node(token_node_size); value(p):=base+k; info(p):=cur_sym; +if k=param_size then overflow("parameter stack size",param_size); +@:MetaPost capacity exceeded parameter stack size}{\quad parameter stack size@> +incr(k); link(p):=r; r:=p; get_t_next; +until cur_cmd<>comma + +@ @<Absorb undelimited parameters, putting them into list |r|@>= +begin p:=get_node(token_node_size); +if cur_mod<expr_base then + begin c:=cur_mod; value(p):=expr_base+k; + end +else begin value(p):=cur_mod+k; + if cur_mod=expr_base then c:=expr_macro + else if cur_mod=suffix_base then c:=suffix_macro + else c:=text_macro; + end; +if k=param_size then overflow("parameter stack size",param_size); +incr(k); get_symbol; info(p):=cur_sym; link(p):=r; r:=p; get_t_next; +if c=expr_macro then if cur_cmd=of_token then + begin c:=of_macro; p:=get_node(token_node_size); + if k=param_size then overflow("parameter stack size",param_size); + value(p):=expr_base+k; get_symbol; info(p):=cur_sym; + link(p):=r; r:=p; get_t_next; + end; +end + +@* \[32] Expanding the next token. +Only a few command codes |<min_command| can possibly be returned by +|get_t_next|; in increasing order, they are +|if_test|, |fi_or_else|, |input|, |iteration|, |repeat_loop|, +|exit_test|, |relax|, |scan_tokens|, |expand_after|, and |defined_macro|. + +\MP\ usually gets the next token of input by saying |get_x_next|. This is +like |get_t_next| except that it keeps getting more tokens until +finding |cur_cmd>=min_command|. In other words, |get_x_next| expands +macros and removes conditionals or iterations or input instructions that +might be present. + +It follows that |get_x_next| might invoke itself recursively. In fact, +there is massive recursion, since macro expansion can involve the +scanning of arbitrarily complex expressions, which in turn involve +macro expansion and conditionals, etc. +@^recursion@> + +Therefore it's necessary to declare a whole bunch of |forward| +procedures at this point, and to insert some other procedures +that will be invoked by |get_x_next|. + +@p procedure@?scan_primary; forward;@t\2@> +procedure@?scan_secondary; forward;@t\2@> +procedure@?scan_tertiary; forward;@t\2@> +procedure@?scan_expression; forward;@t\2@> +procedure@?scan_suffix; forward;@t\2@>@/ +@t\4@>@<Declare the procedure called |macro_call|@>@;@/ +procedure@?get_boolean; forward;@t\2@> +procedure@?pass_text; forward;@t\2@> +procedure@?conditional; forward;@t\2@> +procedure@?start_input; forward;@t\2@> +procedure@?begin_iteration; forward;@t\2@> +procedure@?resume_iteration; forward;@t\2@> +procedure@?stop_iteration; forward;@t\2@> + +@ An auxiliary subroutine called |expand| is used by |get_x_next| +when it has to do exotic expansion commands. + +@p procedure expand; +var @!p:pointer; {for list manipulation} +@!k:integer; {something that we hope is |<=buf_size|} +@!j:pool_pointer; {index into |str_pool|} +begin if internal[tracing_commands]>unity then if cur_cmd<>defined_macro then + show_cur_cmd_mod; +case cur_cmd of +if_test:conditional; {this procedure is discussed in Part 36 below} +fi_or_else:@<Terminate the current conditional and skip to \&{fi}@>; +input:@<Initiate or terminate input from a file@>; +iteration:if cur_mod=end_for then + @<Scold the user for having an extra \&{endfor}@> + else begin_iteration; {this procedure is discussed in Part 37 below} +repeat_loop: @<Repeat a loop@>; +exit_test: @<Exit a loop if the proper time has come@>; +relax: do_nothing; +expand_after: @<Expand the token after the next token@>; +scan_tokens: @<Put a string into the input buffer@>; +defined_macro:macro_call(cur_mod,null,cur_sym); +end; {there are no other cases} +end; + +@ @<Scold the user...@>= +begin print_err("Extra `endfor'"); +@.Extra `endfor'@> +help2("I'm not currently working on a for loop,")@/ + ("so I had better not try to end anything.");@/ +error; +end + +@ The processing of \&{input} involves the |start_input| subroutine, +which will be declared later; the processing of \&{endinput} is trivial. + +@<Put each...@>= +primitive("input",input,0);@/ +@!@:input_}{\&{input} primitive@> +primitive("endinput",input,1);@/ +@!@:end_input_}{\&{endinput} primitive@> + +@ @<Cases of |print_cmd_mod|...@>= +input: if m=0 then print("input")@+else print("endinput"); + +@ @<Initiate or terminate input...@>= +if cur_mod>0 then force_eof:=true +else start_input + +@ We'll discuss the complicated parts of loop operations later. For now +it suffices to know that there's a global variable called |loop_ptr| +that will be |null| if no loop is in progress. + +@<Repeat a loop@>= +begin while token_state and(loc=null) do end_token_list; {conserve stack space} +if loop_ptr=null then + begin print_err("Lost loop"); +@.Lost loop@> + help2("I'm confused; after exiting from a loop, I still seem")@/ + ("to want to repeat it. I'll try to forget the problem.");@/ + error; + end +else resume_iteration; {this procedure is in Part 37 below} +end + +@ @<Exit a loop if the proper time has come@>= +begin get_boolean; +if internal[tracing_commands]>unity then show_cmd_mod(nullary,cur_exp); +if cur_exp=true_code then + if loop_ptr=null then + begin print_err("No loop is in progress"); +@.No loop is in progress@> + help1("Why say `exitif' when there's nothing to exit from?"); + if cur_cmd=semicolon then error@+else back_error; + end + else @<Exit prematurely from an iteration@> +else if cur_cmd<>semicolon then + begin missing_err(";");@/ +@.Missing `;'@> + help2("After `exitif <boolean exp>' I expect to see a semicolon.")@/ + ("I shall pretend that one was there."); back_error; + end; +end + +@ Here we use the fact that |forever_text| is the only |token_type| that +is less than |loop_text|. + +@<Exit prematurely...@>= +begin p:=null; +repeat if file_state then end_file_reading +else begin if token_type<=loop_text then p:=start; + end_token_list; + end; +until p<>null; +if p<>info(loop_ptr) then fatal_error("*** (loop confusion)"); +@.loop confusion@> +stop_iteration; {this procedure is in Part 34 below} +end + +@ @<Expand the token after the next token@>= +begin get_t_next; +p:=cur_tok; get_t_next; +if cur_cmd<min_command then expand else back_input; +back_list(p); +end + +@ @<Put a string into the input buffer@>= +begin get_x_next; scan_primary; +if cur_type<>string_type then + begin disp_err(null,"Not a string"); +@.Not a string@> + help2("I'm going to flush this expression, since")@/ + ("scantokens should be followed by a known string."); + put_get_flush_error(0); + end +else begin back_input; + if length(cur_exp)>0 then @<Pretend we're reading a new one-line file@>; + end; +end + +@ @<Pretend we're reading a new one-line file@>= +begin begin_file_reading; name:=is_scantok; +k:=first+length(cur_exp); +if k>=max_buf_stack then + begin if k>=buf_size then + begin max_buf_stack:=buf_size; + overflow("buffer size",buf_size); +@:MetaPost capacity exceeded buffer size}{\quad buffer size@> + end; + max_buf_stack:=k+1; + end; +j:=str_start[cur_exp]; limit:=k; +while first<limit do + begin buffer[first]:=so(str_pool[j]); incr(j); incr(first); + end; +buffer[limit]:="%"; first:=limit+1; loc:=start; flush_cur_exp(0); +end + +@ Here finally is |get_x_next|. + +The expression scanning routines to be considered later +communicate via the global quantities |cur_type| and |cur_exp|; +we must be very careful to save and restore these quantities while +macros are being expanded. +@^inner loop@> + +@p procedure get_x_next; +var @!save_exp:pointer; {a capsule to save |cur_type| and |cur_exp|} +begin get_t_next; +if cur_cmd<min_command then + begin save_exp:=stash_cur_exp; + repeat if cur_cmd=defined_macro then macro_call(cur_mod,null,cur_sym) + else expand; + get_t_next; + until cur_cmd>=min_command; + unstash_cur_exp(save_exp); {that restores |cur_type| and |cur_exp|} + end; +end; + +@ Now let's consider the |macro_call| procedure, which is used to start up +all user-defined macros. Since the arguments to a macro might be expressions, +|macro_call| is recursive. +@^recursion@> + +The first parameter to |macro_call| points to the reference count of the +token list that defines the macro. The second parameter contains any +arguments that have already been parsed (see below). The third parameter +points to the symbolic token that names the macro. If the third parameter +is |null|, the macro was defined by \&{vardef}, so its name can be +reconstructed from the prefix and ``at'' arguments found within the +second parameter. + +What is this second parameter? It's simply a linked list of one-word items, +whose |info| fields point to the arguments. In other words, if |arg_list=null|, +no arguments have been scanned yet; otherwise |info(arg_list)| points to +the first scanned argument, and |link(arg_list)| points to the list of +further arguments (if any). + +Arguments of type \&{expr} are so-called capsules, which we will +discuss later when we concentrate on expressions; they can be +recognized easily because their |link| field is |void|. Arguments of type +\&{suffix} and \&{text} are token lists without reference counts. + +@ After argument scanning is complete, the arguments are moved to the +|param_stack|. (They can't be put on that stack any sooner, because +the stack is growing and shrinking in unpredictable ways as more arguments +are being acquired.) Then the macro body is fed to the scanner; i.e., +the replacement text of the macro is placed at the top of the \MP's +input stack, so that |get_t_next| will proceed to read it next. + +@<Declare the procedure called |macro_call|@>= +@t\4@>@<Declare the procedure called |print_macro_name|@>@; +@t\4@>@<Declare the procedure called |print_arg|@>@; +@t\4@>@<Declare the procedure called |scan_text_arg|@>@; +procedure macro_call(@!def_ref,@!arg_list,@!macro_name:pointer); + {invokes a user-defined control sequence} +label found; +var @!r:pointer; {current node in the macro's token list} +@!p,@!q:pointer; {for list manipulation} +@!n:integer; {the number of arguments} +@!l_delim,@!r_delim:pointer; {a delimiter pair} +@!tail:pointer; {tail of the argument list} +begin r:=link(def_ref); add_mac_ref(def_ref); +if arg_list=null then n:=0 +else @<Determine the number |n| of arguments already supplied, + and set |tail| to the tail of |arg_list|@>; +if internal[tracing_macros]>0 then + @<Show the text of the macro being expanded, and the existing arguments@>; +@<Scan the remaining arguments, if any; set |r| to the first token + of the replacement text@>; +@<Feed the arguments and replacement text to the scanner@>; +end; + +@ @<Show the text of the macro...@>= +begin begin_diagnostic; print_ln; print_macro_name(arg_list,macro_name); +if n=3 then print("@@#"); {indicate a suffixed macro} +show_macro(def_ref,null,100000); +if arg_list<>null then + begin n:=0; p:=arg_list; + repeat q:=info(p); + print_arg(q,n,0); + incr(n); p:=link(p); + until p=null; + end; +end_diagnostic(false); +end + +@ @<Declare the procedure called |print_macro_name|@>= +procedure print_macro_name(@!a,@!n:pointer); +var @!p,@!q:pointer; {they traverse the first part of |a|} +begin if n<>null then print(text(n)) +else begin p:=info(a); + if p=null then print(text(info(info(link(a))))) + else begin q:=p; + while link(q)<>null do q:=link(q); + link(q):=info(link(a)); + show_token_list(p,null,1000,0); + link(q):=null; + end; + end; +end; + +@ @<Declare the procedure called |print_arg|@>= +procedure print_arg(@!q:pointer;@!n:integer;@!b:pointer); +begin if link(q)=void then print_nl("(EXPR") +else if (b<text_base)and(b<>text_macro) then print_nl("(SUFFIX") +else print_nl("(TEXT"); +print_int(n); print(")<-"); +if link(q)=void then print_exp(q,1) +else show_token_list(q,null,1000,0); +end; + +@ @<Determine the number |n| of arguments already supplied...@>= +begin n:=1; tail:=arg_list; +while link(tail)<>null do + begin incr(n); tail:=link(tail); + end; +end + +@ @<Scan the remaining arguments, if any; set |r|...@>= +cur_cmd:=comma+1; {anything |<>comma| will do} +while info(r)>=expr_base do + begin @<Scan the delimited argument represented by |info(r)|@>; + r:=link(r); + end; +if cur_cmd=comma then + begin print_err("Too many arguments to "); +@.Too many arguments...@> + print_macro_name(arg_list,macro_name); print_char(";"); + print_nl(" Missing `"); print(text(r_delim)); +@.Missing `)'...@> + print("' has been inserted"); + help3("I'm going to assume that the comma I just read was a")@/ + ("right delimiter, and then I'll begin expanding the macro.")@/ + ("You might want to delete some tokens before continuing."); + error; + end; +if info(r)<>general_macro then @<Scan undelimited argument(s)@>; +r:=link(r) + +@ At this point, the reader will find it advisable to review the explanation +of token list format that was presented earlier, paying special attention to +the conventions that apply only at the beginning of a macro's token list. + +On the other hand, the reader will have to take the expression-parsing +aspects of the following program on faith; we will explain |cur_type| +and |cur_exp| later. (Several things in this program depend on each other, +and it's necessary to jump into the circle somewhere.) + +@<Scan the delimited argument represented by |info(r)|@>= +if cur_cmd<>comma then + begin get_x_next; + if cur_cmd<>left_delimiter then + begin print_err("Missing argument to "); +@.Missing argument...@> + print_macro_name(arg_list,macro_name); + help3("That macro has more parameters than you thought.")@/ + ("I'll continue by pretending that each missing argument")@/ + ("is either zero or null."); + if info(r)>=suffix_base then + begin cur_exp:=null; cur_type:=token_list; + end + else begin cur_exp:=0; cur_type:=known; + end; + back_error; cur_cmd:=right_delimiter; goto found; + end; + l_delim:=cur_sym; r_delim:=cur_mod; + end; +@<Scan the argument represented by |info(r)|@>; +if cur_cmd<>comma then @<Check that the proper right delimiter was present@>; +found: @<Append the current expression to |arg_list|@> + +@ @<Check that the proper right delim...@>= +if (cur_cmd<>right_delimiter)or(cur_mod<>l_delim) then + if info(link(r))>=expr_base then + begin missing_err(","); +@.Missing `,'@> + help3("I've finished reading a macro argument and am about to")@/ + ("read another; the arguments weren't delimited correctly.")@/ + ("You might want to delete some tokens before continuing."); + back_error; cur_cmd:=comma; + end + else begin missing_err(text(r_delim)); +@.Missing `)'@> + help2("I've gotten to the end of the macro parameter list.")@/ + ("You might want to delete some tokens before continuing."); + back_error; + end + +@ A \&{suffix} or \&{text} parameter will be have been scanned as +a token list pointed to by |cur_exp|, in which case we will have +|cur_type=token_list|. + +@<Append the current expression to |arg_list|@>= +begin p:=get_avail; +if cur_type=token_list then info(p):=cur_exp +else info(p):=stash_cur_exp; +if internal[tracing_macros]>0 then + begin begin_diagnostic; print_arg(info(p),n,info(r)); end_diagnostic(false); + end; +if arg_list=null then arg_list:=p +else link(tail):=p; +tail:=p; incr(n); +end + +@ @<Scan the argument represented by |info(r)|@>= +if info(r)>=text_base then scan_text_arg(l_delim,r_delim) +else begin get_x_next; + if info(r)>=suffix_base then scan_suffix + else scan_expression; + end + +@ The parameters to |scan_text_arg| are either a pair of delimiters +or zero; the latter case is for undelimited text arguments, which +end with the first semicolon or \&{endgroup} or \&{end} that is not +contained in a group. + +@<Declare the procedure called |scan_text_arg|@>= +procedure scan_text_arg(@!l_delim,@!r_delim:pointer); +label done; +var @!balance:integer; {excess of |l_delim| over |r_delim|} +@!p:pointer; {list tail} +begin warning_info:=l_delim; scanner_status:=absorbing; +p:=hold_head; balance:=1; link(hold_head):=null; +loop@+ begin get_t_next; + if l_delim=0 then @<Adjust the balance for an undelimited argument; + |goto done| if done@> + else @<Adjust the balance for a delimited argument; + |goto done| if done@>; + link(p):=cur_tok; p:=link(p); + end; +done: cur_exp:=link(hold_head); cur_type:=token_list; +scanner_status:=normal; +end; + +@ @<Adjust the balance for a delimited argument...@>= +begin if cur_cmd=right_delimiter then + begin if cur_mod=l_delim then + begin decr(balance); + if balance=0 then goto done; + end; + end +else if cur_cmd=left_delimiter then if cur_mod=r_delim then incr(balance); +end + +@ @<Adjust the balance for an undelimited...@>= +begin if end_of_statement then {|cur_cmd=semicolon|, |end_group|, or |stop|} + begin if balance=1 then goto done + else if cur_cmd=end_group then decr(balance); + end +else if cur_cmd=begin_group then incr(balance); +end + +@ @<Scan undelimited argument(s)@>= +begin if info(r)<text_macro then + begin get_x_next; + if info(r)<>suffix_macro then + if (cur_cmd=equals)or(cur_cmd=assignment) then get_x_next; + end; +case info(r) of +primary_macro:scan_primary; +secondary_macro:scan_secondary; +tertiary_macro:scan_tertiary; +expr_macro:scan_expression; +of_macro:@<Scan an expression followed by `\&{of} $\langle$primary$\rangle$'@>; +suffix_macro:@<Scan a suffix with optional delimiters@>; +text_macro:scan_text_arg(0,0); +end; {there are no other cases} +back_input; @<Append the current expression to |arg_list|@>; +end + +@ @<Scan an expression followed by `\&{of} $\langle$primary$\rangle$'@>= +begin scan_expression; p:=get_avail; info(p):=stash_cur_exp; +if internal[tracing_macros]>0 then + begin begin_diagnostic; print_arg(info(p),n,0); end_diagnostic(false); + end; +if arg_list=null then arg_list:=p@+else link(tail):=p; +tail:=p;incr(n); +if cur_cmd<>of_token then + begin missing_err("of"); print(" for "); +@.Missing `of'@> + print_macro_name(arg_list,macro_name); + help1("I've got the first argument; will look now for the other."); + back_error; + end; +get_x_next; scan_primary; +end + +@ @<Scan a suffix with optional delimiters@>= +begin if cur_cmd<>left_delimiter then l_delim:=null +else begin l_delim:=cur_sym; r_delim:=cur_mod; get_x_next; + end; +scan_suffix; +if l_delim<>null then + begin if(cur_cmd<>right_delimiter)or(cur_mod<>l_delim) then + begin missing_err(text(r_delim)); +@.Missing `)'@> + help2("I've gotten to the end of the macro parameter list.")@/ + ("You might want to delete some tokens before continuing."); + back_error; + end; + get_x_next; + end; +end + +@ Before we put a new token list on the input stack, it is wise to clean off +all token lists that have recently been depleted. Then a user macro that ends +with a call to itself will not require unbounded stack space. + +@<Feed the arguments and replacement text to the scanner@>= +while token_state and(loc=null) do end_token_list; {conserve stack space} +if param_ptr+n>max_param_stack then + begin max_param_stack:=param_ptr+n; + if max_param_stack>param_size then + overflow("parameter stack size",param_size); +@:MetaPost capacity exceeded parameter stack size}{\quad parameter stack size@> + end; +begin_token_list(def_ref,macro); name:=macro_name; loc:=r; +if n>0 then + begin p:=arg_list; + repeat param_stack[param_ptr]:=info(p); incr(param_ptr); p:=link(p); + until p=null; + flush_list(arg_list); + end + +@ It's sometimes necessary to put a single argument onto |param_stack|. +The |stack_argument| subroutine does this. + +@p procedure stack_argument(@!p:pointer); +begin if param_ptr=max_param_stack then + begin incr(max_param_stack); + if max_param_stack>param_size then + overflow("parameter stack size",param_size); +@:MetaPost capacity exceeded parameter stack size}{\quad parameter stack size@> + end; +param_stack[param_ptr]:=p; incr(param_ptr); +end; + +@* \[33] Conditional processing. +Let's consider now the way \&{if} commands are handled. + +Conditions can be inside conditions, and this nesting has a stack +that is independent of other stacks. +Four global variables represent the top of the condition stack: +|cond_ptr| points to pushed-down entries, if~any; |cur_if| tells whether +we are processing \&{if} or \&{elseif}; |if_limit| specifies +the largest code of a |fi_or_else| command that is syntactically legal; +and |if_line| is the line number at which the current conditional began. + +If no conditions are currently in progress, the condition stack has the +special state |cond_ptr=null|, |if_limit=normal|, |cur_if=0|, |if_line=0|. +Otherwise |cond_ptr| points to a two-word node; the |type|, |name_type|, and +|link| fields of the first word contain |if_limit|, |cur_if|, and +|cond_ptr| at the next level, and the second word contains the +corresponding |if_line|. + +@d if_node_size=2 {number of words in stack entry for conditionals} +@d if_line_field(#)==mem[#+1].int +@d if_code=1 {code for \&{if} being evaluated} +@d fi_code=2 {code for \&{fi}} +@d else_code=3 {code for \&{else}} +@d else_if_code=4 {code for \&{elseif}} + +@<Glob...@>= +@!cond_ptr:pointer; {top of the condition stack} +@!if_limit:normal..else_if_code; {upper bound on |fi_or_else| codes} +@!cur_if:small_number; {type of conditional being worked on} +@!if_line:integer; {line where that conditional began} + +@ @<Set init...@>= +cond_ptr:=null; if_limit:=normal; cur_if:=0; if_line:=0; + +@ @<Put each...@>= +primitive("if",if_test,if_code);@/ +@!@:if_}{\&{if} primitive@> +primitive("fi",fi_or_else,fi_code); eqtb[frozen_fi]:=eqtb[cur_sym];@/ +@!@:fi_}{\&{fi} primitive@> +primitive("else",fi_or_else,else_code);@/ +@!@:else_}{\&{else} primitive@> +primitive("elseif",fi_or_else,else_if_code);@/ +@!@:else_if_}{\&{elseif} primitive@> + +@ @<Cases of |print_cmd_mod|...@>= +if_test,fi_or_else: case m of + if_code:print("if"); + fi_code:print("fi"); + else_code:print("else"); + othercases print("elseif") + endcases; + +@ Here is a procedure that ignores text until coming to an \&{elseif}, +\&{else}, or \&{fi} at level zero of $\&{if}\ldots\&{fi}$ +nesting. After it has acted, |cur_mod| will indicate the token that +was found. + +\MP's smallest two command codes are |if_test| and |fi_or_else|; this +makes the skipping process a bit simpler. + +@p procedure pass_text; +label done; +var l:integer; +begin scanner_status:=skipping; l:=0; warning_info:=true_line; +loop@+ begin get_t_next; + if cur_cmd<=fi_or_else then + if cur_cmd<fi_or_else then incr(l) + else begin if l=0 then goto done; + if cur_mod=fi_code then decr(l); + end + else @<Decrease the string reference count, + if the current token is a string@>; + end; +done: scanner_status:=normal; +end; + +@ @<Decrease the string reference count...@>= +if cur_cmd=string_token then delete_str_ref(cur_mod) + +@ When we begin to process a new \&{if}, we set |if_limit:=if_code|; then +if \&{elseif} or \&{else} or \&{fi} occurs before the current \&{if} +condition has been evaluated, a colon will be inserted. +A construction like `\.{if fi}' would otherwise get \MP\ confused. + +@<Push the condition stack@>= +begin p:=get_node(if_node_size); link(p):=cond_ptr; type(p):=if_limit; +name_type(p):=cur_if; if_line_field(p):=if_line; +cond_ptr:=p; if_limit:=if_code; if_line:=true_line; cur_if:=if_code; +end + +@ @<Pop the condition stack@>= +begin p:=cond_ptr; if_line:=if_line_field(p); +cur_if:=name_type(p); if_limit:=type(p); cond_ptr:=link(p); +free_node(p,if_node_size); +end + +@ Here's a procedure that changes the |if_limit| code corresponding to +a given value of |cond_ptr|. + +@p procedure change_if_limit(@!l:small_number;@!p:pointer); +label exit; +var q:pointer; +begin if p=cond_ptr then if_limit:=l {that's the easy case} +else begin q:=cond_ptr; + loop@+ begin if q=null then confusion("if"); +@:this can't happen if}{\quad if@> + if link(q)=p then + begin type(q):=l; return; + end; + q:=link(q); + end; + end; +exit:end; + +@ The user is supposed to put colons into the proper parts of conditional +statements. Therefore, \MP\ has to check for their presence. + +@p procedure check_colon; +begin if cur_cmd<>colon then + begin missing_err(":");@/ +@.Missing `:'@> + help2("There should've been a colon after the condition.")@/ + ("I shall pretend that one was there.");@; + back_error; + end; +end; + +@ A condition is started when the |get_x_next| procedure encounters +an |if_test| command; in that case |get_x_next| calls |conditional|, +which is a recursive procedure. +@^recursion@> + +@p procedure conditional; +label exit,done,reswitch,found; +var @!save_cond_ptr:pointer; {|cond_ptr| corresponding to this conditional} +@!new_if_limit:fi_code..else_if_code; {future value of |if_limit|} +@!p:pointer; {temporary register} +begin @<Push the condition stack@>;@+save_cond_ptr:=cond_ptr; +reswitch: get_boolean; new_if_limit:=else_if_code; +if internal[tracing_commands]>unity then + @<Display the boolean value of |cur_exp|@>; +found: check_colon; +if cur_exp=true_code then + begin change_if_limit(new_if_limit,save_cond_ptr); + return; {wait for \&{elseif}, \&{else}, or \&{fi}} + end; +@<Skip to \&{elseif} or \&{else} or \&{fi}, then |goto done|@>; +done: cur_if:=cur_mod; if_line:=true_line; +if cur_mod=fi_code then @<Pop the condition stack@> +else if cur_mod=else_if_code then goto reswitch +else begin cur_exp:=true_code; new_if_limit:=fi_code; get_x_next; goto found; + end; +exit:end; + +@ In a construction like `\&{if} \&{if} \&{true}: $0=1$: \\{foo} +\&{else}: \\{bar} \&{fi}', the first \&{else} +that we come to after learning that the \&{if} is false is not the +\&{else} we're looking for. Hence the following curious logic is needed. + +@<Skip to \&{elseif}...@>= +loop@+ begin pass_text; + if cond_ptr=save_cond_ptr then goto done + else if cur_mod=fi_code then @<Pop the condition stack@>; + end + + +@ @<Display the boolean value...@>= +begin begin_diagnostic; +if cur_exp=true_code then print("{true}")@+else print("{false}"); +end_diagnostic(false); +end + +@ The processing of conditionals is complete except for the following +code, which is actually part of |get_x_next|. It comes into play when +\&{elseif}, \&{else}, or \&{fi} is scanned. + +@<Terminate the current conditional and skip to \&{fi}@>= +if cur_mod>if_limit then + if if_limit=if_code then {condition not yet evaluated} + begin missing_err(":"); +@.Missing `:'@> + back_input; cur_sym:=frozen_colon; ins_error; + end + else begin print_err("Extra "); print_cmd_mod(fi_or_else,cur_mod); +@.Extra else@> +@.Extra elseif@> +@.Extra fi@> + help1("I'm ignoring this; it doesn't match any if."); + error; + end +else begin while cur_mod<>fi_code do pass_text; {skip to \&{fi}} + @<Pop the condition stack@>; + end + +@* \[34] Iterations. +To bring our treatment of |get_x_next| to a close, we need to consider what +\MP\ does when it sees \&{for}, \&{forsuffixes}, and \&{forever}. + +There's a global variable |loop_ptr| that keeps track of the \&{for} loops +that are currently active. If |loop_ptr=null|, no loops are in progress; +otherwise |info(loop_ptr)| points to the iterative text of the current +(innermost) loop, and |link(loop_ptr)| points to the data for any other +loops that enclose the current one. + +A loop-control node also has two other fields, called |loop_type| and +|loop_list|, whose contents depend on the type of loop: + +\yskip\indent|loop_type(loop_ptr)=null| means that |loop_list(loop_ptr)| +points to a list of one-word nodes whose |info| fields point to the +remaining argument values of a suffix list and expression list. + +\yskip\indent|loop_type(loop_ptr)=void| means that the current loop is +`\&{forever}'. + +\yskip\indent|loop_type(loop_ptr)=progression_flag| means that +|p=loop_list(loop_ptr)| points to a ``progression node'' and |value(p)|, +|step_size(p)|, and |final_value(p)| contain the data for an arithmetic +progression. + +\yskip\indent|loop_type(loop_ptr)=p>void| means that |p| points to an edge +header and |loop_list(loop_ptr)| points into the graphical object list for +that edge header. + +\yskip\noindent In the case of a progression node, the first word is not used +because the link field of words in the dynamic memory area cannot be arbitrary. + +@d loop_list_loc(#)==#+1 {where the |loop_list| field resides} +@d loop_type(#)==info(loop_list_loc(#)) {the type of \&{for} loop} +@d loop_list(#)==link(loop_list_loc(#)) {the remaining list elements} +@d loop_node_size=2 {the number of words in a loop control node} +@d progression_node_size=4 {the number of words in a progression node} +@d step_size(#)==mem[#+2].sc {the step size in an arithmetic progression} +@d final_value(#)==mem[#+3].sc {the final value in an arithmetic progression} +@d progression_flag==null+2 + {|loop_type| value when |loop_list| points to a progression node} + +@<Glob...@>= +@!loop_ptr:pointer; {top of the loop-control-node stack} + +@ @<Set init...@>= +loop_ptr:=null; + +@ If the expressions that define an arithmetic progression in +a \&{for} loop don't have known numeric values, the |bad_for| +subroutine screams at the user. + +@p procedure bad_for(@!s:str_number); +begin disp_err(null,"Improper "); {show the bad expression above the message} +@.Improper...replaced by 0@> +print(s); print(" has been replaced by 0"); +help4("When you say `for x=a step b until c',")@/ + ("the initial value `a' and the step size `b'")@/ + ("and the final value `c' must have known numeric values.")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); +put_get_flush_error(0); +end; + +@ Here's what \MP\ does when \&{for}, \&{forsuffixes}, or \&{forever} +has just been scanned. (This code requires slight familiarity with +expression-parsing routines that we have not yet discussed; but it seems +to belong in the present part of the program, even though the original author +didn't write it until later. The reader may wish to come back to it.) + +@p procedure begin_iteration; +label continue,done; +var @!m:halfword; {|expr_base| (\&{for}) or |suffix_base| (\&{forsuffixes})} +@!n:halfword; {hash address of the current symbol} +@!s:pointer; {the new loop-control node} +@!p:pointer; {substitution list for |scan_toks|} +@!q:pointer; {link manipulation register} +@!pp:pointer; {a new progression node} +begin m:=cur_mod; n:=cur_sym; s:=get_node(loop_node_size); +if m=start_forever then + begin loop_type(s):=void; p:=null; get_x_next; + end +else begin get_symbol; p:=get_node(token_node_size); + info(p):=cur_sym; value(p):=m;@/ + get_x_next; + if cur_cmd=within_token then @<Set up a picture iteration@> + else begin @<Check for the |"="| or |":="| in a loop header@>; + @<Scan the values to be used in the loop@>; + end; + end; +@<Check for the presence of a colon@>; +@<Scan the loop text and put it on the loop control stack@>; +resume_iteration; +end; + +@ @<Check for the |"="| or |":="| in a loop header@>= +if (cur_cmd<>equals)and(cur_cmd<>assignment) then + begin missing_err("=");@/ +@.Missing `='@> + help3("The next thing in this loop should have been `=' or `:='.")@/ + ("But don't worry; I'll pretend that an equals sign")@/ + ("was present, and I'll look for the values next.");@/ + back_error; + end + +@ @<Check for the presence of a colon@>= +if cur_cmd<>colon then + begin missing_err(":");@/ +@.Missing `:'@> + help3("The next thing in this loop should have been a `:'.")@/ + ("So I'll pretend that a colon was present;")@/ + ("everything from here to `endfor' will be iterated."); + back_error; + end + +@ We append a special |frozen_repeat_loop| token in place of the +`\&{endfor}' at the end of the loop. This will come through \MP's scanner +at the proper time to cause the loop to be repeated. + +(If the user tries some shenanigan like `\&{for} $\ldots$ \&{let} \&{endfor}', +he will be foiled by the |get_symbol| routine, which keeps frozen +tokens unchanged. Furthermore the |frozen_repeat_loop| is an \&{outer} +token, so it won't be lost accidentally.) + +@ @<Scan the loop text...@>= +q:=get_avail; info(q):=frozen_repeat_loop; +scanner_status:=loop_defining; warning_info:=n; +info(s):=scan_toks(iteration,p,q,0); scanner_status:=normal;@/ +link(s):=loop_ptr; loop_ptr:=s + +@ @<Initialize table...@>= +eq_type(frozen_repeat_loop):=repeat_loop+outer_tag; +text(frozen_repeat_loop):=" ENDFOR"; + +@ The loop text is inserted into \MP's scanning apparatus by the +|resume_iteration| routine. + +@p procedure resume_iteration; +label not_found,exit; +var @!p,@!q:pointer; {link registers} +begin p:=loop_type(loop_ptr); +if p=progression_flag then + begin p:=loop_list(loop_ptr); {now |p| points to a progression node} + cur_exp:=value(p); + if @<The arithmetic progression has ended@> then goto not_found; + cur_type:=known; q:=stash_cur_exp; {make |q| an \&{expr} argument} + value(p):=cur_exp+step_size(p); {set |value(p)| for the next iteration} + end +else if p=null then + begin p:=loop_list(loop_ptr); + if p=null then goto not_found; + loop_list(loop_ptr):=link(p); q:=info(p); free_avail(p); + end +else if p=void then + begin begin_token_list(info(loop_ptr),forever_text); return; + end +else @<Make |q| a capsule containing the next picture component from + |loop_list(loop_ptr)| or |goto not_found|@>; +begin_token_list(info(loop_ptr),loop_text); +stack_argument(q); +if internal[tracing_commands]>unity then @<Trace the start of a loop@>; +return; +not_found:stop_iteration; +exit:end; + +@ @<The arithmetic progression has ended@>= +((step_size(p)>0)and(cur_exp>final_value(p)))or@| + ((step_size(p)<0)and(cur_exp<final_value(p))) + +@ @<Trace the start of a loop@>= +begin begin_diagnostic; print_nl("{loop value="); +@.loop value=n@> +if (q<>null)and(link(q)=void) then print_exp(q,1) +else show_token_list(q,null,50,0); +print_char("}"); end_diagnostic(false); +end + +@ @<Make |q| a capsule containing the next picture component from...@>= +begin q:=loop_list(loop_ptr); +if q=null then goto not_found; +skip_component(q)(goto not_found); +cur_exp:=copy_objects(loop_list(loop_ptr),q); +init_bbox(cur_exp); +cur_type:=picture_type;@/ +loop_list(loop_ptr):=q; +q:=stash_cur_exp; +end + +@ A level of loop control disappears when |resume_iteration| has decided +not to resume, or when an \&{exitif} construction has removed the loop text +from the input stack. + +@p procedure stop_iteration; +var @!p,@!q:pointer; {the usual} +begin p:=loop_type(loop_ptr); +if p=progression_flag then free_node(loop_list(loop_ptr),progression_node_size) +else if p=null then + begin q:=loop_list(loop_ptr); + while q<>null do + begin p:=info(q); + if p<>null then + if link(p)=void then {it's an \&{expr} parameter} + begin recycle_value(p); free_node(p,value_node_size); + end + else flush_token_list(p); {it's a \&{suffix} or \&{text} parameter} + p:=q; q:=link(q); free_avail(p); + end; + end +else if p>progression_flag then delete_edge_ref(p); +p:=loop_ptr; loop_ptr:=link(p); flush_token_list(info(p)); +free_node(p,loop_node_size); +end; + +@ Now that we know all about loop control, we can finish up +the missing portion of |begin_iteration| and we'll be done. + +The following code is performed after the `\.=' has been scanned in +a \&{for} construction (if |m=expr_base|) or a \&{forsuffixes} construction +(if |m=suffix_base|). + +@<Scan the values to be used in the loop@>= +loop_type(s):=null; q:=loop_list_loc(s); link(q):=null; {|link(q)=loop_list(s)|} +repeat get_x_next; +if m<>expr_base then scan_suffix +else begin if cur_cmd>=colon then if cur_cmd<=comma then goto continue; + scan_expression; + if cur_cmd=step_token then if q=loop_list_loc(s) then + @<Prepare for step-until construction and |goto done|@>; + cur_exp:=stash_cur_exp; + end; +link(q):=get_avail; q:=link(q); info(q):=cur_exp; cur_type:=vacuous; +continue: until cur_cmd<>comma; +done: + +@ @<Prepare for step-until construction and |goto done|@>= +begin if cur_type<>known then bad_for("initial value"); +pp:=get_node(progression_node_size); value(pp):=cur_exp;@/ +get_x_next; scan_expression; +if cur_type<>known then bad_for("step size"); +step_size(pp):=cur_exp; +if cur_cmd<>until_token then + begin missing_err("until");@/ +@.Missing `until'@> + help2("I assume you meant to say `until' after `step'.")@/ + ("So I'll look for the final value and colon next."); + back_error; + end; +get_x_next; scan_expression; +if cur_type<>known then bad_for("final value"); +final_value(pp):=cur_exp; loop_list(s):=pp; +loop_type(s):=progression_flag; goto done; +end + +@ The last case is when we have just seen ``\&{within}'', and we need to +parse a picture expression and prepare to iterate over it. + +@<Set up a picture iteration@>= +begin get_x_next; +scan_expression; +@<Make sure the current expression is a known picture@>; +loop_type(s):=cur_exp; cur_type:=vacuous;@/ +q:=link(dummy_loc(cur_exp)); +if q<> null then + if is_start_or_stop(q) then + if skip_1component(q)=null then q:=link(q); +loop_list(s):=q; +end + +@ @<Make sure the current expression is a known picture@>= +if cur_type<>picture_type then + begin + disp_err(null,"Improper iteration spec has been replaced by nullpicture"); + help1("When you say `for x in p', p must be a known picture."); + put_get_flush_error(get_node(edge_header_size)); + init_edges(cur_exp); cur_type:=picture_type; + end + +@* \[35] File names. +It's time now to fret about file names. Besides the fact that different +operating systems treat files in different ways, we must cope with the +fact that completely different naming conventions are used by different +groups of people. The following programs show what is required for one +particular operating system; similar routines for other systems are not +difficult to devise. +@^system dependencies@> + +\MP\ assumes that a file name has three parts: the name proper; its +``extension''; and a ``file area'' where it is found in an external file +system. The extension of an input file is assumed to be +`\.{.mp}' unless otherwise specified; it is `\.{.log}' on the +transcript file that records each run of \MP; it is `\.{.tfm}' on the font +metric files that describe characters in any fonts created by \MP; it is +`\.{.ps}' or `.{\it nnn}' for some number {\it nnn} on the \ps\ output files; +and it is `\.{.mem}' on the mem files written by \.{INIMP} to initialize \MP. +The file area can be arbitrary on input files, but files are usually +output to the user's current area. If an input file cannot be +found on the specified area, \MP\ will look for it on a special system +area; this special area is intended for commonly used input files. + +Simple uses of \MP\ refer only to file names that have no explicit +extension or area. For example, a person usually says `\.{input} \.{cmr10}' +instead of `\.{input} \.{cmr10.new}'. Simple file +names are best, because they make the \MP\ source files portable; +whenever a file name consists entirely of letters and digits, it should be +treated in the same way by all implementations of \MP. However, users +need the ability to refer to other files in their environment, especially +when responding to error messages concerning unopenable files; therefore +we want to let them use the syntax that appears in their favorite +operating system. + +@ \MP\ uses the same conventions that have proved to be satisfactory for +\TeX\ and \MF. In order to isolate the system-dependent aspects of file names, +@^system dependencies@> +the system-independent parts of \MP\ are expressed in terms +of three system-dependent +procedures called |begin_name|, |more_name|, and |end_name|. In +essence, if the user-specified characters of the file name are $c_1\ldots c_n$, +the system-independent driver program does the operations +$$|begin_name|;\,|more_name|(c_1);\,\ldots\,;|more_name|(c_n); +\,|end_name|.$$ +These three procedures communicate with each other via global variables. +Afterwards the file name will appear in the string pool as three strings +called |cur_name|\penalty10000\hskip-.05em, +|cur_area|, and |cur_ext|; the latter two are null (i.e., +|""|), unless they were explicitly specified by the user. + +Actually the situation is slightly more complicated, because \MP\ needs +to know when the file name ends. The |more_name| routine is a function +(with side effects) that returns |true| on the calls |more_name|$(c_1)$, +\dots, |more_name|$(c_{n-1})$. The final call |more_name|$(c_n)$ +returns |false|; or, it returns |true| and $c_n$ is the last character +on the current input line. In other words, +|more_name| is supposed to return |true| unless it is sure that the +file name has been completely scanned; and |end_name| is supposed to be able +to finish the assembly of |cur_name|, |cur_area|, and |cur_ext| regardless of +whether $|more_name|(c_n)$ returned |true| or |false|. + +@<Glob...@>= +@!cur_name:str_number; {name of file just scanned} +@!cur_area:str_number; {file area just scanned, or \.{""}} +@!cur_ext:str_number; {file extension just scanned, or \.{""}} + +@ It is easier to maintain reference counts if we assign initial values. + +@<Set init...@>= +cur_name:=""; cur_area:=""; cur_ext:=""; + +@ The file names we shall deal with for illustrative purposes have the +following structure: If the name contains `\.>' or `\.:', the file area +consists of all characters up to and including the final such character; +otherwise the file area is null. If the remaining file name contains +`\..', the file extension consists of all such characters from the first +remaining `\..' to the end, otherwise the file extension is null. +@^system dependencies@> + +We can scan such file names easily by using two global variables that keep track +of the occurrences of area and extension delimiters. Note that these variables +cannot be of type |pool_pointer| because a string pool compaction could occur +while scanning a file name. + +@<Glob...@>= +@!area_delimiter:integer; + {most recent `\.>' or `\.:' relative to |str_start[str_ptr]|} +@!ext_delimiter:integer; {the relevant `\..', if any} + +@ Input files that can't be found in the user's area may appear in standard +system areas called |MP_area| and |MF_area|. (The latter is used when the file +extension is |".mf"|.) The standard system area for font metric files +to be read is |MP_font_area|. +This system area name will, of course, vary from place to place. +@^system dependencies@> + +@d MP_area=="MPinputs:" +@.MPinputs@> +@d MF_area=="MFinputs:" +@.MFinputs@> +@d MP_font_area=="TeXfonts:" +@.TeXfonts@> + +@ Here now is the first of the system-dependent routines for file name scanning. +@^system dependencies@> + +@<Declare subroutines for parsing file names@>= +procedure begin_name; +begin delete_str_ref(cur_name); delete_str_ref(cur_area); +delete_str_ref(cur_ext);@/ +area_delimiter:=-1; ext_delimiter:=-1; +end; + +@ And here's the second. +@^system dependencies@> + +@<Declare subroutines for parsing file names@>= +function more_name(@!c:ASCII_code):boolean; +begin if c=" " then more_name:=false +else begin if (c=">")or(c=":") then + begin area_delimiter:=pool_ptr-str_start[str_ptr]; ext_delimiter:=-1; + end + else if (c=".")and(ext_delimiter<0) then + ext_delimiter:=pool_ptr-str_start[str_ptr]; + str_room(1); append_char(c); {contribute |c| to the current string} + more_name:=true; + end; +end; + +@ The third. +@^system dependencies@> + +@<Declare subroutines for parsing file names@>= +procedure end_name; +var a,@!n,@!e:pool_pointer; {length of area, name, and extension} +begin e:=pool_ptr-str_start[str_ptr]; {total length} +if ext_delimiter<0 then ext_delimiter:=e; +a:=area_delimiter+1; n:=ext_delimiter-a; e:=e-ext_delimiter; +if a=0 then cur_area:="" +else begin cur_area:=make_string; + chop_last_string(str_start[cur_area]+a); + end; +if n=0 then cur_name:="" +else begin cur_name:=make_string; + chop_last_string(str_start[cur_name]+n); + end; +if e=0 then cur_ext:="" @+ else cur_ext:=make_string; +end; + +@ Conversely, here is a routine that takes three strings and prints a file +name that might have produced them. (The routine is system dependent, because +some operating systems put the file area last instead of first.) +@^system dependencies@> + +@<Basic printing...@>= +procedure print_file_name(@!n,@!a,@!e:integer); +begin print(a); print(n); print(e); +end; + +@ Another system-dependent routine is needed to convert three internal +\MP\ strings +to the |name_of_file| value that is used to open files. The present code +allows both lowercase and uppercase letters in the file name. +@^system dependencies@> + +@d append_to_name(#)==begin c:=#; incr(k); + if k<=file_name_size then name_of_file[k]:=xchr[c]; + end + +@<Declare subroutines for parsing file names@>= +procedure pack_file_name(@!n,@!a,@!e:str_number); +var @!k:integer; {number of positions filled in |name_of_file|} +@!c: ASCII_code; {character being packed} +@!j:pool_pointer; {index into |str_pool|} +begin k:=0; +for j:=str_start[a] to str_stop(a)-1 do append_to_name(so(str_pool[j])); +for j:=str_start[n] to str_stop(n)-1 do append_to_name(so(str_pool[j])); +for j:=str_start[e] to str_stop(e)-1 do append_to_name(so(str_pool[j])); +if k<=file_name_size then name_length:=k@+else name_length:=file_name_size; +for k:=name_length+1 to file_name_size do name_of_file[k]:=' '; +end; + +@ A messier routine is also needed, since mem file names must be scanned +before \MP's string mechanism has been initialized. We shall use the +global variable |MP_mem_default| to supply the text for default system areas +and extensions related to mem files. +@^system dependencies@> + +@d mem_default_length=15 {length of the |MP_mem_default| string} +@d mem_area_length=6 {length of its area part} +@d mem_ext_length=4 {length of its `\.{.mem}' part} +@d mem_extension=".mem" {the extension, as a \.{WEB} constant} + +@<Glob...@>= +@!MP_mem_default:packed array[1..mem_default_length] of char; + +@ @<Set init...@>= +MP_mem_default:='MPlib:plain.mem'; +@.MPlib@> +@.plain@> +@^system dependencies@> + +@ @<Check the ``constant'' values for consistency@>= +if mem_default_length>file_name_size then bad:=20; + +@ Here is the messy routine that was just mentioned. It sets |name_of_file| +from the first |n| characters of |MP_mem_default|, followed by +|buffer[a..b]|, followed by the last |mem_ext_length| characters of +|MP_mem_default|. + +We dare not give error messages here, since \MP\ calls this routine before +the |error| routine is ready to roll. Instead, we simply drop excess characters, +since the error will be detected in another way when a strange file name +isn't found. +@^system dependencies@> + +@p procedure pack_buffered_name(@!n:small_number;@!a,@!b:integer); +var @!k:integer; {number of positions filled in |name_of_file|} +@!c: ASCII_code; {character being packed} +@!j:integer; {index into |buffer| or |MP_mem_default|} +begin if n+b-a+1+mem_ext_length>file_name_size then + b:=a+file_name_size-n-1-mem_ext_length; +k:=0; +for j:=1 to n do append_to_name(xord[MP_mem_default[j]]); +for j:=a to b do append_to_name(buffer[j]); +for j:=mem_default_length-mem_ext_length+1 to mem_default_length do + append_to_name(xord[MP_mem_default[j]]); +if k<=file_name_size then name_length:=k@+else name_length:=file_name_size; +for k:=name_length+1 to file_name_size do name_of_file[k]:=' '; +end; + +@ Here is the only place we use |pack_buffered_name|. This part of the program +becomes active when a ``virgin'' \MP\ is trying to get going, just after +the preliminary initialization, or when the user is substituting another +mem file by typing `\.\&' after the initial `\.{**}' prompt. The buffer +contains the first line of input in |buffer[loc..(last-1)]|, where +|loc<last| and |buffer[loc]<>" "|. + +@<Declare the function called |open_mem_file|@>= +function open_mem_file:boolean; +label found,exit; +var @!j:0..buf_size; {the first space after the file name} +begin j:=loc; +if buffer[loc]="&" then + begin incr(loc); j:=loc; buffer[last]:=" "; + while buffer[j]<>" " do incr(j); + pack_buffered_name(0,loc,j-1); {try first without the system file area} + if w_open_in(mem_file) then goto found; + pack_buffered_name(mem_area_length,loc,j-1); + {now try the system mem file area} + if w_open_in(mem_file) then goto found; + wake_up_terminal; + wterm_ln('Sorry, I can''t find that mem file;',' will try PLAIN.'); +@.Sorry, I can't find...@> + update_terminal; + end; + {now pull out all the stops: try for the system \.{plain} file} +pack_buffered_name(mem_default_length-mem_ext_length,1,0); +if not w_open_in(mem_file) then + begin wake_up_terminal; + wterm_ln('I can''t find the PLAIN mem file!'); +@.I can't find PLAIN...@> +@.plain@> + open_mem_file:=false; return; + end; +found:loc:=j; open_mem_file:=true; +exit:end; + +@ Operating systems often make it possible to determine the exact name (and +possible version number) of a file that has been opened. The following routine, +which simply makes a \MP\ string from the value of |name_of_file|, should +ideally be changed to deduce the full name of file~|f|, which is the file +most recently opened, if it is possible to do this in a \PASCAL\ program. +@^system dependencies@> + +This routine might be called after string memory has overflowed, hence +we check for this before calling `|str_room|'. + +@p function make_name_string:str_number; +var @!k:1..file_name_size; {index into |name_of_file|} +begin if str_overflowed then + make_name_string:="?" +else begin str_room(name_length); + for k:=1 to name_length do append_char(xord[name_of_file[k]]); + make_name_string:=make_string; + end; +end; +function a_make_name_string(var @!f:alpha_file):str_number; +begin a_make_name_string:=make_name_string; +end; +function b_make_name_string(var @!f:byte_file):str_number; +begin b_make_name_string:=make_name_string; +end; +function w_make_name_string(var @!f:word_file):str_number; +begin w_make_name_string:=make_name_string; +end; + +@ Now let's consider the ``driver'' +routines by which \MP\ deals with file names +in a system-independent manner. First comes a procedure that looks for a +file name in the input by taking the information from the input buffer. +(We can't use |get_next|, because the conversion to tokens would +destroy necessary information.) + +This procedure doesn't allow semicolons or percent signs to be part of +file names, because of other conventions of \MP. +{\sl The {\logos METAFONT\/}book} doesn't +use semicolons or percents immediately after file names, but some users +no doubt will find it natural to do so; therefore system-dependent +changes to allow such characters in file names should probably +be made with reluctance, and only when an entire file name that +includes special characters is ``quoted'' somehow. +@^system dependencies@> + +@p procedure scan_file_name; +label done; +begin begin_name; +while buffer[loc]=" " do incr(loc); +loop@+begin if (buffer[loc]=";")or(buffer[loc]="%") then goto done; + if not more_name(buffer[loc]) then goto done; + incr(loc); + end; +done: end_name; +end; + +@ Here is another version that takes its input from a string. + +@<Declare subroutines for parsing file names@>= +procedure str_scan_file(@!s:str_number); +label done; +var @!p,@!q:pool_pointer; {current position and stopping point} +begin begin_name; +p:=str_start[s]; q:=str_stop(s); +while p<q do + begin if not more_name(so(str_pool[p])) then goto done; + incr(p); + end; +done: end_name; +end; + +@ The global variable |job_name| contains the file name that was first +\&{input} by the user. This name is extended by `\.{.log}' and `\.{ps}' and +`\.{.mem}' and `\.{.tfm}' in order to make the names of \MP's output files. + +@<Glob...@>= +@!job_name:str_number; {principal file name} +@!log_opened:boolean; {has the transcript file been opened?} +@!log_name:str_number; {full name of the log file} + +@ Initially |job_name=0|; it becomes nonzero as soon as the true name is known. +We have |job_name=0| if and only if the `\.{log}' file has not been opened, +except of course for a short time just after |job_name| has become nonzero. + +@<Initialize the output...@>=job_name:=0; log_opened:=false; + +@ Here is a routine that manufactures the output file names, assuming that +|job_name<>0|. It ignores and changes the current settings of |cur_area| +and |cur_ext|. + +@d pack_cur_name==pack_file_name(cur_name,cur_area,cur_ext) + +@p procedure pack_job_name(@!s:str_number); + {|s = ".log"|, |".mem"|, |".ps"|, or .\\{nnn}} +begin add_str_ref(s); +delete_str_ref(cur_name); delete_str_ref(cur_area); +delete_str_ref(cur_ext);@/ +cur_area:=""; cur_ext:=s; +cur_name:=job_name; pack_cur_name; +end; + +@ If some trouble arises when \MP\ tries to open a file, the following +routine calls upon the user to supply another file name. Parameter~|s| +is used in the error message to identify the type of file; parameter~|e| +is the default extension if none is given. Upon exit from the routine, +variables |cur_name|, |cur_area|, |cur_ext|, and |name_of_file| are +ready for another attempt at file opening. + +@p procedure prompt_file_name(@!s,@!e:str_number); +label done; +var @!k:0..buf_size; {index into |buffer|} +begin if interaction=scroll_mode then wake_up_terminal; +if s="input file name" then print_err("I can't find file `") +@.I can't find file x@> +else print_err("I can't write on file `"); +@.I can't write on file x@> +print_file_name(cur_name,cur_area,cur_ext); print("'."); +if e="" then show_context; +print_nl("Please type another "); print(s); +@.Please type...@> +if interaction<scroll_mode then + fatal_error("*** (job aborted, file error in nonstop mode)"); +@.job aborted, file error...@> +clear_terminal; prompt_input(": "); @<Scan file name in the buffer@>; +if cur_ext="" then cur_ext:=e; +pack_cur_name; +end; + +@ @<Scan file name in the buffer@>= +begin begin_name; k:=first; +while (buffer[k]=" ")and(k<last) do incr(k); +loop@+ begin if k=last then goto done; + if not more_name(buffer[k]) then goto done; + incr(k); + end; +done:end_name; +end + +@ The |open_log_file| routine is used to open the transcript file and to help +it catch up to what has previously been printed on the terminal. + +@p procedure open_log_file; +var @!old_setting:0..max_selector; {previous |selector| setting} +@!k:0..buf_size; {index into |months| and |buffer|} +@!l:0..buf_size; {end of first input line} +@!m:integer; {the current month} +@!months:packed array [1..36] of char; {abbreviations of month names} +begin old_setting:=selector; +if job_name=0 then job_name:="mpout"; +pack_job_name(".log"); +while not a_open_out(log_file) do @<Try to get a different log file name@>; +log_name:=a_make_name_string(log_file); +selector:=log_only; log_opened:=true; +@<Print the banner line, including the date and time@>; +input_stack[input_ptr]:=cur_input; {make sure bottom level is in memory} +print_nl("**"); +@.**@> +l:=input_stack[0].limit_field-1; {last position of first line} +for k:=1 to l do print(buffer[k]); +print_ln; {now the transcript file contains the first line of input} +selector:=old_setting+2; {|log_only| or |term_and_log|} +end; + +@ Sometimes |open_log_file| is called at awkward moments when \MP\ is +unable to print error messages or even to |show_context|. +The |prompt_file_name| routine can result in a |fatal_error|, but the |error| +routine will not be invoked because |log_opened| will be false. + +The normal idea of |batch_mode| is that nothing at all should be written +on the terminal. However, in the unusual case that +no log file could be opened, we make an exception and allow +an explanatory message to be seen. + +Incidentally, the program always refers to the log file as a `\.{transcript +file}', because some systems cannot use the extension `\.{.log}' for +this file. + +@<Try to get a different log file name@>= +begin selector:=term_only; +prompt_file_name("transcript file name",".log"); +end + +@ @<Print the banner...@>= +begin wlog(banner); +print(mem_ident); print(" "); +print_int(round_unscaled(internal[day])); print_char(" "); +months:='JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC'; +m:=round_unscaled(internal[month]); +for k:=3*m-2 to 3*m do wlog(months[k]); +print_char(" "); print_int(round_unscaled(internal[year])); print_char(" "); +m:=round_unscaled(internal[time]); +print_dd(m div 60); print_char(":"); print_dd(m mod 60); +end + +@ The |try_extension| function tries to open an input file determined by +|cur_name|, |cur_area|, and the argument |ext|. It returns |false| if it +can't find the file in |cur_area| or the appropriate system area. + +@p function try_extension(@!ext:str_number):boolean; +begin pack_file_name(cur_name,cur_area,ext); +in_name:=cur_name; in_area:=cur_area; +if a_open_in(cur_file) then try_extension:=true +else begin if str_vs_str(ext,".mf")=0 then in_area:=MF_area + else in_area:=MP_area; + pack_file_name(cur_name,in_area,ext); + try_extension:=a_open_in(cur_file); + end; +end; + +@ After all calls to |try_extension|, we must make sure that we count references +for |in_name| and |in_area| if they match |cur_name| and/or |cur_area|. + +@<Update the string reference counts for |in_name| and |in_area|@>= +if in_name=cur_name then add_str_ref(cur_name); +if in_area=cur_area then add_str_ref(cur_area) + +@ Let's turn now to the procedure that is used to initiate file reading +when an `\.{input}' command is being processed. + +@p procedure start_input; {\MP\ will \.{input} something} +label done; +begin @<Put the desired file name in |(cur_name,cur_ext,cur_area)|@>; +loop@+ begin begin_file_reading; {set up |cur_file| and new level of input} + if cur_ext="" then + if try_extension(".mp") then goto done + else if try_extension("") then goto done + else if try_extension(".mf") then goto done + else do_nothing + else if try_extension(cur_ext) then goto done; + end_file_reading; {remove the level that didn't work} + prompt_file_name("input file name",""); + end; +done: name:=a_make_name_string(cur_file); +@<Update the string reference counts for |in_name| and |in_area|@>; +if job_name=0 then + begin job_name:=cur_name; str_ref[job_name]:=max_str_ref; + open_log_file; + end; {|open_log_file| doesn't |show_context|, so |limit| + and |loc| needn't be set to meaningful values yet} +if term_offset+length(name)>max_print_line-2 then print_ln +else if (term_offset>0)or(file_offset>0) then print_char(" "); +print_char("("); incr(open_parens); print(name); update_terminal; +@<Flush |name| and replace it with |cur_name| if it won't be needed@>; +@<Read the first line of the new file@>; +end; + +@ This code should be omitted if |a_make_name_string| returns something other +than just a copy of its argument and the full file name is needed for opening +\.{MPX} files or implementing the switch-to-editor option. +@^system dependencies@> + +@<Flush |name| and replace it with |cur_name| if it won't be needed@>= +flush_string(name); name:=cur_name; cur_name:=0 + +@ Here we have to remember to tell the |input_ln| routine not to +start with a |get|. If the file is empty, it is considered to +contain a single blank line. +@^system dependencies@> + +@<Read the first line...@>= +begin line:=1; +if input_ln(cur_file,false) then do_nothing; +firm_up_the_line; +buffer[limit]:="%"; first:=limit+1; loc:=start; +end + +@ @<Put the desired file name in |(cur_name,cur_ext,cur_area)|@>= +while token_state and(loc=null) do end_token_list; +if token_state then + begin print_err("File names can't appear within macros"); +@.File names can't...@> + help3("Sorry...I've converted what follows to tokens,")@/ + ("possibly garbaging the name you gave.")@/ + ("Please delete the tokens and insert the name again.");@/ + error; + end; +if file_state then scan_file_name +else begin cur_name:=""; cur_ext:=""; cur_area:=""; + end + +@ Sometimes we need to deal with two file names at once. This procedure +copies the given string into a special array for an old file name. + +@p procedure copy_old_name(s:str_number); +var @!k:integer; {number of positions filled in |old_file_name|} +@!j:pool_pointer; {index into |str_pool|} +begin k:=0; +for j:=str_start[s] to str_stop(s)-1 do + begin incr(k); + if k<=file_name_size then old_file_name[k]:=xchr[so(str_pool[j])]; + end; +if k<=file_name_size then old_name_length:=k +else old_name_length:=file_name_size; +for k:=old_name_length+1 to file_name_size do @+old_file_name[k]:=' '; +end; + +@ @<Glob...@>= +@!old_file_name : packed array[1..file_name_size] of char; + {analogous to |name_of_file|} +@!old_name_length : 0..file_name_size; + {this many relevant characters followed by blanks} + +@ The following simple routine starts reading the \.{MPX} file associated +with the current input file. + +@p procedure start_mpx_input; +label exit,not_found; +var k:1..file_name_size; +begin pack_file_name(in_name,in_area,".mpx"); +@<Try to make sure |name_of_file| refers to a valid \.{MPX} file and + |goto not_found| if there is a problem@>; +begin_file_reading; +if not a_open_in(cur_file) then + begin end_file_reading; + goto not_found; + end; +name:=a_make_name_string(cur_file); +mpx_name[index]:=name; add_str_ref(name); +@<Read the first line of the new file@>; +return; +not_found: @<Explain that the \.{MPX} file can't be read and |succumb|@>; +exit:end; + +@ This should ideally be changed to do whatever is necessary to create the +\.{MPX} file given by |name_of_file| if it does not exist or if it is out +of date. This requires invoking \.{MPtoTeX} on the |old_file_name| and passing +the results through \TeX\ and \.{DVItoMP}. (It is possible to use a +completely different typesetting program if suitable postprocessor is +available to perform the function of \.{DVItoMP}.) +@^system dependencies@> + +@<Try to make sure |name_of_file| refers to a valid \.{MPX} file and + |goto not_found| if there is a problem@>= +copy_old_name(name) +{System-dependent code should be added here} + +@ @<Explain that the \.{MPX} file can't be read and |succumb|@>= +if interaction=error_stop_mode then wake_up_terminal; +print_nl(">> "); +for k:=1 to old_name_length do print(xord[old_file_name[k]]); +print_nl(">> "); +for k:=1 to name_length do print(xord[name_of_file[k]]); +print_nl("! Unable to make mpx file"); +help4("The two files given above are one of your source files")@/ + ("and an auxiliary file I need to read to find out what your")@/ + ("btex..etex blocks mean. If you don't know why I had trouble,")@/ + ("try running it manually through MPtoTeX, TeX, and DVItoMP"); +succumb; + +@ The last file-opening commands are for files accessed via the \&{readfrom} +@:read_from_}{\&{readfrom} primitive@> +operator and the \&{write} command. Such files are stored in separate arrays. +@:write_}{\&{write} primitive@> + +@<Types in the outer block@>= +readf_index = 0..max_read_files; +write_index = 0..max_write_files; + +@ @<Glob...@>= +rd_file:array [readf_index] of alpha_file; {\&{readfrom} files} +rd_fname:array [readf_index] of str_number; + {corresponding file name or 0 if file not open} +read_files:readf_index; {number of valid entries in the above arrays} +wr_file:array [write_index] of alpha_file; {\&{write} files} +wr_fname:array [write_index] of str_number; + {corresponding file name or 0 if file not open} +write_files:write_index; {number of valid entries in the above arrays} + +@ @<Set init...@>= +read_files:=0; +write_files:=0; + +@ This routine starts reading the file named by string~|s| without setting +|loc|, |limit|, or |name|. It returns |false| if the file is empty or cannot +be opened. Otherwise it updates |rd_file[n]| and |rd_fname[n]|. + +@p function start_read_input(s:str_number; n:readf_index):boolean; +label exit,not_found; +begin str_scan_file(s); +pack_cur_name; +begin_file_reading; +if not a_open_in(rd_file[n]) then goto not_found; +if not input_ln(rd_file[n],false) then + begin a_close(rd_file[n]); goto not_found; end; +rd_fname[n]:=s; +add_str_ref(s); +start_read_input:=true; +return; +not_found: end_file_reading; +start_read_input:=false; +exit:end; + +@ Open |wr_file[n]| using file name~|s| and update |wr_fname[n]|. + +@p procedure open_write_file(s:str_number; n:readf_index); +begin str_scan_file(s); +pack_cur_name; +while not a_open_out(wr_file[n]) do + prompt_file_name("file name for write output",""); +wr_fname[n]:=s; +add_str_ref(s); +end; + + +@* \[36] Introduction to the parsing routines. +We come now to the central nervous system that sparks many of \MP's activities. +By evaluating expressions, from their primary constituents to ever larger +subexpressions, \MP\ builds the structures that ultimately define complete +pictures or fonts of type. + +Four mutually recursive subroutines are involved in this process: We call them +$$\hbox{|scan_primary|, |scan_secondary|, |scan_tertiary|, +and |scan_expression|.}$$ +@^recursion@> +Each of them is parameterless and begins with the first token to be scanned +already represented in |cur_cmd|, |cur_mod|, and |cur_sym|. After execution, +the value of the primary or secondary or tertiary or expression that was +found will appear in the global variables |cur_type| and |cur_exp|. The +token following the expression will be represented in |cur_cmd|, |cur_mod|, +and |cur_sym|. + +Technically speaking, the parsing algorithms are ``LL(1),'' more or less; +backup mechanisms have been added in order to provide reasonable error +recovery. + +@<Glob...@>= +@!cur_type:small_number; {the type of the expression just found} +@!cur_exp:integer; {the value of the expression just found} + +@ @<Set init...@>= +cur_exp:=0; + +@ Many different kinds of expressions are possible, so it is wise to have +precise descriptions of what |cur_type| and |cur_exp| mean in all cases: + +\smallskip\hang +|cur_type=vacuous| means that this expression didn't turn out to have a +value at all, because it arose from a \&{begingroup}$\,\ldots\,$\&{endgroup} +construction in which there was no expression before the \&{endgroup}. +In this case |cur_exp| has some irrelevant value. + +\smallskip\hang +|cur_type=boolean_type| means that |cur_exp| is either |true_code| +or |false_code|. + +\smallskip\hang +|cur_type=unknown_boolean| means that |cur_exp| points to a capsule +node that is in the ring of variables equivalent +to at least one undefined boolean variable. + +\smallskip\hang +|cur_type=string_type| means that |cur_exp| is a string number (i.e., an +integer in the range |0<=cur_exp<str_ptr|). That string's reference count +includes this particular reference. + +\smallskip\hang +|cur_type=unknown_string| means that |cur_exp| points to a capsule +node that is in the ring of variables equivalent +to at least one undefined string variable. + +\smallskip\hang +|cur_type=pen_type| means that |cur_exp| points to a node in a pen. Nobody +else points to any of the nodes in this pen. The pen may be polygonal or +elliptical. + +\smallskip\hang +|cur_type=unknown_pen| means that |cur_exp| points to a capsule +node that is in the ring of variables equivalent +to at least one undefined pen variable. + +\smallskip\hang +|cur_type=path_type| means that |cur_exp| points to a the first node of +a path; nobody else points to this particular path. The control points of +the path will have been chosen. + +\smallskip\hang +|cur_type=unknown_path| means that |cur_exp| points to a capsule +node that is in the ring of variables equivalent +to at least one undefined path variable. + +\smallskip\hang +|cur_type=picture_type| means that |cur_exp| points to an edge header node. +There may be other pointers to this particular set of edges. The header node +contains a reference count that includes this particular reference. + +\smallskip\hang +|cur_type=unknown_picture| means that |cur_exp| points to a capsule +node that is in the ring of variables equivalent +to at least one undefined picture variable. + +\smallskip\hang +|cur_type=transform_type| means that |cur_exp| points to a |transform_type| +capsule node. The |value| part of this capsule +points to a transform node that contains six numeric values, +each of which is |independent|, |dependent|, |proto_dependent|, or |known|. + +\smallskip\hang +|cur_type=color_type| means that |cur_exp| points to a |color_type| +capsule node. The |value| part of this capsule +points to a color node that contains three numeric values, +each of which is |independent|, |dependent|, |proto_dependent|, or |known|. + +\smallskip\hang +|cur_type=pair_type| means that |cur_exp| points to a capsule +node whose type is |pair_type|. The |value| part of this capsule +points to a pair node that contains two numeric values, +each of which is |independent|, |dependent|, |proto_dependent|, or |known|. + +\smallskip\hang +|cur_type=known| means that |cur_exp| is a |scaled| value. + +\smallskip\hang +|cur_type=dependent| means that |cur_exp| points to a capsule node whose type +is |dependent|. The |dep_list| field in this capsule points to the associated +dependency list. + +\smallskip\hang +|cur_type=proto_dependent| means that |cur_exp| points to a |proto_dependent| +capsule node. The |dep_list| field in this capsule +points to the associated dependency list. + +\smallskip\hang +|cur_type=independent| means that |cur_exp| points to a capsule node +whose type is |independent|. This somewhat unusual case can arise, for +example, in the expression +`$x+\&{begingroup}\penalty0\,\&{string}\,x; 0\,\&{endgroup}$'. + +\smallskip\hang +|cur_type=token_list| means that |cur_exp| points to a linked list of +tokens. This case arises only on the left-hand side of an assignment +(`\.{:=}') operation, under very special circumstances. + +\smallskip\noindent +The possible settings of |cur_type| have been listed here in increasing +numerical order. Notice that |cur_type| will never be |numeric_type| or +|suffixed_macro| or |unsuffixed_macro|, although variables of those types +are allowed. Conversely, \MP\ has no variables of type |vacuous| or +|token_list|. + +@ Capsules are two-word nodes that have a similar meaning +to |cur_type| and |cur_exp|. Such nodes have |name_type=capsule| +and |link<=void|; and their |type| field is one of the possibilities for +|cur_type| listed above. + +The |value| field of a capsule is, in most cases, the value that +corresponds to its |type|, as |cur_exp| corresponds to |cur_type|. +However, when |cur_exp| would point to a capsule, +no extra layer of indirection is present; the |value| +field is what would have been called |value(cur_exp)| if it had not been +encapsulated. Furthermore, if the type is |dependent| or +|proto_dependent|, the |value| field of a capsule is replaced by +|dep_list| and |prev_dep| fields, since dependency lists in capsules are +always part of the general |dep_list| structure. + +The |get_x_next| routine is careful not to change the values of |cur_type| +and |cur_exp| when it gets an expanded token. However, |get_x_next| might +call a macro, which might parse an expression, which might execute lots of +commands in a group; hence it's possible that |cur_type| might change +from, say, |unknown_boolean| to |boolean_type|, or from |dependent| to +|known| or |independent|, during the time |get_x_next| is called. The +programs below are careful to stash sensitive intermediate results in +capsules, so that \MP's generality doesn't cause trouble. + +Here's a procedure that illustrates these conventions. It takes +the contents of $(|cur_type|\kern-.3pt,|cur_exp|\kern-.3pt)$ +and stashes them away in a +capsule. It is not used when |cur_type=token_list|. +After the operation, |cur_type=vacuous|; hence there is no need to +copy path lists or to update reference counts, etc. + +The special link |void| is put on the capsule returned by +|stash_cur_exp|, because this procedure is used to store macro parameters +that must be easily distinguishable from token lists. + +@<Declare the stashing/unstashing routines@>= +function stash_cur_exp:pointer; +var @!p:pointer; {the capsule that will be returned} +begin case cur_type of +unknown_types,transform_type,color_type,pair_type,dependent,proto_dependent, + independent:p:=cur_exp; +othercases begin p:=get_node(value_node_size); name_type(p):=capsule; + type(p):=cur_type; value(p):=cur_exp; + end +endcases;@/ +cur_type:=vacuous; link(p):=void; stash_cur_exp:=p; +end; + +@ The inverse of |stash_cur_exp| is the following procedure, which +deletes an unnecessary capsule and puts its contents into |cur_type| +and |cur_exp|. + +The program steps of \MP\ can be divided into two categories: those in +which |cur_type| and |cur_exp| are ``alive'' and those in which they are +``dead,'' in the sense that |cur_type| and |cur_exp| contain relevant +information or not. It's important not to ignore them when they're alive, +and it's important not to pay attention to them when they're dead. + +There's also an intermediate category: If |cur_type=vacuous|, then +|cur_exp| is irrelevant, hence we can proceed without caring if |cur_type| +and |cur_exp| are alive or dead. In such cases we say that |cur_type| +and |cur_exp| are {\sl dormant}. It is permissible to call |get_x_next| +only when they are alive or dormant. + +The \\{stash} procedure above assumes that |cur_type| and |cur_exp| +are alive or dormant. The \\{unstash} procedure assumes that they are +dead or dormant; it resuscitates them. + +@<Declare the stashing/unstashing...@>= +procedure unstash_cur_exp(@!p:pointer); +begin cur_type:=type(p); +case cur_type of +unknown_types,transform_type,color_type,pair_type,dependent,proto_dependent, + independent: cur_exp:=p; +othercases begin cur_exp:=value(p); + free_node(p,value_node_size); + end +endcases;@/ +end; + +@ The following procedure prints the values of expressions in an +abbreviated format. If its first parameter |p| is null, the value of +|(cur_type,cur_exp)| is displayed; otherwise |p| should be a capsule +containing the desired value. The second parameter controls the amount of +output. If it is~0, dependency lists will be abbreviated to +`\.{linearform}' unless they consist of a single term. If it is greater +than~1, complicated structures (pens, pictures, and paths) will be displayed +in full. + +@<Declare subroutines for printing expressions@>= +@t\4@>@<Declare the procedure called |print_dp|@>@; +@t\4@>@<Declare the stashing/unstashing routines@>@; +procedure print_exp(@!p:pointer;@!verbosity:small_number); +var @!restore_cur_exp:boolean; {should |cur_exp| be restored?} +@!t:small_number; {the type of the expression} +@!v:integer; {the value of the expression} +@!q:pointer; {a big node being displayed} +begin if p<>null then restore_cur_exp:=false +else begin p:=stash_cur_exp; restore_cur_exp:=true; + end; +t:=type(p); +if t<dependent then v:=value(p)@+else if t<independent then v:=dep_list(p); +@<Print an abbreviated value of |v| with format depending on |t|@>; +if restore_cur_exp then unstash_cur_exp(p); +end; + +@ @<Print an abbreviated value of |v| with format depending on |t|@>= +case t of +vacuous:print("vacuous"); +boolean_type:if v=true_code then print("true")@+else print("false"); +unknown_types,numeric_type:@<Display a variable + that's been declared but not defined@>; +string_type:begin print_char(""""); print(v); print_char(""""); + end; +pen_type,path_type,picture_type:@<Display a complex type@>; +transform_type,color_type,pair_type:if v=null then print_type(t) + else @<Display a big node@>; +known:print_scaled(v); +dependent,proto_dependent:print_dp(t,v,verbosity); +independent:print_variable_name(p); +othercases confusion("exp") +@:this can't happen exp}{\quad exp@> +endcases + +@ @<Display a big node@>= +begin print_char("("); q:=v+big_node_size[t]; +repeat if type(v)=known then print_scaled(value(v)) +else if type(v)=independent then print_variable_name(v) +else print_dp(type(v),dep_list(v),verbosity); +v:=v+2; +if v<>q then print_char(","); +until v=q; +print_char(")"); +end + +@ Values of type \&{picture}, \&{path}, and \&{pen} are displayed verbosely +in the log file only, unless the user has given a positive value to +\\{tracingonline}. + +@<Display a complex type@>= +if verbosity<=1 then print_type(t) +else begin if selector=term_and_log then + if internal[tracing_online]<=0 then + begin selector:=term_only; + print_type(t); print(" (see the transcript file)"); + selector:=term_and_log; + end; + case t of + pen_type:print_pen(v,"",false); + path_type:print_path(v,"",false); + picture_type:print_edges(v,"",false); + end; {there are no other cases} + end + +@ @<Declare the procedure called |print_dp|@>= +procedure print_dp(@!t:small_number;@!p:pointer;@!verbosity:small_number); +var @!q:pointer; {the node following |p|} +begin q:=link(p); +if (info(q)=null) or (verbosity>0) then print_dependency(p,t) +else print("linearform"); +end; + +@ The displayed name of a variable in a ring will not be a capsule unless +the ring consists entirely of capsules. + +@<Display a variable that's been declared but not defined@>= +begin print_type(t); +if v<>null then + begin print_char(" "); + while (name_type(v)=capsule) and (v<>p) do v:=value(v); + print_variable_name(v); + end; +end + +@ When errors are detected during parsing, it is often helpful to +display an expression just above the error message, using |exp_err| +or |disp_err| instead of |print_err|. + +@d exp_err(#)==disp_err(null,#) {displays the current expression} + +@<Declare subroutines for printing expressions@>= +procedure disp_err(@!p:pointer;@!s:str_number); +begin if interaction=error_stop_mode then wake_up_terminal; +print_nl(">> "); +@.>>@> +print_exp(p,1); {``medium verbose'' printing of the expression} +if s<>"" then + begin print_nl("! "); print(s); +@.!\relax@> + end; +end; + +@ If |cur_type| and |cur_exp| contain relevant information that should +be recycled, we will use the following procedure, which changes |cur_type| +to |known| and stores a given value in |cur_exp|. We can think of |cur_type| +and |cur_exp| as either alive or dormant after this has been done, +because |cur_exp| will not contain a pointer value. + +@<Declare the procedure called |flush_cur_exp|@>= +procedure flush_cur_exp(@!v:scaled); +begin case cur_type of +unknown_types,transform_type,color_type,pair_type,@| + dependent,proto_dependent,independent: + begin recycle_value(cur_exp); free_node(cur_exp,value_node_size); + end; +string_type:delete_str_ref(cur_exp); +pen_type,path_type: toss_knot_list(cur_exp); +picture_type:delete_edge_ref(cur_exp); +othercases do_nothing +endcases;@/ +cur_type:=known; cur_exp:=v; +end; + +@ There's a much more general procedure that is capable of releasing +the storage associated with any two-word value packet. + +@<Declare the recycling subroutines@>= +procedure recycle_value(@!p:pointer); +label done; +var @!t:small_number; {a type code} +@!v:integer; {a value} +@!vv:integer; {another value} +@!q,@!r,@!s,@!pp:pointer; {link manipulation registers} +begin t:=type(p); +if t<dependent then v:=value(p); +case t of +undefined,vacuous,boolean_type,known,numeric_type:do_nothing; +unknown_types:ring_delete(p); +string_type:delete_str_ref(v); +path_type,pen_type:toss_knot_list(v); +picture_type:delete_edge_ref(v); +pair_type,color_type,transform_type:@<Recycle a big node@>; +dependent,proto_dependent:@<Recycle a dependency list@>; +independent:@<Recycle an independent variable@>; +token_list,structured:confusion("recycle"); +@:this can't happen recycle}{\quad recycle@> +unsuffixed_macro,suffixed_macro:delete_mac_ref(value(p)); +end; {there are no other cases} +type(p):=undefined; +end; + +@ @<Recycle a big node@>= +if v<>null then + begin q:=v+big_node_size[t]; + repeat q:=q-2; recycle_value(q); + until q=v; + free_node(v,big_node_size[t]); + end + +@ @<Recycle a dependency list@>= +begin q:=dep_list(p); +while info(q)<>null do q:=link(q); +link(prev_dep(p)):=link(q); +prev_dep(link(q)):=prev_dep(p); +link(q):=null; flush_node_list(dep_list(p)); +end + +@ When an independent variable disappears, it simply fades away, unless +something depends on it. In the latter case, a dependent variable whose +coefficient of dependence is maximal will take its place. +The relevant algorithm is due to Ignacio~A. Zabala, who implemented it +as part of his Ph.D. thesis (Stanford University, December 1982). +@^Zabala Salelles, Ignacio Andres@> + +For example, suppose that variable $x$ is being recycled, and that the +only variables depending on~$x$ are $y=2x+a$ and $z=x+b$. In this case +we want to make $y$ independent and $z=.5y-.5a+b$; no other variables +will depend on~$y$. If $\\{tracingequations}>0$ in this situation, +we will print `\.{\#\#\# -2x=-y+a}'. + +There's a slight complication, however: An independent variable $x$ +can occur both in dependency lists and in proto-dependency lists. +This makes it necessary to be careful when deciding which coefficient +is maximal. + +Furthermore, this complication is not so slight when +a proto-dependent variable is chosen to become independent. For example, +suppose that $y=2x+100a$ is proto-dependent while $z=x+b$ is dependent; +then we must change $z=.5y-50a+b$ to a proto-dependency, because of the +large coefficient `50'. + +In order to deal with these complications without wasting too much time, +we shall link together the occurrences of~$x$ among all the linear +dependencies, maintaining separate lists for the dependent and +proto-dependent cases. + +@<Recycle an independent variable@>= +begin max_c[dependent]:=0; max_c[proto_dependent]:=0;@/ +max_link[dependent]:=null; max_link[proto_dependent]:=null;@/ +q:=link(dep_head); +while q<>dep_head do + begin s:=value_loc(q); {now |link(s)=dep_list(q)|} + loop@+ begin r:=link(s); + if info(r)=null then goto done; + if info(r)<>p then s:=r + else begin t:=type(q); link(s):=link(r); info(r):=q; + if abs(value(r))>max_c[t] then + @<Record a new maximum coefficient of type |t|@> + else begin link(r):=max_link[t]; max_link[t]:=r; + end; + end; + end; +done: q:=link(r); + end; +if (max_c[dependent]>0)or(max_c[proto_dependent]>0) then + @<Choose a dependent variable to take the place of the disappearing + independent variable, and change all remaining dependencies + accordingly@>; +end + +@ The code for independency removal makes use of three two-word arrays. + +@<Glob...@>= +@!max_c:array[dependent..proto_dependent] of integer; + {max coefficient magnitude} +@!max_ptr:array[dependent..proto_dependent] of pointer; + {where |p| occurs with |max_c|} +@!max_link:array[dependent..proto_dependent] of pointer; + {other occurrences of |p|} + +@ @<Record a new maximum coefficient...@>= +begin if max_c[t]>0 then + begin link(max_ptr[t]):=max_link[t]; max_link[t]:=max_ptr[t]; + end; +max_c[t]:=abs(value(r)); max_ptr[t]:=r; +end + +@ @<Choose a dependent...@>= +begin if (max_c[dependent] div @'10000 >= + max_c[proto_dependent]) then + t:=dependent +else t:=proto_dependent; +@<Determine the dependency list |s| to substitute for the independent + variable~|p|@>; +t:=dependent+proto_dependent-t; {complement |t|} +if max_c[t]>0 then {we need to pick up an unchosen dependency} + begin link(max_ptr[t]):=max_link[t]; max_link[t]:=max_ptr[t]; + end; +if t<>dependent then @<Substitute new dependencies in place of |p|@> +else @<Substitute new proto-dependencies in place of |p|@>; +flush_node_list(s); +if fix_needed then fix_dependencies; +check_arith; +end + +@ Let |s=max_ptr[t]|. At this point we have $|value|(s)=\pm|max_c|[t]$, +and |info(s)| points to the dependent variable~|pp| of type~|t| from +whose dependency list we have removed node~|s|. We must reinsert +node~|s| into the dependency list, with coefficient $-1.0$, and with +|pp| as the new independent variable. Since |pp| will have a larger serial +number than any other variable, we can put node |s| at the head of the +list. + +@<Determine the dep...@>= +s:=max_ptr[t]; pp:=info(s); v:=value(s); +if t=dependent then value(s):=-fraction_one@+else value(s):=-unity; +r:=dep_list(pp); link(s):=r; +while info(r)<>null do r:=link(r); +q:=link(r); link(r):=null; +prev_dep(q):=prev_dep(pp); link(prev_dep(pp)):=q; +new_indep(pp); +if cur_exp=pp then if cur_type=t then cur_type:=independent; +if internal[tracing_equations]>0 then @<Show the transformed dependency@> + +@ Now $(-v)$ times the formerly independent variable~|p| is being replaced +by the dependency list~|s|. + +@<Show the transformed...@>= +if interesting(p) then + begin begin_diagnostic; print_nl("### "); +@:]]]\#\#\#_}{\.{\#\#\#}@> + if v>0 then print_char("-"); + if t=dependent then vv:=round_fraction(max_c[dependent]) + else vv:=max_c[proto_dependent]; + if vv<>unity then print_scaled(vv); + print_variable_name(p); + while value(p) mod s_scale>0 do + begin print("*4"); value(p):=value(p)-2; + end; + if t=dependent then print_char("=")@+else print(" = "); + print_dependency(s,t); + end_diagnostic(false); + end + +@ Finally, there are dependent and proto-dependent variables whose +dependency lists must be brought up to date. + +@<Substitute new dependencies...@>= +for t:=dependent to proto_dependent do + begin r:=max_link[t]; + while r<>null do + begin q:=info(r); + dep_list(q):=p_plus_fq(dep_list(q),@| + make_fraction(value(r),-v),s,t,dependent); + if dep_list(q)=dep_final then make_known(q,dep_final); + q:=r; r:=link(r); free_node(q,dep_node_size); + end; + end + +@ @<Substitute new proto...@>= +for t:=dependent to proto_dependent do + begin r:=max_link[t]; + while r<>null do + begin q:=info(r); + if t=dependent then {for safety's sake, we change |q| to |proto_dependent|} + begin if cur_exp=q then if cur_type=dependent then + cur_type:=proto_dependent; + dep_list(q):=p_over_v(dep_list(q),unity,dependent,proto_dependent); + type(q):=proto_dependent; value(r):=round_fraction(value(r)); + end; + dep_list(q):=p_plus_fq(dep_list(q),@| + make_scaled(value(r),-v),s,proto_dependent,proto_dependent); + if dep_list(q)=dep_final then make_known(q,dep_final); + q:=r; r:=link(r); free_node(q,dep_node_size); + end; + end + +@ Here are some routines that provide handy combinations of actions +that are often needed during error recovery. For example, +`|flush_error|' flushes the current expression, replaces it by +a given value, and calls |error|. + +Errors often are detected after an extra token has already been scanned. +The `\\{put\_get}' routines put that token back before calling |error|; +then they get it back again. (Or perhaps they get another token, if +the user has changed things.) + +@<Declare the procedure called |flush_cur_exp|@>= +procedure flush_error(@!v:scaled);@+begin error; flush_cur_exp(v);@+end; +@# +procedure@?back_error; forward;@t\2@>@/ +procedure@?get_x_next; forward;@t\2@>@/ +@# +procedure put_get_error;@+begin back_error; get_x_next;@+end; +@# +procedure put_get_flush_error(@!v:scaled);@+begin put_get_error; + flush_cur_exp(v);@+end; + +@ A global variable |var_flag| is set to a special command code +just before \MP\ calls |scan_expression|, if the expression should be +treated as a variable when this command code immediately follows. For +example, |var_flag| is set to |assignment| at the beginning of a +statement, because we want to know the {\sl location\/} of a variable at +the left of `\.{:=}', not the {\sl value\/} of that variable. + +The |scan_expression| subroutine calls |scan_tertiary|, +which calls |scan_secondary|, which calls |scan_primary|, which sets +|var_flag:=0|. In this way each of the scanning routines ``knows'' +when it has been called with a special |var_flag|, but |var_flag| is +usually zero. + +A variable preceding a command that equals |var_flag| is converted to a +token list rather than a value. Furthermore, an `\.{=}' sign following an +expression with |var_flag=assignment| is not considered to be a relation +that produces boolean expressions. + + +@<Glob...@>= +@!var_flag:0..max_command_code; {command that wants a variable} + +@ @<Set init...@>= +var_flag:=0; + +@* \[37] Parsing primary expressions. +The first parsing routine, |scan_primary|, is also the most complicated one, +since it involves so many different cases. But each case---with one +exception---is fairly simple by itself. + +When |scan_primary| begins, the first token of the primary to be scanned +should already appear in |cur_cmd|, |cur_mod|, and |cur_sym|. The values +of |cur_type| and |cur_exp| should be either dead or dormant, as explained +earlier. If |cur_cmd| is not between |min_primary_command| and +|max_primary_command|, inclusive, a syntax error will be signaled. + +@<Declare the basic parsing subroutines@>= +procedure scan_primary; +label restart, done, done1, done2; +var @!p,@!q,@!r:pointer; {for list manipulation} +@!c:quarterword; {a primitive operation code} +@!my_var_flag:0..max_command_code; {initial value of |my_var_flag|} +@!l_delim,@!r_delim:pointer; {hash addresses of a delimiter pair} +@<Other local variables for |scan_primary|@>@; +begin my_var_flag:=var_flag; var_flag:=0; +restart:check_arith; +@<Supply diagnostic information, if requested@>; +case cur_cmd of +left_delimiter:@<Scan a delimited primary@>; +begin_group:@<Scan a grouped primary@>; +string_token:@<Scan a string constant@>; +numeric_token:@<Scan a primary that starts with a numeric token@>; +nullary:@<Scan a nullary operation@>; +unary,type_name,cycle,plus_or_minus:@<Scan a unary operation@>; +primary_binary:@<Scan a binary operation with `\&{of}' between its operands@>; +str_op:@<Convert a suffix to a string@>; +internal_quantity:@<Scan an internal numeric quantity@>; +capsule_token:make_exp_copy(cur_mod); +tag_token:@<Scan a variable primary; + |goto restart| if it turns out to be a macro@>; +othercases begin bad_exp("A primary"); goto restart; +@.A primary expression...@> + end +endcases;@/ +get_x_next; {the routines |goto done| if they don't want this} +done: if cur_cmd=left_bracket then + if cur_type>=known then @<Scan a mediation construction@>; +end; + +@ Errors at the beginning of expressions are flagged by |bad_exp|. + +@p procedure bad_exp(@!s:str_number); +var save_flag:0..max_command_code; +begin print_err(s); print(" expression can't begin with `"); +print_cmd_mod(cur_cmd,cur_mod); print_char("'"); +help4("I'm afraid I need some sort of value in order to continue,")@/ + ("so I've tentatively inserted `0'. You may want to")@/ + ("delete this zero and insert something else;")@/ + ("see Chapter 27 of The METAFONTbook for an example."); +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +back_input; cur_sym:=0; cur_cmd:=numeric_token; cur_mod:=0; ins_error;@/ +save_flag:=var_flag; var_flag:=0; get_x_next; +var_flag:=save_flag; +end; + +@ @<Supply diagnostic information, if requested@>= +debug if panicking then check_mem(false);@+gubed@;@/ +if interrupt<>0 then if OK_to_interrupt then + begin back_input; check_interrupt; get_x_next; + end + +@ @<Scan a delimited primary@>= +begin l_delim:=cur_sym; r_delim:=cur_mod; get_x_next; scan_expression; +if (cur_cmd=comma) and (cur_type>=known) then + @<Scan the rest of a pair or triplet of numerics@> +else check_delimiter(l_delim,r_delim); +end + +@ The |stash_in| subroutine puts the current (numeric) expression into a field +within a ``big node.'' + +@p procedure stash_in(@!p:pointer); +var @!q:pointer; {temporary register} +begin type(p):=cur_type; +if cur_type=known then value(p):=cur_exp +else begin if cur_type=independent then + @<Stash an independent |cur_exp| into a big node@> + else begin mem[value_loc(p)]:=mem[value_loc(cur_exp)]; + {|dep_list(p):=dep_list(cur_exp)| and |prev_dep(p):=prev_dep(cur_exp)|} + link(prev_dep(p)):=p; + end; + free_node(cur_exp,value_node_size); + end; +cur_type:=vacuous; +end; + +@ In rare cases the current expression can become |independent|. There +may be many dependency lists pointing to such an independent capsule, +so we can't simply move it into place within a big node. Instead, +we copy it, then recycle it. + +@ @<Stash an independent |cur_exp|...@>= +begin q:=single_dependency(cur_exp); +if q=dep_final then + begin type(p):=known; value(p):=0; free_node(q,dep_node_size); + end +else begin type(p):=dependent; new_dep(p,q); + end; +recycle_value(cur_exp); +end + +@ This code uses the fact that |red_part_loc| and |green_part_loc| +are synonymous with |x_part_loc| and |y_part_loc|. + +@<Scan the rest of a pair or triplet of numerics@>= +begin p:=stash_cur_exp; +get_x_next; scan_expression; +@<Make sure the second part of a pair or color has a numeric type@>; +q:=get_node(value_node_size); name_type(q):=capsule; +if cur_cmd=comma then type(q):=color_type +else type(q):=pair_type; +init_big_node(q); r:=value(q); +stash_in(y_part_loc(r)); +unstash_cur_exp(p); +stash_in(x_part_loc(r)); +if cur_cmd=comma then @<Scan the last of a triplet of numerics@>; +check_delimiter(l_delim,r_delim); +cur_type:=type(q); +cur_exp:=q; +end + +@ @<Make sure the second part of a pair or color has a numeric type@>= +if cur_type<known then + begin exp_err("Nonnumeric ypart has been replaced by 0"); +@.Nonnumeric...replaced by 0@> + help4("I've started to scan a pair `(a,b)' or a color `(a,b,c)';")@/ + ("but after finding a nice `a' I found a `b' that isn't")@/ + ("of numeric type. So I've changed that part to zero.")@/ + ("(The b that I didn't like appears above the error message.)"); + put_get_flush_error(0); + end + +@ @<Scan the last of a triplet of numerics@>= +begin get_x_next; scan_expression; +if cur_type<known then + begin exp_err("Nonnumeric bluepart has been replaced by 0"); +@.Nonnumeric...replaced by 0@> + help3("I've just scanned a color `(r,g,b)'; but the `b' isn't")@/ + ("of numeric type. So I've changed that part to zero.")@/ + ("(The b that I didn't like appears above the error message.)");@/ + put_get_flush_error(0); + end; +stash_in(blue_part_loc(r)); +end + +@ The local variable |group_line| keeps track of the line +where a \&{begingroup} command occurred; this will be useful +in an error message if the group doesn't actually end. + +@<Other local variables for |scan_primary|@>= +@!group_line:integer; {where a group began} + +@ @<Scan a grouped primary@>= +begin group_line:=true_line; +if internal[tracing_commands]>0 then show_cur_cmd_mod; +save_boundary_item(p); +repeat do_statement; {ends with |cur_cmd>=semicolon|} +until cur_cmd<>semicolon; +if cur_cmd<>end_group then + begin print_err("A group begun on line "); +@.A group...never ended@> + print_int(group_line); + print(" never ended"); + help2("I saw a `begingroup' back there that hasn't been matched")@/ + ("by `endgroup'. So I've inserted `endgroup' now."); + back_error; cur_cmd:=end_group; + end; +unsave; {this might change |cur_type|, if independent variables are recycled} +if internal[tracing_commands]>0 then show_cur_cmd_mod; +end + +@ @<Scan a string constant@>= +begin cur_type:=string_type; cur_exp:=cur_mod; +end + +@ Later we'll come to procedures that perform actual operations like +addition, square root, and so on; our purpose now is to do the parsing. +But we might as well mention those future procedures now, so that the +suspense won't be too bad: + +\smallskip +|do_nullary(c)| does primitive operations that have no operands (e.g., +`\&{true}' or `\&{pencircle}'); + +\smallskip +|do_unary(c)| applies a primitive operation to the current expression; + +\smallskip +|do_binary(p,c)| applies a primitive operation to the capsule~|p| +and the current expression. + +@<Scan a nullary operation@>=do_nullary(cur_mod) + +@ @<Scan a unary operation@>= +begin c:=cur_mod; get_x_next; scan_primary; do_unary(c); goto done; +end + +@ A numeric token might be a primary by itself, or it might be the +numerator of a fraction composed solely of numeric tokens, or it might +multiply the primary that follows (provided that the primary doesn't begin +with a plus sign or a minus sign). The code here uses the facts that +|max_primary_command=plus_or_minus| and +|max_primary_command-1=numeric_token|. If a fraction is found that is less +than unity, we try to retain higher precision when we use it in scalar +multiplication. + +@<Other local variables for |scan_primary|@>= +@!num,@!denom:scaled; {for primaries that are fractions, like `1/2'} + +@ @<Scan a primary that starts with a numeric token@>= +begin cur_exp:=cur_mod; cur_type:=known; get_x_next; +if cur_cmd<>slash then + begin num:=0; denom:=0; + end +else begin get_x_next; + if cur_cmd<>numeric_token then + begin back_input; + cur_cmd:=slash; cur_mod:=over; cur_sym:=frozen_slash; + goto done; + end; + num:=cur_exp; denom:=cur_mod; + if denom=0 then @<Protest division by zero@> + else cur_exp:=make_scaled(num,denom); + check_arith; get_x_next; + end; +if cur_cmd>=min_primary_command then + if cur_cmd<numeric_token then {in particular, |cur_cmd<>plus_or_minus|} + begin p:=stash_cur_exp; scan_primary; + if (abs(num)>=abs(denom))or(cur_type<color_type) then do_binary(p,times) + else begin frac_mult(num,denom); + free_node(p,value_node_size); + end; + end; +goto done; +end + +@ @<Protest division...@>= +begin print_err("Division by zero"); +@.Division by zero@> +help1("I'll pretend that you meant to divide by 1."); error; +end + +@ @<Scan a binary operation with `\&{of}' between its operands@>= +begin c:=cur_mod; get_x_next; scan_expression; +if cur_cmd<>of_token then + begin missing_err("of"); print(" for "); print_cmd_mod(primary_binary,c); +@.Missing `of'@> + help1("I've got the first argument; will look now for the other."); + back_error; + end; +p:=stash_cur_exp; get_x_next; scan_primary; do_binary(p,c); goto done; +end + +@ @<Convert a suffix to a string@>= +begin get_x_next; scan_suffix; old_setting:=selector; selector:=new_string; +show_token_list(cur_exp,null,100000,0); flush_token_list(cur_exp); +cur_exp:=make_string; selector:=old_setting; cur_type:=string_type; +goto done; +end + +@ If an internal quantity appears all by itself on the left of an +assignment, we return a token list of length one, containing the address +of the internal quantity plus |hash_end|. (This accords with the conventions +of the save stack, as described earlier.) + +@<Scan an internal...@>= +begin q:=cur_mod; +if my_var_flag=assignment then + begin get_x_next; + if cur_cmd=assignment then + begin cur_exp:=get_avail; + info(cur_exp):=q+hash_end; cur_type:=token_list; goto done; + end; + back_input; + end; +cur_type:=known; cur_exp:=internal[q]; +end + +@ The most difficult part of |scan_primary| has been saved for last, since +it was necessary to build up some confidence first. We can now face the task +of scanning a variable. + +As we scan a variable, we build a token list containing the relevant +names and subscript values, simultaneously following along in the +``collective'' structure to see if we are actually dealing with a macro +instead of a value. + +The local variables |pre_head| and |post_head| will point to the beginning +of the prefix and suffix lists; |tail| will point to the end of the list +that is currently growing. + +Another local variable, |tt|, contains partial information about the +declared type of the variable-so-far. If |tt>=unsuffixed_macro|, the +relation |tt=type(q)| will always hold. If |tt=undefined|, the routine +doesn't bother to update its information about type. And if +|undefined<tt<unsuffixed_macro|, the precise value of |tt| isn't critical. + +@ @<Other local variables for |scan_primary|@>= +@!pre_head,@!post_head,@!tail:pointer; + {prefix and suffix list variables} +@!tt:small_number; {approximation to the type of the variable-so-far} +@!t:pointer; {a token} +@!macro_ref:pointer; {reference count for a suffixed macro} + +@ @<Scan a variable primary...@>= +begin fast_get_avail(pre_head); tail:=pre_head; post_head:=null; tt:=vacuous; +loop@+ begin t:=cur_tok; link(tail):=t; + if tt<>undefined then + begin @<Find the approximate type |tt| and corresponding~|q|@>; + if tt>=unsuffixed_macro then + @<Either begin an unsuffixed macro call or + prepare for a suffixed one@>; + end; + get_x_next; tail:=t; + if cur_cmd=left_bracket then + @<Scan for a subscript; replace |cur_cmd| by |numeric_token| if found@>; + if cur_cmd>max_suffix_token then goto done1; + if cur_cmd<min_suffix_token then goto done1; + end; {now |cur_cmd| is |internal_quantity|, |tag_token|, or |numeric_token|} +done1:@<Handle unusual cases that masquerade as variables, and |goto restart| + or |goto done| if appropriate; + otherwise make a copy of the variable and |goto done|@>; +end + +@ @<Either begin an unsuffixed macro call or...@>= +begin link(tail):=null; +if tt>unsuffixed_macro then {|tt=suffixed_macro|} + begin post_head:=get_avail; tail:=post_head; link(tail):=t;@/ + tt:=undefined; macro_ref:=value(q); add_mac_ref(macro_ref); + end +else @<Set up unsuffixed macro call and |goto restart|@>; +end + +@ @<Scan for a subscript; replace |cur_cmd| by |numeric_token| if found@>= +begin get_x_next; scan_expression; +if cur_cmd<>right_bracket then + @<Put the left bracket and the expression back to be rescanned@> +else begin if cur_type<>known then bad_subscript; + cur_cmd:=numeric_token; cur_mod:=cur_exp; cur_sym:=0; + end; +end + +@ The left bracket that we thought was introducing a subscript might have +actually been the left bracket in a mediation construction like `\.{x[a,b]}'. +So we don't issue an error message at this point; but we do want to back up +so as to avoid any embarrassment about our incorrect assumption. + +@<Put the left bracket and the expression back to be rescanned@>= +begin back_input; {that was the token following the current expression} +back_expr; cur_cmd:=left_bracket; cur_mod:=0; cur_sym:=frozen_left_bracket; +end + +@ Here's a routine that puts the current expression back to be read again. + +@p procedure back_expr; +var @!p:pointer; {capsule token} +begin p:=stash_cur_exp; link(p):=null; back_list(p); +end; + +@ Unknown subscripts lead to the following error message. + +@p procedure bad_subscript; +begin exp_err("Improper subscript has been replaced by zero"); +@.Improper subscript...@> +help3("A bracketed subscript must have a known numeric value;")@/ + ("unfortunately, what I found was the value that appears just")@/ + ("above this error message. So I'll try a zero subscript."); +flush_error(0); +end; + +@ Every time we call |get_x_next|, there's a chance that the variable we've +been looking at will disappear. Thus, we cannot safely keep |q| pointing +into the variable structure; we need to start searching from the root each time. + +@<Find the approximate type |tt| and corresponding~|q|@>= +@^inner loop@> +begin p:=link(pre_head); q:=info(p); tt:=undefined; +if eq_type(q) mod outer_tag=tag_token then + begin q:=equiv(q); + if q=null then goto done2; + loop@+ begin p:=link(p); + if p=null then + begin tt:=type(q); goto done2; + end; + if type(q)<>structured then goto done2; + q:=link(attr_head(q)); {the |collective_subscript| attribute} + if p>=hi_mem_min then {it's not a subscript} + begin repeat q:=link(q); + until attr_loc(q)>=info(p); + if attr_loc(q)>info(p) then goto done2; + end; + end; + end; +done2:end + +@ How do things stand now? Well, we have scanned an entire variable name, +including possible subscripts and/or attributes; |cur_cmd|, |cur_mod|, and +|cur_sym| represent the token that follows. If |post_head=null|, a +token list for this variable name starts at |link(pre_head)|, with all +subscripts evaluated. But if |post_head<>null|, the variable turned out +to be a suffixed macro; |pre_head| is the head of the prefix list, while +|post_head| is the head of a token list containing both `\.{\AT!}' and +the suffix. + +Our immediate problem is to see if this variable still exists. (Variable +structures can change drastically whenever we call |get_x_next|; users +aren't supposed to do this, but the fact that it is possible means that +we must be cautious.) + +The following procedure prints an error message when a variable +unexpectedly disappears. Its help message isn't quite right for +our present purposes, but we'll be able to fix that up. + +@p procedure obliterated(@!q:pointer); +begin print_err("Variable "); show_token_list(q,null,1000,0); +print(" has been obliterated"); +@.Variable...obliterated@> +help5("It seems you did a nasty thing---probably by accident,")@/ + ("but nevertheless you nearly hornswoggled me...")@/ + ("While I was evaluating the right-hand side of this")@/ + ("command, something happened, and the left-hand side")@/ + ("is no longer a variable! So I won't change anything."); +end; + +@ If the variable does exist, we also need to check +for a few other special cases before deciding that a plain old ordinary +variable has, indeed, been scanned. + +@<Handle unusual cases that masquerade as variables...@>= +if post_head<>null then @<Set up suffixed macro call and |goto restart|@>; +q:=link(pre_head); free_avail(pre_head); +if cur_cmd=my_var_flag then + begin cur_type:=token_list; cur_exp:=q; goto done; + end; +p:=find_variable(q); +if p<>null then make_exp_copy(p) +else begin obliterated(q);@/ + help_line[2]:="While I was evaluating the suffix of this variable,"; + help_line[1]:="something was redefined, and it's no longer a variable!"; + help_line[0]:="In order to get back on my feet, I've inserted `0' instead."; + put_get_flush_error(0); + end; +flush_node_list(q); goto done + +@ The only complication associated with macro calling is that the prefix +and ``at'' parameters must be packaged in an appropriate list of lists. + +@<Set up unsuffixed macro call and |goto restart|@>= +begin p:=get_avail; info(pre_head):=link(pre_head); link(pre_head):=p; +info(p):=t; macro_call(value(q),pre_head,null); get_x_next; goto restart; +end + +@ If the ``variable'' that turned out to be a suffixed macro no longer exists, +we don't care, because we have reserved a pointer (|macro_ref|) to its +token list. + +@<Set up suffixed macro call and |goto restart|@>= +begin back_input; p:=get_avail; q:=link(post_head); +info(pre_head):=link(pre_head); link(pre_head):=post_head; +info(post_head):=q; link(post_head):=p; info(p):=link(q); link(q):=null; +macro_call(macro_ref,pre_head,null); decr(ref_count(macro_ref)); +get_x_next; goto restart; +end + +@ Our remaining job is simply to make a copy of the value that has been +found. Some cases are harder than others, but complexity arises solely +because of the multiplicity of possible cases. + +@<Declare the procedure called |make_exp_copy|@>= +@t\4@>@<Declare subroutines needed by |make_exp_copy|@>@; +procedure make_exp_copy(@!p:pointer); +label restart; +var @!q,@!r,@!t:pointer; {registers for list manipulation} +begin restart: cur_type:=type(p); +case cur_type of +vacuous,boolean_type,known:cur_exp:=value(p); +unknown_types:cur_exp:=new_ring_entry(p); +string_type:begin cur_exp:=value(p); add_str_ref(cur_exp); + end; +picture_type:begin cur_exp:=value(p);add_edge_ref(cur_exp); + end; +pen_type:cur_exp:=copy_pen(value(p)); +path_type:cur_exp:=copy_path(value(p)); +transform_type,color_type,pair_type:@<Copy the big node |p|@>; +dependent,proto_dependent:encapsulate(copy_dep_list(dep_list(p))); +numeric_type:begin new_indep(p); goto restart; + end; +independent: begin q:=single_dependency(p); + if q=dep_final then + begin cur_type:=known; cur_exp:=0; free_node(q,value_node_size); + end + else begin cur_type:=dependent; encapsulate(q); + end; + end; +othercases confusion("copy") +@:this can't happen copy}{\quad copy@> +endcases; +end; + +@ The |encapsulate| subroutine assumes that |dep_final| is the +tail of dependency list~|p|. + +@<Declare subroutines needed by |make_exp_copy|@>= +procedure encapsulate(@!p:pointer); +begin cur_exp:=get_node(value_node_size); type(cur_exp):=cur_type; +name_type(cur_exp):=capsule; new_dep(cur_exp,p); +end; + +@ The most tedious case arises when the user refers to a +\&{pair}, \&{color}, or \&{transform} variable; we must copy several fields, +each of which can be |independent|, |dependent|, |proto_dependent|, +or |known|. + +@<Copy the big node |p|@>= +begin if value(p)=null then init_big_node(p); +t:=get_node(value_node_size); name_type(t):=capsule; type(t):=cur_type; +init_big_node(t);@/ +q:=value(p)+big_node_size[cur_type]; r:=value(t)+big_node_size[cur_type]; +repeat q:=q-2; r:=r-2; install(r,q); +until q=value(p); +cur_exp:=t; +end + +@ The |install| procedure copies a numeric field~|q| into field~|r| of +a big node that will be part of a capsule. + +@<Declare subroutines needed by |make_exp_copy|@>= +procedure install(@!r,@!q:pointer); +var p:pointer; {temporary register} +begin if type(q)=known then + begin value(r):=value(q); type(r):=known; + end +else if type(q)=independent then + begin p:=single_dependency(q); + if p=dep_final then + begin type(r):=known; value(r):=0; free_node(p,value_node_size); + end + else begin type(r):=dependent; new_dep(r,p); + end; + end + else begin type(r):=type(q); new_dep(r,copy_dep_list(dep_list(q))); + end; +end; + +@ Expressions of the form `\.{a[b,c]}' are converted into +`\.{b+a*(c-b)}', without checking the types of \.b~or~\.c, +provided that \.a is numeric. + +@<Scan a mediation...@>= +begin p:=stash_cur_exp; get_x_next; scan_expression; +if cur_cmd<>comma then + begin @<Put the left bracket and the expression back...@>; + unstash_cur_exp(p); + end +else begin q:=stash_cur_exp; get_x_next; scan_expression; + if cur_cmd<>right_bracket then + begin missing_err("]");@/ +@.Missing `]'@> + help3("I've scanned an expression of the form `a[b,c',")@/ + ("so a right bracket should have come next.")@/ + ("I shall pretend that one was there.");@/ + back_error; + end; + r:=stash_cur_exp; make_exp_copy(q);@/ + do_binary(r,minus); do_binary(p,times); do_binary(q,plus); get_x_next; + end; +end + +@ Here is a comparatively simple routine that is used to scan the +\&{suffix} parameters of a macro. + +@<Declare the basic parsing subroutines@>= +procedure scan_suffix; +label done; +var @!h,@!t:pointer; {head and tail of the list being built} +@!p:pointer; {temporary register} +begin h:=get_avail; t:=h; +loop@+ begin if cur_cmd=left_bracket then + @<Scan a bracketed subscript and set |cur_cmd:=numeric_token|@>; + if cur_cmd=numeric_token then p:=new_num_tok(cur_mod) + else if (cur_cmd=tag_token)or(cur_cmd=internal_quantity) then + begin p:=get_avail; info(p):=cur_sym; + end + else goto done; + link(t):=p; t:=p; get_x_next; + end; +done: cur_exp:=link(h); free_avail(h); cur_type:=token_list; +end; + +@ @<Scan a bracketed subscript and set |cur_cmd:=numeric_token|@>= +begin get_x_next; scan_expression; +if cur_type<>known then bad_subscript; +if cur_cmd<>right_bracket then + begin missing_err("]");@/ +@.Missing `]'@> + help3("I've seen a `[' and a subscript value, in a suffix,")@/ + ("so a right bracket should have come next.")@/ + ("I shall pretend that one was there.");@/ + back_error; + end; +cur_cmd:=numeric_token; cur_mod:=cur_exp; +end + +@* \[38] Parsing secondary and higher expressions. +After the intricacies of |scan_primary|\kern-1pt, +the |scan_secondary| routine is +refreshingly simple. It's not trivial, but the operations are relatively +straightforward; the main difficulty is, again, that expressions and data +structures might change drastically every time we call |get_x_next|, so a +cautious approach is mandatory. For example, a macro defined by +\&{primarydef} might have disappeared by the time its second argument has +been scanned; we solve this by increasing the reference count of its token +list, so that the macro can be called even after it has been clobbered. + +@<Declare the basic parsing subroutines@>= +procedure scan_secondary; +label restart,continue; +var @!p:pointer; {for list manipulation} +@!c,@!d:halfword; {operation codes or modifiers} +@!mac_name:pointer; {token defined with \&{primarydef}} +begin restart:if(cur_cmd<min_primary_command)or@| + (cur_cmd>max_primary_command) then + bad_exp("A secondary"); +@.A secondary expression...@> +scan_primary; +continue: if cur_cmd<=max_secondary_command then + if cur_cmd>=min_secondary_command then + begin p:=stash_cur_exp; c:=cur_mod; d:=cur_cmd; + if d=secondary_primary_macro then + begin mac_name:=cur_sym; add_mac_ref(c); + end; + get_x_next; scan_primary; + if d<>secondary_primary_macro then do_binary(p,c) + else begin back_input; binary_mac(p,c,mac_name); + decr(ref_count(c)); get_x_next; goto restart; + end; + goto continue; + end; +end; + +@ The following procedure calls a macro that has two parameters, +|p| and |cur_exp|. + +@p procedure binary_mac(@!p,@!c,@!n:pointer); +var @!q,@!r:pointer; {nodes in the parameter list} +begin q:=get_avail; r:=get_avail; link(q):=r;@/ +info(q):=p; info(r):=stash_cur_exp;@/ +macro_call(c,q,n); +end; + +@ The next procedure, |scan_tertiary|, is pretty much the same deal. + +@<Declare the basic parsing subroutines@>= +procedure scan_tertiary; +label restart,continue; +var @!p:pointer; {for list manipulation} +@!c,@!d:halfword; {operation codes or modifiers} +@!mac_name:pointer; {token defined with \&{secondarydef}} +begin restart:if(cur_cmd<min_primary_command)or@| + (cur_cmd>max_primary_command) then + bad_exp("A tertiary"); +@.A tertiary expression...@> +scan_secondary; +continue: if cur_cmd<=max_tertiary_command then + if cur_cmd>=min_tertiary_command then + begin p:=stash_cur_exp; c:=cur_mod; d:=cur_cmd; + if d=tertiary_secondary_macro then + begin mac_name:=cur_sym; add_mac_ref(c); + end; + get_x_next; scan_secondary; + if d<>tertiary_secondary_macro then do_binary(p,c) + else begin back_input; binary_mac(p,c,mac_name); + decr(ref_count(c)); get_x_next; goto restart; + end; + goto continue; + end; +end; + +@ Finally we reach the deepest level in our quartet of parsing routines. +This one is much like the others; but it has an extra complication from +paths, which materialize here. + +@d continue_path=25 {a label inside of |scan_expression|} +@d finish_path=26 {another} + +@<Declare the basic parsing subroutines@>= +procedure scan_expression; +label restart,done,continue,continue_path,finish_path,exit; +var @!p,@!q,@!r,@!pp,@!qq:pointer; {for list manipulation} +@!c,@!d:halfword; {operation codes or modifiers} +@!my_var_flag:0..max_command_code; {initial value of |var_flag|} +@!mac_name:pointer; {token defined with \&{tertiarydef}} +@!cycle_hit:boolean; {did a path expression just end with `\&{cycle}'?} +@!x,@!y:scaled; {explicit coordinates or tension at a path join} +@!t:endpoint..open; {knot type following a path join} +begin my_var_flag:=var_flag; +restart:if(cur_cmd<min_primary_command)or@| + (cur_cmd>max_primary_command) then + bad_exp("An"); +@.An expression...@> +scan_tertiary; +continue: if cur_cmd<=max_expression_command then + if cur_cmd>=min_expression_command then + if (cur_cmd<>equals)or(my_var_flag<>assignment) then + begin p:=stash_cur_exp; c:=cur_mod; d:=cur_cmd; + if d=expression_tertiary_macro then + begin mac_name:=cur_sym; add_mac_ref(c); + end; + if (d<ampersand)or((d=ampersand)and@| + ((type(p)=pair_type)or(type(p)=path_type))) then + @<Scan a path construction operation; + but |return| if |p| has the wrong type@> + else begin get_x_next; scan_tertiary; + if d<>expression_tertiary_macro then do_binary(p,c) + else begin back_input; binary_mac(p,c,mac_name); + decr(ref_count(c)); get_x_next; goto restart; + end; + end; + goto continue; + end; +exit:end; + +@ The reader should review the data structure conventions for paths before +hoping to understand the next part of this code. + +@<Scan a path construction operation...@>= +begin cycle_hit:=false; +@<Convert the left operand, |p|, into a partial path ending at~|q|; + but |return| if |p| doesn't have a suitable type@>; +continue_path: @<Determine the path join parameters; + but |goto finish_path| if there's only a direction specifier@>; +if cur_cmd=cycle then @<Get ready to close a cycle@> +else begin scan_tertiary; + @<Convert the right operand, |cur_exp|, + into a partial path from |pp| to~|qq|@>; + end; +@<Join the partial paths and reset |p| and |q| to the head and tail + of the result@>; +if cur_cmd>=min_expression_command then + if cur_cmd<=ampersand then if not cycle_hit then goto continue_path; +finish_path: +@<Choose control points for the path and put the result into |cur_exp|@>; +end + +@ @<Convert the left operand, |p|, into a partial path ending at~|q|...@>= +begin unstash_cur_exp(p); +if cur_type=pair_type then p:=new_knot +else if cur_type=path_type then p:=cur_exp +else return; +q:=p; +while link(q)<>p do q:=link(q); +if left_type(p)<>endpoint then {open up a cycle} + begin r:=copy_knot(p); link(q):=r; q:=r; + end; +left_type(p):=open; right_type(q):=open; +end + +@ A pair of numeric values is changed into a knot node for a one-point path +when \MP\ discovers that the pair is part of a path. + +@p@t\4@>@<Declare the procedure called |known_pair|@>@; +function new_knot:pointer; {convert a pair to a knot with two endpoints} +var @!q:pointer; {the new node} +begin q:=get_node(knot_node_size); left_type(q):=endpoint; +right_type(q):=endpoint; originator(q):=metapost_user; link(q):=q;@/ +known_pair; x_coord(q):=cur_x; y_coord(q):=cur_y; +new_knot:=q; +end; + +@ The |known_pair| subroutine sets |cur_x| and |cur_y| to the components +of the current expression, assuming that the current expression is a +pair of known numerics. Unknown components are zeroed, and the +current expression is flushed. + +@<Declare the procedure called |known_pair|@>= +procedure known_pair; +var @!p:pointer; {the pair node} +begin if cur_type<>pair_type then + begin exp_err("Undefined coordinates have been replaced by (0,0)"); +@.Undefined coordinates...@> + help5("I need x and y numbers for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + ("you might want to type `I ???' now.)"); + put_get_flush_error(0); cur_x:=0; cur_y:=0; + end +else begin p:=value(cur_exp); + @<Make sure that both |x| and |y| parts of |p| are known; + copy them into |cur_x| and |cur_y|@>; + flush_cur_exp(0); + end; +end; + +@ @<Make sure that both |x| and |y| parts of |p| are known...@>= +if type(x_part_loc(p))=known then cur_x:=value(x_part_loc(p)) +else begin disp_err(x_part_loc(p), + "Undefined x coordinate has been replaced by 0"); +@.Undefined coordinates...@> + help5("I need a `known' x value for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + ("you might want to type `I ???' now.)"); + put_get_error; recycle_value(x_part_loc(p)); cur_x:=0; + end; +if type(y_part_loc(p))=known then cur_y:=value(y_part_loc(p)) +else begin disp_err(y_part_loc(p), + "Undefined y coordinate has been replaced by 0"); + help5("I need a `known' y value for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ + ("you might want to type `I ???' now.)"); + put_get_error; recycle_value(y_part_loc(p)); cur_y:=0; + end + +@ At this point |cur_cmd| is either |ampersand|, |left_brace|, or |path_join|. + +@<Determine the path join parameters...@>= +if cur_cmd=left_brace then + @<Put the pre-join direction information into node |q|@>; +d:=cur_cmd; +if d=path_join then @<Determine the tension and/or control points@> +else if d<>ampersand then goto finish_path; +get_x_next; +if cur_cmd=left_brace then + @<Put the post-join direction information into |x| and |t|@> +else if right_type(q)<>explicit then + begin t:=open; x:=0; + end + +@ The |scan_direction| subroutine looks at the directional information +that is enclosed in braces, and also scans ahead to the following character. +A type code is returned, either |open| (if the direction was $(0,0)$), +or |curl| (if the direction was a curl of known value |cur_exp|), or +|given| (if the direction is given by the |angle| value that now +appears in |cur_exp|). + +There's nothing difficult about this subroutine, but the program is rather +lengthy because a variety of potential errors need to be nipped in the bud. + +@p function scan_direction:small_number; +var @!t:given..open; {the type of information found} +@!x:scaled; {an |x| coordinate} +begin get_x_next; +if cur_cmd=curl_command then @<Scan a curl specification@> +else @<Scan a given direction@>; +if cur_cmd<>right_brace then + begin missing_err("}");@/ +@.Missing `\char`\}'@> + help3("I've scanned a direction spec for part of a path,")@/ + ("so a right brace should have come next.")@/ + ("I shall pretend that one was there.");@/ + back_error; + end; +get_x_next; scan_direction:=t; +end; + +@ @<Scan a curl specification@>= +begin get_x_next; scan_expression; +if (cur_type<>known)or(cur_exp<0) then + begin exp_err("Improper curl has been replaced by 1"); +@.Improper curl@> + help1("A curl must be a known, nonnegative number."); + put_get_flush_error(unity); + end; +t:=curl; +end + +@ @<Scan a given direction@>= +begin scan_expression; +if cur_type>pair_type then @<Get given directions separated by commas@> +else known_pair; +if (cur_x=0)and(cur_y=0) then t:=open +else begin t:=given; cur_exp:=n_arg(cur_x,cur_y); + end; +end + +@ @<Get given directions separated by commas@>= +begin if cur_type<>known then + begin exp_err("Undefined x coordinate has been replaced by 0"); +@.Undefined coordinates...@> + help5("I need a `known' x value for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + ("you might want to type `I ???' now.)"); + put_get_flush_error(0); + end; +x:=cur_exp; +if cur_cmd<>comma then + begin missing_err(",");@/ +@.Missing `,'@> + help2("I've got the x coordinate of a path direction;")@/ + ("will look for the y coordinate next."); + back_error; + end; +get_x_next; scan_expression; +if cur_type<>known then + begin exp_err("Undefined y coordinate has been replaced by 0"); + help5("I need a `known' y value for this part of the path.")@/ + ("The value I found (see above) was no good;")@/ + ("so I'll try to keep going by using zero instead.")@/ + ("(Chapter 27 of The METAFONTbook explains that")@/ + ("you might want to type `I ???' now.)"); + put_get_flush_error(0); + end; +cur_y:=cur_exp; cur_x:=x; +end + +@ At this point |right_type(q)| is usually |open|, but it may have been +set to some other value by a previous splicing operation. We must maintain +the value of |right_type(q)| in unusual cases such as +`\.{..z1\{z2\}\&\{z3\}z1\{0,0\}..}'. + +@<Put the pre-join...@>= +begin t:=scan_direction; +if t<>open then + begin right_type(q):=t; right_given(q):=cur_exp; + if left_type(q)=open then + begin left_type(q):=t; left_given(q):=cur_exp; + end; {note that |left_given(q)=left_curl(q)|} + end; +end + +@ Since |left_tension| and |left_y| share the same position in knot nodes, +and since |left_given| is similarly equivalent to |left_x|, we use +|x| and |y| to hold the given direction and tension information when +there are no explicit control points. + +@<Put the post-join...@>= +begin t:=scan_direction; +if right_type(q)<>explicit then x:=cur_exp +else t:=explicit; {the direction information is superfluous} +end + +@ @<Determine the tension and/or...@>= +begin get_x_next; +if cur_cmd=tension then @<Set explicit tensions@> +else if cur_cmd=controls then @<Set explicit control points@> +else begin right_tension(q):=unity; y:=unity; back_input; {default tension} + goto done; + end; +if cur_cmd<>path_join then + begin missing_err("..");@/ +@.Missing `..'@> + help1("A path join command should end with two dots."); + back_error; + end; +done:end + +@ @<Set explicit tensions@>= +begin get_x_next; y:=cur_cmd; +if cur_cmd=at_least then get_x_next; +scan_primary; +@<Make sure that the current expression is a valid tension setting@>; +if y=at_least then negate(cur_exp); +right_tension(q):=cur_exp; +if cur_cmd=and_command then + begin get_x_next; y:=cur_cmd; + if cur_cmd=at_least then get_x_next; + scan_primary; + @<Make sure that the current expression is a valid tension setting@>; + if y=at_least then negate(cur_exp); + end; +y:=cur_exp; +end + +@ @d min_tension==three_quarter_unit + +@<Make sure that the current expression is a valid tension setting@>= +if (cur_type<>known)or(cur_exp<min_tension) then + begin exp_err("Improper tension has been set to 1"); +@.Improper tension@> + help1("The expression above should have been a number >=3/4."); + put_get_flush_error(unity); + end + +@ @<Set explicit control points@>= +begin right_type(q):=explicit; t:=explicit; get_x_next; scan_primary;@/ +known_pair; right_x(q):=cur_x; right_y(q):=cur_y; +if cur_cmd<>and_command then + begin x:=right_x(q); y:=right_y(q); + end +else begin get_x_next; scan_primary;@/ + known_pair; x:=cur_x; y:=cur_y; + end; +end + +@ @<Convert the right operand, |cur_exp|, into a partial path...@>= +begin if cur_type<>path_type then pp:=new_knot +else pp:=cur_exp; +qq:=pp; +while link(qq)<>pp do qq:=link(qq); +if left_type(pp)<>endpoint then {open up a cycle} + begin r:=copy_knot(pp); link(qq):=r; qq:=r; + end; +left_type(pp):=open; right_type(qq):=open; +end + +@ If a person tries to define an entire path by saying `\.{(x,y)\&cycle}', +we silently change the specification to `\.{(x,y)..cycle}', since a cycle +shouldn't have length zero. + +@<Get ready to close a cycle@>= +begin cycle_hit:=true; get_x_next; pp:=p; qq:=p; +if d=ampersand then if p=q then + begin d:=path_join; right_tension(q):=unity; y:=unity; + end; +end + +@ @<Join the partial paths and reset |p| and |q|...@>= +begin if d=ampersand then + if (x_coord(q)<>x_coord(pp))or(y_coord(q)<>y_coord(pp)) then + begin print_err("Paths don't touch; `&' will be changed to `..'"); +@.Paths don't touch@> + help3("When you join paths `p&q', the ending point of p")@/ + ("must be exactly equal to the starting point of q.")@/ + ("So I'm going to pretend that you said `p..q' instead."); + put_get_error; d:=path_join; right_tension(q):=unity; y:=unity; + end; +@<Plug an opening in |right_type(pp)|, if possible@>; +if d=ampersand then @<Splice independent paths together@> +else begin @<Plug an opening in |right_type(q)|, if possible@>; + link(q):=pp; left_y(pp):=y; + if t<>open then + begin left_x(pp):=x; left_type(pp):=t; + end; + end; +q:=qq; +end + +@ @<Plug an opening in |right_type(q)|...@>= +if right_type(q)=open then + if (left_type(q)=curl)or(left_type(q)=given) then + begin right_type(q):=left_type(q); right_given(q):=left_given(q); + end + +@ @<Plug an opening in |right_type(pp)|...@>= +if right_type(pp)=open then + if (t=curl)or(t=given) then + begin right_type(pp):=t; right_given(pp):=x; + end + +@ @<Splice independent paths together@>= +begin if left_type(q)=open then if right_type(q)=open then + begin left_type(q):=curl; left_curl(q):=unity; + end; +if right_type(pp)=open then if t=open then + begin right_type(pp):=curl; right_curl(pp):=unity; + end; +right_type(q):=right_type(pp); link(q):=link(pp);@/ +right_x(q):=right_x(pp); right_y(q):=right_y(pp); +free_node(pp,knot_node_size); +if qq=pp then qq:=q; +end + +@ @<Choose control points for the path...@>= +if cycle_hit then + begin if d=ampersand then p:=q; + end +else begin left_type(p):=endpoint; + if right_type(p)=open then + begin right_type(p):=curl; right_curl(p):=unity; + end; + right_type(q):=endpoint; + if left_type(q)=open then + begin left_type(q):=curl; left_curl(q):=unity; + end; + link(q):=p; + end; +make_choices(p); +cur_type:=path_type; cur_exp:=p + +@ Finally, we sometimes need to scan an expression whose value is +supposed to be either |true_code| or |false_code|. + +@<Declare the basic parsing subroutines@>= +procedure get_boolean; +begin get_x_next; scan_expression; +if cur_type<>boolean_type then + begin exp_err("Undefined condition will be treated as `false'"); +@.Undefined condition...@> + help2("The expression shown above should have had a definite")@/ + ("true-or-false value. I'm changing it to `false'.");@/ + put_get_flush_error(false_code); cur_type:=boolean_type; + end; +end; + +@* \[39] Doing the operations. +The purpose of parsing is primarily to permit people to avoid piles of +parentheses. But the real work is done after the structure of an expression +has been recognized; that's when new expressions are generated. We +turn now to the guts of \MP, which handles individual operators that +have come through the parsing mechanism. + +We'll start with the easy ones that take no operands, then work our way +up to operators with one and ultimately two arguments. In other words, +we will write the three procedures |do_nullary|, |do_unary|, and |do_binary| +that are invoked periodically by the expression scanners. + +First let's make sure that all of the primitive operators are in the +hash table. Although |scan_primary| and its relatives made use of the +\\{cmd} code for these operators, the \\{do} routines base everything +on the \\{mod} code. For example, |do_binary| doesn't care whether the +operation it performs is a |primary_binary| or |secondary_binary|, etc. + +@<Put each...@>= +primitive("true",nullary,true_code);@/ +@!@:true_}{\&{true} primitive@> +primitive("false",nullary,false_code);@/ +@!@:false_}{\&{false} primitive@> +primitive("nullpicture",nullary,null_picture_code);@/ +@!@:null_picture_}{\&{nullpicture} primitive@> +primitive("nullpen",nullary,null_pen_code);@/ +@!@:null_pen_}{\&{nullpen} primitive@> +primitive("jobname",nullary,job_name_op);@/ +@!@:job_name_}{\&{jobname} primitive@> +primitive("readstring",nullary,read_string_op);@/ +@!@:read_string_}{\&{readstring} primitive@> +primitive("pencircle",nullary,pen_circle);@/ +@!@:pen_circle_}{\&{pencircle} primitive@> +primitive("normaldeviate",nullary,normal_deviate);@/ +@!@:normal_deviate_}{\&{normaldeviate} primitive@> +primitive("readfrom",unary,read_from_op);@/ +@!@:read_from_}{\&{readfrom} primitive@> +primitive("closefrom",unary,close_from_op);@/ +@!@:close_from_}{\&{closefrom} primitive@> +primitive("odd",unary,odd_op);@/ +@!@:odd_}{\&{odd} primitive@> +primitive("known",unary,known_op);@/ +@!@:known_}{\&{known} primitive@> +primitive("unknown",unary,unknown_op);@/ +@!@:unknown_}{\&{unknown} primitive@> +primitive("not",unary,not_op);@/ +@!@:not_}{\&{not} primitive@> +primitive("decimal",unary,decimal);@/ +@!@:decimal_}{\&{decimal} primitive@> +primitive("reverse",unary,reverse);@/ +@!@:reverse_}{\&{reverse} primitive@> +primitive("makepath",unary,make_path_op);@/ +@!@:make_path_}{\&{makepath} primitive@> +primitive("makepen",unary,make_pen_op);@/ +@!@:make_pen_}{\&{makepen} primitive@> +primitive("oct",unary,oct_op);@/ +@!@:oct_}{\&{oct} primitive@> +primitive("hex",unary,hex_op);@/ +@!@:hex_}{\&{hex} primitive@> +primitive("ASCII",unary,ASCII_op);@/ +@!@:ASCII_}{\&{ASCII} primitive@> +primitive("char",unary,char_op);@/ +@!@:char_}{\&{char} primitive@> +primitive("length",unary,length_op);@/ +@!@:length_}{\&{length} primitive@> +primitive("turningnumber",unary,turning_op);@/ +@!@:turning_number_}{\&{turningnumber} primitive@> +primitive("xpart",unary,x_part);@/ +@!@:x_part_}{\&{xpart} primitive@> +primitive("ypart",unary,y_part);@/ +@!@:y_part_}{\&{ypart} primitive@> +primitive("xxpart",unary,xx_part);@/ +@!@:xx_part_}{\&{xxpart} primitive@> +primitive("xypart",unary,xy_part);@/ +@!@:xy_part_}{\&{xypart} primitive@> +primitive("yxpart",unary,yx_part);@/ +@!@:yx_part_}{\&{yxpart} primitive@> +primitive("yypart",unary,yy_part);@/ +@!@:yy_part_}{\&{yypart} primitive@> +primitive("redpart",unary,red_part);@/ +@!@:red_part_}{\&{redpart} primitive@> +primitive("greenpart",unary,green_part);@/ +@!@:green_part_}{\&{greenpart} primitive@> +primitive("bluepart",unary,blue_part);@/ +@!@:blue_part_}{\&{bluepart} primitive@> +primitive("fontpart",unary,font_part);@/ +@!@:font_part_}{\&{fontpart} primitive@> +primitive("textpart",unary,text_part);@/ +@!@:text_part_}{\&{textpart} primitive@> +primitive("pathpart",unary,path_part);@/ +@!@:path_part_}{\&{pathpart} primitive@> +primitive("penpart",unary,pen_part);@/ +@!@:pen_part_}{\&{penpart} primitive@> +primitive("dashpart",unary,dash_part);@/ +@!@:dash_part_}{\&{dashpart} primitive@> +primitive("sqrt",unary,sqrt_op);@/ +@!@:sqrt_}{\&{sqrt} primitive@> +primitive("mexp",unary,m_exp_op);@/ +@!@:m_exp_}{\&{mexp} primitive@> +primitive("mlog",unary,m_log_op);@/ +@!@:m_log_}{\&{mlog} primitive@> +primitive("sind",unary,sin_d_op);@/ +@!@:sin_d_}{\&{sind} primitive@> +primitive("cosd",unary,cos_d_op);@/ +@!@:cos_d_}{\&{cosd} primitive@> +primitive("floor",unary,floor_op);@/ +@!@:floor_}{\&{floor} primitive@> +primitive("uniformdeviate",unary,uniform_deviate);@/ +@!@:uniform_deviate_}{\&{uniformdeviate} primitive@> +primitive("charexists",unary,char_exists_op);@/ +@!@:char_exists_}{\&{charexists} primitive@> +primitive("fontsize",unary,font_size);@/ +@!@:font_size_}{\&{fontsize} primitive@> +primitive("llcorner",unary,ll_corner_op);@/ +@!@:ll_corner_}{\&{llcorner} primitive@> +primitive("lrcorner",unary,lr_corner_op);@/ +@!@:lr_corner_}{\&{lrcorner} primitive@> +primitive("ulcorner",unary,ul_corner_op);@/ +@!@:ul_corner_}{\&{ulcorner} primitive@> +primitive("urcorner",unary,ur_corner_op);@/ +@!@:ur_corner_}{\&{urcorner} primitive@> +primitive("arclength",unary,arc_length);@/ +@!@:arc_length_}{\&{arclength} primitive@> +primitive("angle",unary,angle_op);@/ +@!@:angle_}{\&{angle} primitive@> +primitive("cycle",cycle,cycle_op);@/ +@!@:cycle_}{\&{cycle} primitive@> +primitive("stroked",unary,stroked_op);@/ +@!@:stroked_}{\&{stroked} primitive@> +primitive("filled",unary,filled_op);@/ +@!@:filled_}{\&{filled} primitive@> +primitive("textual",unary,textual_op);@/ +@!@:textual_}{\&{textual} primitive@> +primitive("clipped",unary,clipped_op);@/ +@!@:clipped_}{\&{clipped} primitive@> +primitive("bounded",unary,bounded_op);@/ +@!@:bounded_}{\&{bounded} primitive@> +primitive("+",plus_or_minus,plus);@/ +@!@:+ }{\.{+} primitive@> +primitive("-",plus_or_minus,minus);@/ +@!@:- }{\.{-} primitive@> +primitive("*",secondary_binary,times);@/ +@!@:* }{\.{*} primitive@> +primitive("/",slash,over); eqtb[frozen_slash]:=eqtb[cur_sym];@/ +@!@:/ }{\.{/} primitive@> +primitive("++",tertiary_binary,pythag_add);@/ +@!@:++_}{\.{++} primitive@> +primitive("+-+",tertiary_binary,pythag_sub);@/ +@!@:+-+_}{\.{+-+} primitive@> +primitive("or",tertiary_binary,or_op);@/ +@!@:or_}{\&{or} primitive@> +primitive("and",and_command,and_op);@/ +@!@:and_}{\&{and} primitive@> +primitive("<",expression_binary,less_than);@/ +@!@:< }{\.{<} primitive@> +primitive("<=",expression_binary,less_or_equal);@/ +@!@:<=_}{\.{<=} primitive@> +primitive(">",expression_binary,greater_than);@/ +@!@:> }{\.{>} primitive@> +primitive(">=",expression_binary,greater_or_equal);@/ +@!@:>=_}{\.{>=} primitive@> +primitive("=",equals,equal_to);@/ +@!@:= }{\.{=} primitive@> +primitive("<>",expression_binary,unequal_to);@/ +@!@:<>_}{\.{<>} primitive@> +primitive("substring",primary_binary,substring_of);@/ +@!@:substring_}{\&{substring} primitive@> +primitive("subpath",primary_binary,subpath_of);@/ +@!@:subpath_}{\&{subpath} primitive@> +primitive("directiontime",primary_binary,direction_time_of);@/ +@!@:direction_time_}{\&{directiontime} primitive@> +primitive("point",primary_binary,point_of);@/ +@!@:point_}{\&{point} primitive@> +primitive("precontrol",primary_binary,precontrol_of);@/ +@!@:precontrol_}{\&{precontrol} primitive@> +primitive("postcontrol",primary_binary,postcontrol_of);@/ +@!@:postcontrol_}{\&{postcontrol} primitive@> +primitive("penoffset",primary_binary,pen_offset_of);@/ +@!@:pen_offset_}{\&{penoffset} primitive@> +primitive("arctime",primary_binary,arc_time_of);@/ +@!@:arc_time_of_}{\&{arctime} primitive@> +primitive("mpversion",nullary,mp_version);@/ +@!@:mp_verison_}{\&{mpversion} primitive@> +primitive("&",ampersand,concatenate);@/ +@!@:!!!}{\.{\&} primitive@> +primitive("rotated",secondary_binary,rotated_by);@/ +@!@:rotated_}{\&{rotated} primitive@> +primitive("slanted",secondary_binary,slanted_by);@/ +@!@:slanted_}{\&{slanted} primitive@> +primitive("scaled",secondary_binary,scaled_by);@/ +@!@:scaled_}{\&{scaled} primitive@> +primitive("shifted",secondary_binary,shifted_by);@/ +@!@:shifted_}{\&{shifted} primitive@> +primitive("transformed",secondary_binary,transformed_by);@/ +@!@:transformed_}{\&{transformed} primitive@> +primitive("xscaled",secondary_binary,x_scaled);@/ +@!@:x_scaled_}{\&{xscaled} primitive@> +primitive("yscaled",secondary_binary,y_scaled);@/ +@!@:y_scaled_}{\&{yscaled} primitive@> +primitive("zscaled",secondary_binary,z_scaled);@/ +@!@:z_scaled_}{\&{zscaled} primitive@> +primitive("infont",secondary_binary,in_font);@/ +@!@:in_font_}{\&{infont} primitive@> +primitive("intersectiontimes",tertiary_binary,intersect);@/ +@!@:intersection_times_}{\&{intersectiontimes} primitive@> + +@ @<Cases of |print_cmd...@>= +nullary,unary,primary_binary,secondary_binary,tertiary_binary, + expression_binary,cycle,plus_or_minus,slash,ampersand,equals,and_command: + print_op(m); + +@ OK, let's look at the simplest \\{do} procedure first. + +@p @t\4@>@<Declare nullary action procedure@>@; +procedure do_nullary(@!c:quarterword); +begin check_arith; +if internal[tracing_commands]>two then + show_cmd_mod(nullary,c); +case c of +true_code,false_code:begin cur_type:=boolean_type; cur_exp:=c; + end; +null_picture_code:begin cur_type:=picture_type; + cur_exp:=get_node(edge_header_size); init_edges(cur_exp); + end; +null_pen_code:begin cur_type:=pen_type; cur_exp:=get_pen_circle(0); + end; +normal_deviate:begin cur_type:=known; cur_exp:=norm_rand; + end; +pen_circle:begin cur_type:=pen_type; cur_exp:=get_pen_circle(unity); + end; +job_name_op: begin if job_name=0 then open_log_file; + cur_type:=string_type; cur_exp:=job_name; + end; +mp_version: begin cur_type:=string_type; cur_exp:=metapost_version; end; +read_string_op:@<Read a string from the terminal@>; +end; {there are no other cases} +check_arith; +end; + +@ @<Read a string...@>= +begin if interaction<=nonstop_mode then + fatal_error("*** (cannot readstring in nonstop modes)"); +begin_file_reading; name:=is_read; +limit:=start; prompt_input(""); +finish_read; +end + +@ @<Declare nullary action procedure@>= +procedure finish_read; {copy |buffer| line to |cur_exp|} +var @!k:pool_pointer; +begin str_room(last-start); +for k:=start to last-1 do append_char(buffer[k]); +end_file_reading; cur_type:=string_type; cur_exp:=make_string; +end; + +@ Things get a bit more interesting when there's an operand. The +operand to |do_unary| appears in |cur_type| and |cur_exp|. + +@p @t\4@>@<Declare unary action procedures@>@; +procedure do_unary(@!c:quarterword); +var @!p,@!q,@!r:pointer; {for list manipulation} +@!x:integer; {a temporary register} +begin check_arith; +if internal[tracing_commands]>two then + @<Trace the current unary operation@>; +case c of +plus:if cur_type<color_type then bad_unary(plus); +minus:@<Negate the current expression@>; +@t\4@>@<Additional cases of unary operators@>@; +end; {there are no other cases} +check_arith; +end; + +@ The |nice_pair| function returns |true| if both components of a pair +are known. + +@<Declare unary action procedures@>= +function nice_pair(@!p:integer;@!t:quarterword):boolean; +label exit; +begin if t=pair_type then + begin p:=value(p); + if type(x_part_loc(p))=known then + if type(y_part_loc(p))=known then + begin nice_pair:=true; return; + end; + end; +nice_pair:=false; +exit:end; + +@ The |nice_color_or_pair| function is analogous except that it also accepts +fully known colors. + +@<Declare unary action procedures@>= +function nice_color_or_pair(@!p:integer;@!t:quarterword):boolean; +label exit; +var @!q,@!r:pointer; {for scanning the big node} +begin if (t<>pair_type)and(t<>color_type) then + nice_color_or_pair:=false +else begin q:=value(p); + r:=q+big_node_size[type(p)]; + repeat r:=r-2; + if type(r)<>known then + begin nice_color_or_pair:=false; return; + end; + until r=q; + nice_color_or_pair:=true; + end; +exit:end; + +@ @<Declare unary action...@>= +procedure print_known_or_unknown_type(@!t:small_number;@!v:integer); +begin print_char("("); +if t>known then print("unknown numeric") +else begin if (t=pair_type)or(t=color_type) then + if not nice_color_or_pair(v,t) then print("unknown "); + print_type(t); + end; +print_char(")"); +end; + +@ @<Declare unary action...@>= +procedure bad_unary(@!c:quarterword); +begin exp_err("Not implemented: "); print_op(c); +@.Not implemented...@> +print_known_or_unknown_type(cur_type,cur_exp); +help3("I'm afraid I don't know how to apply that operation to that")@/ + ("particular type. Continue, and I'll simply return the")@/ + ("argument (shown above) as the result of the operation."); +put_get_error; +end; + +@ @<Trace the current unary operation@>= +begin begin_diagnostic; print_nl("{"); print_op(c); print_char("(");@/ +print_exp(null,0); {show the operand, but not verbosely} +print(")}"); end_diagnostic(false); +end + +@ Negation is easy except when the current expression +is of type |independent|, or when it is a pair with one or more +|independent| components. + +It is tempting to argue that the negative of an independent variable +is an independent variable, hence we don't have to do anything when +negating it. The fallacy is that other dependent variables pointing +to the current expression must change the sign of their +coefficients if we make no change to the current expression. + +Instead, we work around the problem by copying the current expression +and recycling it afterwards (cf.~the |stash_in| routine). + +@<Negate the current expression@>= +case cur_type of +color_type,pair_type,independent: begin q:=cur_exp; make_exp_copy(q); + if cur_type=dependent then negate_dep_list(dep_list(cur_exp)) + else if cur_type<=pair_type then {|color_type| or |pair_type|} + begin p:=value(cur_exp); + r:=p+big_node_size[cur_type]; + repeat r:=r-2; + if type(r)=known then negate(value(r)) + else negate_dep_list(dep_list(r)); + until r=p; + end; {if |cur_type=known| then |cur_exp=0|} + recycle_value(q); free_node(q,value_node_size); + end; +dependent,proto_dependent:negate_dep_list(dep_list(cur_exp)); +known:negate(cur_exp); +othercases bad_unary(minus) +endcases + +@ @<Declare unary action...@>= +procedure negate_dep_list(@!p:pointer); +label exit; +begin loop@+begin negate(value(p)); + if info(p)=null then return; + p:=link(p); + end; +exit:end; + +@ @<Additional cases of unary operators@>= +not_op: if cur_type<>boolean_type then bad_unary(not_op) + else cur_exp:=true_code+false_code-cur_exp; + +@ @d three_sixty_units==23592960 {that's |360*unity|} +@d boolean_reset(#)==if # then cur_exp:=true_code@+else cur_exp:=false_code + +@<Additional cases of unary operators@>= +sqrt_op,m_exp_op,m_log_op,sin_d_op,cos_d_op,floor_op, + uniform_deviate,odd_op,char_exists_op:@t@>@;@/ + if cur_type<>known then bad_unary(c) + else case c of + sqrt_op:cur_exp:=square_rt(cur_exp); + m_exp_op:cur_exp:=m_exp(cur_exp); + m_log_op:cur_exp:=m_log(cur_exp); + sin_d_op,cos_d_op:begin n_sin_cos((cur_exp mod three_sixty_units)*16); + if c=sin_d_op then cur_exp:=round_fraction(n_sin) + else cur_exp:=round_fraction(n_cos); + end; + floor_op:cur_exp:=floor_scaled(cur_exp); + uniform_deviate:cur_exp:=unif_rand(cur_exp); + odd_op: begin boolean_reset(odd(round_unscaled(cur_exp))); + cur_type:=boolean_type; + end; + char_exists_op:@<Determine if a character has been shipped out@>; + end; {there are no other cases} + +@ @<Additional cases of unary operators@>= +angle_op:if nice_pair(cur_exp,cur_type) then + begin p:=value(cur_exp); + x:=n_arg(value(x_part_loc(p)),value(y_part_loc(p))); + if x>=0 then flush_cur_exp((x+8)div 16) + else flush_cur_exp(-((-x+8)div 16)); + end + else bad_unary(angle_op); + +@ If the current expression is a pair, but the context wants it to +be a path, we call |pair_to_path|. + +@<Declare unary action...@>= +procedure pair_to_path; +begin cur_exp:=new_knot; cur_type:=path_type; +end; + +@ @<Additional cases of unary operators@>= +x_part,y_part:if (cur_type=pair_type)or(cur_type=transform_type) then + take_part(c) + else if cur_type=picture_type then take_pict_part(c) + else bad_unary(c); +xx_part,xy_part,yx_part,yy_part: if cur_type=transform_type then take_part(c) + else if cur_type=picture_type then take_pict_part(c) + else bad_unary(c); +red_part,green_part,blue_part: if cur_type=color_type then take_part(c) + else if cur_type=picture_type then take_pict_part(c) + else bad_unary(c); + +@ In the following procedure, |cur_exp| points to a capsule, which points to +a big node. We want to delete all but one part of the big node. + +@<Declare unary action...@>= +procedure take_part(@!c:quarterword); +var @!p:pointer; {the big node} +begin p:=value(cur_exp); value(temp_val):=p; type(temp_val):=cur_type; +link(p):=temp_val; free_node(cur_exp,value_node_size); +make_exp_copy(p+sector_offset[c+x_part_sector-x_part]); +recycle_value(temp_val); +end; + +@ @<Initialize table entries...@>= +name_type(temp_val):=capsule; + +@ @<Additional cases of unary operators@>= +font_part,text_part,path_part,pen_part,dash_part: + if cur_type=picture_type then take_pict_part(c) + else bad_unary(c); + +@ @<Declare unary action...@>= +procedure@?scale_edges; forward;@t\2@>@;@/ +procedure take_pict_part(@!c:quarterword); +label exit, not_found; +var @!p:pointer; {first graphical object in |cur_exp|} +begin p:=link(dummy_loc(cur_exp)); +if p<>null then + begin case c of + x_part,y_part,xx_part,xy_part,yx_part,yy_part: + if type(p)=text_code then flush_cur_exp(text_trans_part(p+c)) + else goto not_found; + red_part,green_part,blue_part: + if has_color(p) then flush_cur_exp(obj_color_part(p+c)) + else goto not_found; + @<Handle other cases in |take_pict_part| or |goto not_found|@>@; + end; {all cases have been enumerated} + return; + end; +not_found:@<Convert the current expression to a null value appropriate + for |c|@>; +exit:end; + +@ @<Handle other cases in |take_pict_part| or |goto not_found|@>= +text_part: if type(p)<>text_code then goto not_found + else begin flush_cur_exp(text_p(p)); + add_str_ref(cur_exp); + cur_type:=string_type; + end; +font_part: if type(p)<>text_code then goto not_found + else begin flush_cur_exp(font_name[font_n(p)]); + add_str_ref(cur_exp); + cur_type:=string_type; + end; +path_part:if type(p)=text_code then goto not_found + else if is_stop(p) then confusion("pict") +@:this can't happen pict}{\quad pict@> + else begin flush_cur_exp(copy_path(path_p(p))); + cur_type:=path_type; + end; +pen_part: if not has_pen(p) then goto not_found + else if pen_p(p)=null then goto not_found + else begin flush_cur_exp(copy_pen(pen_p(p))); + cur_type:=pen_type; + end; +dash_part: if type(p)<>stroked_code then goto not_found + else if dash_p(p)=null then goto not_found + else begin add_edge_ref(dash_p(p));@/ + se_sf:=dash_scale(p); + se_pic:=dash_p(p); + scale_edges; + flush_cur_exp(se_pic); + cur_type:=picture_type; + end; + +@ Since |scale_edges| had to be declared |forward|, it had to be declared as a +parameterless procedure even though it really takes two arguments and updates +one of them. Hence the following globals are needed. + +@<Global...@>= +@!se_pic:pointer; {edge header used and updated by |scale_edges|} +@!se_sf:scaled; {the scale factor argument to |scale_edges|} + +@ @<Convert the current expression to a null value appropriate...@>= +case c of +text_part,font_part: begin flush_cur_exp(""); + cur_type:=string_type; + end; +path_part: begin flush_cur_exp(get_node(knot_node_size)); + left_type(cur_exp):=endpoint; + right_type(cur_exp):=endpoint; + link(cur_exp):=cur_exp; + x_coord(cur_exp):=0; + y_coord(cur_exp):=0; + originator(cur_exp):=metapost_user; + cur_type:=path_type; + end; +pen_part: begin flush_cur_exp(get_pen_circle(0)); + cur_type:=pen_type; + end; +dash_part: begin flush_cur_exp(get_node(edge_header_size)); + init_edges(cur_exp); + cur_type:=picture_type; + end; +othercases flush_cur_exp(0) +endcases + +@ @<Additional cases of unary...@>= +char_op: if cur_type<>known then bad_unary(char_op) + else begin cur_exp:=round_unscaled(cur_exp) mod 256; cur_type:=string_type; + if cur_exp<0 then cur_exp:=cur_exp+256; + end; +decimal: if cur_type<>known then bad_unary(decimal) + else begin old_setting:=selector; selector:=new_string; + print_scaled(cur_exp); cur_exp:=make_string; + selector:=old_setting; cur_type:=string_type; + end; +oct_op,hex_op,ASCII_op: if cur_type<>string_type then bad_unary(c) + else str_to_num(c); +font_size: if cur_type<>string_type then bad_unary(font_size) + else @<Find the design size of the font whose name is |cur_exp|@>; + +@ @<Declare unary action...@>= +procedure str_to_num(@!c:quarterword); {converts a string to a number} +var @!n:integer; {accumulator} +@!m:ASCII_code; {current character} +@!k:pool_pointer; {index into |str_pool|} +@!b:8..16; {radix of conversion} +@!bad_char:boolean; {did the string contain an invalid digit?} +begin if c=ASCII_op then + if length(cur_exp)=0 then n:=-1 + else n:=so(str_pool[str_start[cur_exp]]) +else begin if c=oct_op then b:=8@+else b:=16; + n:=0; bad_char:=false; + for k:=str_start[cur_exp] to str_stop(cur_exp)-1 do + begin m:=so(str_pool[k]); + if (m>="0")and(m<="9") then m:=m-"0" + else if (m>="A")and(m<="F") then m:=m-"A"+10 + else if (m>="a")and(m<="f") then m:=m-"a"+10 + else begin bad_char:=true; m:=0; + end; + if m>=b then + begin bad_char:=true; m:=0; + end; + if n<32768 div b then n:=n*b+m@+else n:=32767; + end; + @<Give error messages if |bad_char| or |n>=4096|@>; + end; +flush_cur_exp(n*unity); +end; + +@ @<Give error messages if |bad_char|...@>= +if bad_char then + begin exp_err("String contains illegal digits"); +@.String contains illegal digits@> + if c=oct_op then + help1("I zeroed out characters that weren't in the range 0..7.") + else help1("I zeroed out characters that weren't hex digits."); + put_get_error; + end; +if (n>4095) then + if internal[warning_check]>0 then + begin print_err("Number too large ("); print_int(n); print_char(")"); +@.Number too large@> + help2("I have trouble with numbers greater than 4095; watch out.")@/ + ("(Set warningcheck:=0 to suppress this message.)"); + put_get_error; + end + +@ The length operation is somewhat unusual in that it applies to a variety +of different types of operands. + +@<Additional cases of unary...@>= +length_op: case cur_type of + string_type: flush_cur_exp(length(cur_exp)*unity); + path_type: flush_cur_exp(path_length); + known: cur_exp:=abs(cur_exp); + picture_type: flush_cur_exp(pict_length); + othercases if nice_pair(cur_exp,cur_type) then + flush_cur_exp(pyth_add(value(x_part_loc(value(cur_exp))),@| + value(y_part_loc(value(cur_exp))))) + else bad_unary(c) + endcases; + +@ @<Declare unary action...@>= +function path_length:scaled; {computes the length of the current path} +var @!n:scaled; {the path length so far} +@!p:pointer; {traverser} +begin p:=cur_exp; +if left_type(p)=endpoint then n:=-unity@+else n:=0; +repeat p:=link(p); n:=n+unity; +until p=cur_exp; +path_length:=n; +end; + +@ @<Declare unary action...@>= +function pict_length:scaled; {counts interior components in picture |cur_exp|} +label found; +var @!n:scaled; {the count so far} +@!p:pointer; {traverser} +begin n:=0; +p:=link(dummy_loc(cur_exp)); +if p<>null then + begin if is_start_or_stop(p) then + if skip_1component(p)=null then p:=link(p); + while p<>null do + begin skip_component(p)(goto found); + n:=n+unity; + end; + end; +found:pict_length:=n; +end; + +@ The turning number is computed only with respect to a triangular pen whose +@:turning_number_}{\&{turningnumber} primitive@> +vertices are $(0,1)$ and $(\pm{1\over2},0)$. The choice of pen isn't supposed +to matter but rounding error could make a difference if the path has a cusp. + +@<Additional cases of unary...@>= +turning_op:if cur_type=pair_type then flush_cur_exp(0) + else if cur_type<>path_type then bad_unary(turning_op) + else if left_type(cur_exp)=endpoint then + flush_cur_exp(0) {not a cyclic path} + else begin + flush_cur_exp(turn_cycles(cur_exp)); + end; + + +@ This code is based on Bogus\l{}av Jackowski's +|emergency_turningnumber| macro, with some minor changes by Taco +Hoekwater. The macro code looked more like this: +{\obeylines +vardef turning\_number primary p = +~~save res, ang, turns; +~~res := 0; +~~if length p <= 2: +~~~~if Angle ((point 0 of p) - (postcontrol 0 of p)) >= 0: 1 else: -1 fi +~~else: +~~~~for t = 0 upto length p-1 : +~~~~~~angc := Angle ((point t+1 of p) - (point t of p)) +~~~~~~~~- Angle ((point t of p) - (point t-1 of p)); +~~~~~~if angc > 180: angc := angc - 360; fi; +~~~~~~if angc < -180: angc := angc + 360; fi; +~~~~~~res := res + angc; +~~~~endfor; +~~res/360 +~~fi +enddef;} +The general idea is to calculate only the sum of the angles of straight lines between +the points, of a path, not worrying about cusps or self-intersections in the segments +at all. If the segment is not well-behaved, the result is not necesarily correct. But +the old code was not always correct either, and worse, it sometimes failed for well-behaved +paths as well. All known bugs that were triggered by the original code no longer occur +with this code, and it runs roughly 3 times as fast because the algorithm is much simpler. + +@ The macro |Angle()| returns the value of the |angle| primitive, or $0$ if the argument is +|origin|. Converting that calling convention to web code gives the |an_angle| function. + +@<Declare unary action...@>= +function an_angle (@!xpar,@!ypar:scaled):angle; +begin + if (not ((xpar=0) and (ypar=0))) then + an_angle := n_arg(xpar,ypar) + else + an_angle := 0; +end; + + +@ It is possible to overflow the return value of the |turn_cycles| +function when the path is sufficiently long and winding, but I am not +going to bother testing for that. In any case, it would only return +the looped result value, which is not a big problem. + +The macro code for the repeat loop was a bit nicer to look +at than the pascal code, because it could use |point -1 of p|. In +pascal, the fastest way to loop around the path is not to look +backward once, but forward twice. These defines help hide the trick. + +@d p_to==link(link(p)) +@d p_here==link(p) +@d p_from==p + +@<Declare unary action...@>= +function turn_cycles (@!c:pointer):scaled; +var @!res,ang:angle; { the angles of intermediate results } +@!turns:scaled; { the turn counter } +@!p:pointer; { for running around the path } +begin res:=0; turns:= 0; p:=c; +if ((link(p) = p) or (link(link(p)) = p)) then + if an_angle (x_coord(p) - right_x(p), y_coord(p) - right_y(p)) >= 0 then + turn_cycles := unity + else + turn_cycles := -unity +else begin + repeat + ang := an_angle (x_coord(p_to) - x_coord(p_here), y_coord(p_to) - y_coord(p_here)) + - an_angle (x_coord(p_here) - x_coord(p_from), y_coord(p_here) - y_coord(p_from)); + reduce_angle(ang); + res := res + ang; + if res >= three_sixty_deg then begin + res := res - three_sixty_deg; + turns := turns + unity; + end; + if res <= -three_sixty_deg then begin + res := res + three_sixty_deg; + turns := turns - unity; + end; + p := link(p); + until p=c; + turn_cycles := turns; +end; +end; + +@ @<Declare unary action...@>= +function count_turns(@!c:pointer):scaled; +var @!p:pointer; {a knot in envelope spec |c|} +@!t:integer; {total pen offset changes counted} +begin t:=0; p:=c; +repeat t:=t+info(p)-zero_off; +p:=link(p); +until p=c; +count_turns:=(t div 3)*unity; +end; + +@ @d type_test_end== flush_cur_exp(true_code) + else flush_cur_exp(false_code); + cur_type:=boolean_type; + end +@d type_range_end(#)==(cur_type<=#) then type_test_end +@d type_range(#)==begin if (cur_type>=#) and type_range_end +@d type_test(#)==begin if cur_type=# then type_test_end + +@<Additional cases of unary operators@>= +boolean_type: type_range(boolean_type)(unknown_boolean); +string_type: type_range(string_type)(unknown_string); +pen_type: type_range(pen_type)(unknown_pen); +path_type: type_range(path_type)(unknown_path); +picture_type: type_range(picture_type)(unknown_picture); +transform_type,color_type,pair_type: type_test(c); +numeric_type: type_range(known)(independent); +known_op,unknown_op: test_known(c); + +@ @<Declare unary action procedures@>= +procedure test_known(@!c:quarterword); +label done; +var @!b:true_code..false_code; {is the current expression known?} +@!p,@!q:pointer; {locations in a big node} +begin b:=false_code; +case cur_type of +vacuous,boolean_type,string_type,pen_type,path_type,picture_type, + known: b:=true_code; +transform_type,color_type,pair_type:begin p:=value(cur_exp); + q:=p+big_node_size[cur_type]; + repeat q:=q-2; + if type(q)<>known then goto done; + until q=p; + b:=true_code; +done: end; +othercases do_nothing +endcases; +if c=known_op then flush_cur_exp(b) +else flush_cur_exp(true_code+false_code-b); +cur_type:=boolean_type; +end; + +@ @<Additional cases of unary operators@>= +cycle_op: begin if cur_type<>path_type then flush_cur_exp(false_code) + else if left_type(cur_exp)<>endpoint then flush_cur_exp(true_code) + else flush_cur_exp(false_code); + cur_type:=boolean_type; + end; + +@ @<Additional cases of unary operators@>= +arc_length: begin if cur_type=pair_type then pair_to_path; + if cur_type<>path_type then bad_unary(arc_length) + else flush_cur_exp(get_arc_length(cur_exp)); + end; + +@ Here we use the fact that |c-filled_op+fill_code| is the desired graphical +object |type|. +@^data structure assumptions@> + +@<Additional cases of unary operators@>= +filled_op,stroked_op,textual_op,clipped_op,bounded_op: + begin if cur_type<>picture_type then flush_cur_exp(false_code) + else if link(dummy_loc(cur_exp))=null then flush_cur_exp(false_code) + else if type(link(dummy_loc(cur_exp)))=c+fill_code-filled_op then + flush_cur_exp(true_code) + else flush_cur_exp(false_code); + cur_type:=boolean_type; + end; + +@ @<Additional cases of unary operators@>= +make_pen_op: begin if cur_type=pair_type then pair_to_path; + if cur_type<>path_type then bad_unary(make_pen_op) + else begin cur_type:=pen_type; + cur_exp:=make_pen(cur_exp,true); + end; + end; +make_path_op: if cur_type<>pen_type then bad_unary(make_path_op) + else begin cur_type:=path_type; + make_path(cur_exp); + end; +reverse: if cur_type=path_type then + begin p:=htap_ypoc(cur_exp); + if right_type(p)=endpoint then p:=link(p); + toss_knot_list(cur_exp); cur_exp:=p; + end + else if cur_type=pair_type then pair_to_path + else bad_unary(reverse); + +@ The |pair_value| routine changes the current expression to a +given ordered pair of values. + +@<Declare unary action procedures@>= +procedure pair_value(@!x,@!y:scaled); +var @!p:pointer; {a pair node} +begin p:=get_node(value_node_size); flush_cur_exp(p); cur_type:=pair_type; +type(p):=pair_type; name_type(p):=capsule; init_big_node(p); +p:=value(p);@/ +type(x_part_loc(p)):=known; value(x_part_loc(p)):=x;@/ +type(y_part_loc(p)):=known; value(y_part_loc(p)):=y;@/ +end; + +@ @<Additional cases of unary operators@>= +ll_corner_op: if not get_cur_bbox then bad_unary(ll_corner_op) + else pair_value(minx,miny); +lr_corner_op: if not get_cur_bbox then bad_unary(lr_corner_op) + else pair_value(maxx,miny); +ul_corner_op: if not get_cur_bbox then bad_unary(ul_corner_op) + else pair_value(minx,maxy); +ur_corner_op: if not get_cur_bbox then bad_unary(ur_corner_op) + else pair_value(maxx,maxy); + +@ Here is a function that sets |minx|, |maxx|, |miny|, |maxy| to the bounding +box of the current expression. The boolean result is |false| if the expression +has the wrong type. + +@<Declare unary action procedures@>= +function get_cur_bbox: boolean; +label exit; +begin case cur_type of +picture_type: begin set_bbox(cur_exp,true); + if minx_val(cur_exp)>maxx_val(cur_exp) then + begin minx:=0; maxx:=0; miny:=0; maxy:=0; + end + else begin minx:=minx_val(cur_exp); + maxx:=maxx_val(cur_exp); + miny:=miny_val(cur_exp); + maxy:=maxy_val(cur_exp); + end; + end; +path_type: path_bbox(cur_exp); +pen_type: pen_bbox(cur_exp); +othercases begin get_cur_bbox:=false; + return; + end +endcases;@/ +get_cur_bbox:=true; +exit:end; + +@ @<Additional cases of unary operators@>= +read_from_op,close_from_op: if cur_type<>string_type then bad_unary(c) +else do_read_or_close(c); + +@ Here is a routine that interprets |cur_exp| as a file name and tries to read +a line from the file or to close the file. + +@d close_file=46 {go here when closing the file} + +@<Declare unary action procedures@>= +procedure do_read_or_close(@!c:quarterword); +label exit, continue, found, not_found, close_file; +var @!n,@!n0:readf_index; {indices for searching |rd_fname|} +begin @<Find the |n| where |rd_fname[n]=cur_exp|; if |cur_exp| must be inserted, + call |start_read_input| and |goto found| or |not_found|@>; +begin_file_reading; +name:=is_read; +if input_ln(rd_file[n],true) then goto found; +end_file_reading; +not_found:@<Record the end of file and set |cur_exp| to a dummy value@>; +return; +close_file:flush_cur_exp(0); cur_type:=vacuous; return; +found:flush_cur_exp(0); +finish_read; +exit:end; + +@ Free slots in the |rd_file| and |rd_fname| arrays are marked with 0's in +|rd_fname|. + +@<Find the |n| where |rd_fname[n]=cur_exp|...@>= +n:=read_files; +n0:=read_files; +repeat +continue:if n>0 then decr(n) +else if c=close_from_op then goto close_file +else @<Insert |cur_exp| at index |n0|, then call |start_read_input| and + |goto found| or |not_found|@>; +if rd_fname[n]=0 then + begin n0:=n; goto continue; + end; +until str_vs_str(cur_exp,rd_fname[n])=0; +if c=close_from_op then + begin a_close(rd_file[n]); goto not_found; end + +@ @<Insert |cur_exp| at index |n0|, then call |start_read_input| and...@>= +begin if n0=read_files then + if read_files<max_read_files then incr(read_files) + else overflow("readfrom files",max_read_files); +n:=n0; +if start_read_input(cur_exp,n) then goto found @+else goto not_found; +end + +@ @<Record the end of file and set |cur_exp| to a dummy value@>= +delete_str_ref(rd_fname[n]); +rd_fname[n]:=0; +if n=read_files-1 then read_files:=n; +if c=close_from_op then goto close_file; +@<Make sure |eof_line| is initialized@>; +flush_cur_exp(eof_line); +cur_type:=string_type + +@ Since the |eof_line| string contains a non-printable character, it must be +initialized at run time and stored in a global variable. + +@<Glob...@>= +eof_line:str_number; {string denoting end-of-file or 0 if uninitialized} + +@ @<Set init...@>= +eof_line:=0; + +@ @<Make sure |eof_line| is initialized@>= +if eof_line=0 then + begin append_char(0); + eof_line:=make_string; + str_ref[eof_line]:=max_str_ref; + end + +@ Finally, we have the operations that combine a capsule~|p| +with the current expression. + +@p @t\4@>@<Declare binary action procedures@>@; +procedure do_binary(@!p:pointer;@!c:quarterword); +label done,done1,exit; +var @!q,@!r,@!rr:pointer; {for list manipulation} +@!old_p,@!old_exp:pointer; {capsules to recycle} +@!v:integer; {for numeric manipulation} +begin check_arith; +if internal[tracing_commands]>two then + @<Trace the current binary operation@>; +@<Sidestep |independent| cases in capsule |p|@>; +@<Sidestep |independent| cases in the current expression@>; +case c of +plus,minus:@<Add or subtract the current expression from |p|@>; +@t\4@>@<Additional cases of binary operators@>@; +end; {there are no other cases} +recycle_value(p); free_node(p,value_node_size); {|return| to avoid this} +exit:check_arith; @<Recycle any sidestepped |independent| capsules@>; +end; + +@ @<Declare binary action...@>= +procedure bad_binary(@!p:pointer;@!c:quarterword); +begin disp_err(p,""); +exp_err("Not implemented: "); +@.Not implemented...@> +if c>=min_of then print_op(c); +print_known_or_unknown_type(type(p),p); +if c>=min_of then print("of")@+else print_op(c); +print_known_or_unknown_type(cur_type,cur_exp);@/ +help3("I'm afraid I don't know how to apply that operation to that")@/ + ("combination of types. Continue, and I'll return the second")@/ + ("argument (see above) as the result of the operation."); +put_get_error; +end; + +@ @<Trace the current binary operation@>= +begin begin_diagnostic; print_nl("{("); +print_exp(p,0); {show the operand, but not verbosely} +print_char(")"); print_op(c); print_char("(");@/ +print_exp(null,0); print(")}"); end_diagnostic(false); +end + +@ Several of the binary operations are potentially complicated by the +fact that |independent| values can sneak into capsules. For example, +we've seen an instance of this difficulty in the unary operation +of negation. In order to reduce the number of cases that need to be +handled, we first change the two operands (if necessary) +to rid them of |independent| components. The original operands are +put into capsules called |old_p| and |old_exp|, which will be +recycled after the binary operation has been safely carried out. + +@<Recycle any sidestepped |independent| capsules@>= +if old_p<>null then + begin recycle_value(old_p); free_node(old_p,value_node_size); + end; +if old_exp<>null then + begin recycle_value(old_exp); free_node(old_exp,value_node_size); + end + +@ A big node is considered to be ``tarnished'' if it contains at least one +independent component. We will define a simple function called `|tarnished|' +that returns |null| if and only if its argument is not tarnished. + +@<Sidestep |independent| cases in capsule |p|@>= +case type(p) of +transform_type,color_type,pair_type: old_p:=tarnished(p); +independent: old_p:=void; +othercases old_p:=null +endcases; +if old_p<>null then + begin q:=stash_cur_exp; old_p:=p; make_exp_copy(old_p); + p:=stash_cur_exp; unstash_cur_exp(q); + end; + +@ @<Sidestep |independent| cases in the current expression@>= +case cur_type of +transform_type,color_type,pair_type:old_exp:=tarnished(cur_exp); +independent:old_exp:=void; +othercases old_exp:=null +endcases; +if old_exp<>null then + begin old_exp:=cur_exp; make_exp_copy(old_exp); + end + +@ @<Declare binary action...@>= +function tarnished(@!p:pointer):pointer; +label exit; +var @!q:pointer; {beginning of the big node} +@!r:pointer; {current position in the big node} +begin q:=value(p); r:=q+big_node_size[type(p)]; +repeat r:=r-2; +if type(r)=independent then + begin tarnished:=void; return; + end; +until r=q; +tarnished:=null; +exit:end; + +@ @<Add or subtract the current expression from |p|@>= +if (cur_type<color_type)or(type(p)<color_type) then bad_binary(p,c) +else if (cur_type>pair_type)and(type(p)>pair_type) then + add_or_subtract(p,null,c) + else if cur_type<>type(p) then bad_binary(p,c) + else begin q:=value(p); r:=value(cur_exp); + rr:=r+big_node_size[cur_type]; + while r<rr do + begin add_or_subtract(q,r,c); + q:=q+2; r:=r+2; + end; + end + +@ The first argument to |add_or_subtract| is the location of a value node +in a capsule or pair node that will soon be recycled. The second argument +is either a location within a pair or transform node of |cur_exp|, +or it is null (which means that |cur_exp| itself should be the second +argument). The third argument is either |plus| or |minus|. + +The sum or difference of the numeric quantities will replace the second +operand. Arithmetic overflow may go undetected; users aren't supposed to +be monkeying around with really big values. + +@<Declare binary action...@>= +@t\4@>@<Declare the procedure called |dep_finish|@>@; +procedure add_or_subtract(@!p,@!q:pointer;@!c:quarterword); +label done,exit; +var @!s,@!t:small_number; {operand types} +@!r:pointer; {list traverser} +@!v:integer; {second operand value} +begin if q=null then + begin t:=cur_type; + if t<dependent then v:=cur_exp@+else v:=dep_list(cur_exp); + end +else begin t:=type(q); + if t<dependent then v:=value(q)@+else v:=dep_list(q); + end; +if t=known then + begin if c=minus then negate(v); + if type(p)=known then + begin v:=slow_add(value(p),v); + if q=null then cur_exp:=v@+else value(q):=v; + return; + end; + @<Add a known value to the constant term of |dep_list(p)|@>; + end +else begin if c=minus then negate_dep_list(v); + @<Add operand |p| to the dependency list |v|@>; + end; +exit:end; + +@ @<Add a known value to the constant term of |dep_list(p)|@>= +r:=dep_list(p); +while info(r)<>null do r:=link(r); +value(r):=slow_add(value(r),v); +if q=null then + begin q:=get_node(value_node_size); cur_exp:=q; cur_type:=type(p); + name_type(q):=capsule; + end; +dep_list(q):=dep_list(p); type(q):=type(p); +prev_dep(q):=prev_dep(p); link(prev_dep(p)):=q; +type(p):=known; {this will keep the recycler from collecting non-garbage} + +@ We prefer |dependent| lists to |proto_dependent| ones, because it is +nice to retain the extra accuracy of |fraction| coefficients. +But we have to handle both kinds, and mixtures too. + +@<Add operand |p| to the dependency list |v|@>= +if type(p)=known then + @<Add the known |value(p)| to the constant term of |v|@> +else begin s:=type(p); r:=dep_list(p); + if t=dependent then + begin if s=dependent then + if max_coef(r)+max_coef(v)<coef_bound then + begin v:=p_plus_q(v,r,dependent); goto done; + end; {|fix_needed| will necessarily be false} + t:=proto_dependent; v:=p_over_v(v,unity,dependent,proto_dependent); + end; + if s=proto_dependent then v:=p_plus_q(v,r,proto_dependent) + else v:=p_plus_fq(v,unity,r,proto_dependent,dependent); + done: @<Output the answer, |v| (which might have become |known|)@>; + end + +@ @<Add the known |value(p)| to the constant term of |v|@>= +begin while info(v)<>null do v:=link(v); +value(v):=slow_add(value(p),value(v)); +end + +@ @<Output the answer, |v| (which might have become |known|)@>= +if q<>null then dep_finish(v,q,t) +else begin cur_type:=t; dep_finish(v,null,t); + end + +@ Here's the current situation: The dependency list |v| of type |t| +should either be put into the current expression (if |q=null|) or +into location |q| within a pair node (otherwise). The destination (|cur_exp| +or |q|) formerly held a dependency list with the same +final pointer as the list |v|. + +@<Declare the procedure called |dep_finish|@>= +procedure dep_finish(@!v,@!q:pointer;@!t:small_number); +var @!p:pointer; {the destination} +@!vv:scaled; {the value, if it is |known|} +begin if q=null then p:=cur_exp@+else p:=q; +dep_list(p):=v; type(p):=t; +if info(v)=null then + begin vv:=value(v); + if q=null then flush_cur_exp(vv) + else begin recycle_value(p); type(q):=known; value(q):=vv; + end; + end +else if q=null then cur_type:=t; +if fix_needed then fix_dependencies; +end; + +@ Let's turn now to the six basic relations of comparison. + +@<Additional cases of binary operators@>= +less_than,less_or_equal,greater_than,greater_or_equal,equal_to,unequal_to: + begin check_arith; {at this point |arith_error| should be |false|?} + if (cur_type>pair_type)and(type(p)>pair_type) then + add_or_subtract(p,null,minus) {|cur_exp:=(p)-cur_exp|} + else if cur_type<>type(p) then + begin bad_binary(p,c); goto done; + end + else if cur_type=string_type then + flush_cur_exp(str_vs_str(value(p),cur_exp)) + else if (cur_type=unknown_string)or(cur_type=unknown_boolean) then + @<Check if unknowns have been equated@> + else if (cur_type<=pair_type)and(cur_type>=transform_type) then + @<Reduce comparison of big nodes to comparison of scalars@> + else if cur_type=boolean_type then flush_cur_exp(cur_exp-value(p)) + else begin bad_binary(p,c); goto done; + end; + @<Compare the current expression with zero@>; +done: arith_error:=false; {ignore overflow in comparisons} + end; + +@ @<Compare the current expression with zero@>= +if cur_type<>known then + begin if cur_type<known then + begin disp_err(p,""); + help1("The quantities shown above have not been equated.")@/ + end + else help2("Oh dear. I can't decide if the expression above is positive,")@/ + ("negative, or zero. So this comparison test won't be `true'."); + exp_err("Unknown relation will be considered false"); +@.Unknown relation...@> + put_get_flush_error(false_code); + end +else case c of + less_than: boolean_reset(cur_exp<0); + less_or_equal: boolean_reset(cur_exp<=0); + greater_than: boolean_reset(cur_exp>0); + greater_or_equal: boolean_reset(cur_exp>=0); + equal_to: boolean_reset(cur_exp=0); + unequal_to: boolean_reset(cur_exp<>0); + end; {there are no other cases} + cur_type:=boolean_type + +@ When two unknown strings are in the same ring, we know that they are +equal. Otherwise, we don't know whether they are equal or not, so we +make no change. + +@<Check if unknowns have been equated@>= +begin q:=value(cur_exp); +while (q<>cur_exp)and(q<>p) do q:=value(q); +if q=p then flush_cur_exp(0); +end + +@ @<Reduce comparison of big nodes to comparison of scalars@>= +begin q:=value(p); r:=value(cur_exp); +rr:=r+big_node_size[cur_type]-2; +loop@+ begin add_or_subtract(q,r,minus); + if type(r)<>known then goto done1; + if value(r)<>0 then goto done1; + if r=rr then goto done1; + q:=q+2; r:=r+2; + end; +done1:take_part(name_type(r)+x_part-x_part_sector); +end + +@ Here we use the sneaky fact that |and_op-false_code=or_op-true_code|. + +@<Additional cases of binary operators@>= +and_op,or_op: if (type(p)<>boolean_type)or(cur_type<>boolean_type) then + bad_binary(p,c) + else if value(p)=c+false_code-and_op then cur_exp:=value(p); + +@ @<Additional cases of binary operators@>= +times: if (cur_type<color_type)or(type(p)<color_type) then bad_binary(p,times) + else if (cur_type=known)or(type(p)=known) then + @<Multiply when at least one operand is known@> + else if (nice_color_or_pair(p,type(p))and(cur_type>pair_type)) + or(nice_color_or_pair(cur_exp,cur_type)and(type(p)>pair_type)) then + begin hard_times(p); return; + end + else bad_binary(p,times); + +@ @<Multiply when at least one operand is known@>= +begin if type(p)=known then + begin v:=value(p); free_node(p,value_node_size); + end +else begin v:=cur_exp; unstash_cur_exp(p); + end; +if cur_type=known then cur_exp:=take_scaled(cur_exp,v) +else if (cur_type=pair_type)or(cur_type=color_type) then + begin p:=value(cur_exp)+big_node_size[cur_type]; + repeat p:=p-2; + dep_mult(p,v,true); + until p=value(cur_exp); + end +else dep_mult(null,v,true); +return; +end + +@ @<Declare binary action...@>= +procedure dep_mult(@!p:pointer;@!v:integer;@!v_is_scaled:boolean); +label exit; +var @!q:pointer; {the dependency list being multiplied by |v|} +@!s,@!t:small_number; {its type, before and after} +begin if p=null then q:=cur_exp +else if type(p)<>known then q:=p +else begin if v_is_scaled then value(p):=take_scaled(value(p),v) + else value(p):=take_fraction(value(p),v); + return; + end; +t:=type(q); q:=dep_list(q); s:=t; +if t=dependent then if v_is_scaled then + if ab_vs_cd(max_coef(q),abs(v),coef_bound-1,unity)>=0 then t:=proto_dependent; +q:=p_times_v(q,v,s,t,v_is_scaled); dep_finish(q,p,t); +exit:end; + +@ Here is a routine that is similar to |times|; but it is invoked only +internally, when |v| is a |fraction| whose magnitude is at most~1, +and when |cur_type>=color_type|. + +@p procedure frac_mult(@!n,@!d:scaled); {multiplies |cur_exp| by |n/d|} +var @!p:pointer; {a pair node} +@!old_exp:pointer; {a capsule to recycle} +@!v:fraction; {|n/d|} +begin if internal[tracing_commands]>two then + @<Trace the fraction multiplication@>; +case cur_type of +transform_type,color_type,pair_type:old_exp:=tarnished(cur_exp); +independent:old_exp:=void; +othercases old_exp:=null +endcases; +if old_exp<>null then + begin old_exp:=cur_exp; make_exp_copy(old_exp); + end; +v:=make_fraction(n,d); +if cur_type=known then cur_exp:=take_fraction(cur_exp,v) +else if cur_type<=pair_type then + begin p:=value(cur_exp)+big_node_size[cur_type]; + repeat p:=p-2; + dep_mult(p,v,false); + until p=value(cur_exp); + end +else dep_mult(null,v,false); +if old_exp<>null then + begin recycle_value(old_exp); free_node(old_exp,value_node_size); + end +end; + +@ @<Trace the fraction multiplication@>= +begin begin_diagnostic; print_nl("{("); print_scaled(n); print_char("/"); +print_scaled(d); print(")*("); print_exp(null,0); print(")}"); +end_diagnostic(false); +end + +@ The |hard_times| routine multiplies a nice color or pair by a dependency list. + +@<Declare binary action procedures@>= +procedure hard_times(@!p:pointer); +label done; +var @!q:pointer; {a copy of the dependent variable |p|} +@!r:pointer; {a component of the big node for the nice color or pair} +@!v:scaled; {the known value for |r|} +begin if type(p)<=pair_type then + begin q:=stash_cur_exp; unstash_cur_exp(p); p:=q; + end; {now |cur_type=pair_type| or |cur_type=color_type|} +r:=value(cur_exp)+big_node_size[cur_type]; +loop @+begin r:=r-2; + v:=value(r); + type(r):=type(p); + if r=value(cur_exp) then goto done; + new_dep(r,copy_dep_list(dep_list(p))); + dep_mult(r,v,true); + end; +done:mem[value_loc(r)]:=mem[value_loc(p)]; +link(prev_dep(p)):=r; +free_node(p,value_node_size); +dep_mult(r,v,true); +end; + +@ @<Additional cases of binary operators@>= +over: if (cur_type<>known)or(type(p)<color_type) then bad_binary(p,over) + else begin v:=cur_exp; unstash_cur_exp(p); + if v=0 then @<Squeal about division by zero@> + else begin if cur_type=known then cur_exp:=make_scaled(cur_exp,v) + else if cur_type<=pair_type then + begin p:=value(cur_exp)+big_node_size[cur_type]; + repeat p:=p-2; + dep_div(p,v); + until p=value(cur_exp); + end + else dep_div(null,v); + end; + return; + end; + +@ @<Declare binary action...@>= +procedure dep_div(@!p:pointer;@!v:scaled); +label exit; +var @!q:pointer; {the dependency list being divided by |v|} +@!s,@!t:small_number; {its type, before and after} +begin if p=null then q:=cur_exp +else if type(p)<>known then q:=p +else begin value(p):=make_scaled(value(p),v); return; + end; +t:=type(q); q:=dep_list(q); s:=t; +if t=dependent then + if ab_vs_cd(max_coef(q),unity,coef_bound-1,abs(v))>=0 then t:=proto_dependent; +q:=p_over_v(q,v,s,t); dep_finish(q,p,t); +exit:end; + +@ @<Squeal about division by zero@>= +begin exp_err("Division by zero"); +@.Division by zero@> +help2("You're trying to divide the quantity shown above the error")@/ + ("message by zero. I'm going to divide it by one instead."); +put_get_error; +end + +@ @<Additional cases of binary operators@>= +pythag_add,pythag_sub: if (cur_type=known)and(type(p)=known) then + if c=pythag_add then cur_exp:=pyth_add(value(p),cur_exp) + else cur_exp:=pyth_sub(value(p),cur_exp) + else bad_binary(p,c); + +@ The next few sections of the program deal with affine transformations +of coordinate data. + +@<Additional cases of binary operators@>= +rotated_by,slanted_by,scaled_by,shifted_by,transformed_by, + x_scaled,y_scaled,z_scaled: @t@>@;@/ + if type(p)=path_type then + begin path_trans(c)(p); return; + end + else if type(p)=pen_type then + begin pen_trans(c)(p); + cur_exp:=convex_hull(cur_exp); {rounding error could destroy convexity} + return; + end + else if (type(p)=pair_type)or(type(p)=transform_type) then big_trans(p,c) + else if type(p)=picture_type then + begin do_edges_trans(p,c); return; + end + else bad_binary(p,c); + +@ Let |c| be one of the eight transform operators. The procedure call +|set_up_trans(c)| first changes |cur_exp| to a transform that corresponds to +|c| and the original value of |cur_exp|. (In particular, |cur_exp| doesn't +change at all if |c=transformed_by|.) + +Then, if all components of the resulting transform are |known|, they are +moved to the global variables |txx|, |txy|, |tyx|, |tyy|, |tx|, |ty|; +and |cur_exp| is changed to the known value zero. + +@<Declare binary action...@>= +procedure set_up_trans(@!c:quarterword); +label done,exit; +var @!p,@!q,@!r:pointer; {list manipulation registers} +begin if (c<>transformed_by)or(cur_type<>transform_type) then + @<Put the current transform into |cur_exp|@>; +@<If the current transform is entirely known, stash it in global variables; + otherwise |return|@>; +exit:end; + +@ @<Glob...@>= +@!txx,@!txy,@!tyx,@!tyy,@!tx,@!ty:scaled; {current transform coefficients} + +@ @<Put the current transform...@>= +begin p:=stash_cur_exp; cur_exp:=id_transform; cur_type:=transform_type; +q:=value(cur_exp); +case c of +@<For each of the eight cases, change the relevant fields of |cur_exp| + and |goto done|; + but do nothing if capsule |p| doesn't have the appropriate type@>@; +end; {there are no other cases} +disp_err(p,"Improper transformation argument"); +@.Improper transformation argument@> +help3("The expression shown above has the wrong type,")@/ + ("so I can't transform anything using it.")@/ + ("Proceed, and I'll omit the transformation."); +put_get_error; +done: recycle_value(p); free_node(p,value_node_size); +end + +@ @<If the current transform is entirely known, ...@>= +q:=value(cur_exp); r:=q+transform_node_size; +repeat r:=r-2; +if type(r)<>known then return; +until r=q; +txx:=value(xx_part_loc(q)); +txy:=value(xy_part_loc(q)); +tyx:=value(yx_part_loc(q)); +tyy:=value(yy_part_loc(q)); +tx:=value(x_part_loc(q)); +ty:=value(y_part_loc(q)); +flush_cur_exp(0) + +@ @<For each of the eight cases...@>= +rotated_by:if type(p)=known then + @<Install sines and cosines, then |goto done|@>; +slanted_by:if type(p)>pair_type then + begin install(xy_part_loc(q),p); goto done; + end; +scaled_by:if type(p)>pair_type then + begin install(xx_part_loc(q),p); install(yy_part_loc(q),p); goto done; + end; +shifted_by:if type(p)=pair_type then + begin r:=value(p); install(x_part_loc(q),x_part_loc(r)); + install(y_part_loc(q),y_part_loc(r)); goto done; + end; +x_scaled:if type(p)>pair_type then + begin install(xx_part_loc(q),p); goto done; + end; +y_scaled:if type(p)>pair_type then + begin install(yy_part_loc(q),p); goto done; + end; +z_scaled:if type(p)=pair_type then + @<Install a complex multiplier, then |goto done|@>; +transformed_by:do_nothing; + +@ @<Install sines and cosines, then |goto done|@>= +begin n_sin_cos((value(p) mod three_sixty_units)*16); +value(xx_part_loc(q)):=round_fraction(n_cos); +value(yx_part_loc(q)):=round_fraction(n_sin); +value(xy_part_loc(q)):=-value(yx_part_loc(q)); +value(yy_part_loc(q)):=value(xx_part_loc(q)); +goto done; +end + +@ @<Install a complex multiplier, then |goto done|@>= +begin r:=value(p); +install(xx_part_loc(q),x_part_loc(r)); +install(yy_part_loc(q),x_part_loc(r)); +install(yx_part_loc(q),y_part_loc(r)); +if type(y_part_loc(r))=known then negate(value(y_part_loc(r))) +else negate_dep_list(dep_list(y_part_loc(r))); +install(xy_part_loc(q),y_part_loc(r)); +goto done; +end + +@ Procedure |set_up_known_trans| is like |set_up_trans|, but it +insists that the transformation be entirely known. + +@<Declare binary action...@>= +procedure set_up_known_trans(@!c:quarterword); +begin set_up_trans(c); +if cur_type<>known then + begin exp_err("Transform components aren't all known"); +@.Transform components...@> + help3("I'm unable to apply a partially specified transformation")@/ + ("except to a fully known pair or transform.")@/ + ("Proceed, and I'll omit the transformation."); + put_get_flush_error(0); + txx:=unity; txy:=0; tyx:=0; tyy:=unity; tx:=0; ty:=0; + end; +end; + +@ Here's a procedure that applies the transform |txx..ty| to a pair of +coordinates in locations |p| and~|q|. + +@<Declare binary action...@>= +procedure trans(@!p,@!q:pointer); +var @!v:scaled; {the new |x| value} +begin v:=take_scaled(mem[p].sc,txx)+take_scaled(mem[q].sc,txy)+tx; +mem[q].sc:=take_scaled(mem[p].sc,tyx)+take_scaled(mem[q].sc,tyy)+ty; +mem[p].sc:=v; +end; + +@ The simplest transformation procedure applies a transform to all +coordinates of a path. The |path_trans(c)(p)| macro applies +a transformation defined by |cur_exp| and the transform operator |c| +to the path~|p|. + +@d path_trans(#)==begin set_up_known_trans(#); path_trans_end +@d path_trans_end(#)==unstash_cur_exp(#); do_path_trans(cur_exp); end + +@<Declare binary action...@>= +procedure do_path_trans(@!p:pointer); +label exit; +var @!q:pointer; {list traverser} +begin q:=p; +repeat +if left_type(q)<>endpoint then trans(q+3,q+4); {that's |left_x| and |left_y|} +trans(q+1,q+2); {that's |x_coord| and |y_coord|} +if right_type(q)<>endpoint then trans(q+5,q+6); {that's |right_x| and |right_y|} +@^data structure assumptions@> +q:=link(q); +until q=p; +exit:end; + +@ Transforming a pen is very similar, except that there are no |left_type| +and |right_type| fields. + +@d pen_trans(#)==begin set_up_known_trans(#); pen_trans_end +@d pen_trans_end(#)==unstash_cur_exp(#); do_pen_trans(cur_exp); end + +@<Declare binary action...@>= +procedure do_pen_trans(@!p:pointer); +label exit; +var @!q:pointer; {list traverser} +begin if pen_is_elliptical(p) then + begin trans(p+3,p+4); {that's |left_x| and |left_y|} + trans(p+5,p+6); {that's |right_x| and |right_y|} + end; +q:=p; +repeat +trans(q+1,q+2); {that's |x_coord| and |y_coord|} +@^data structure assumptions@> +q:=link(q); +until q=p; +exit:end; + +@ The next transformation procedure applies to edge structures. It will do +any transformation, but the results may be substandard if the picture contains +text that uses downloaded bitmap fonts. The binary action procedure is +|do_edges_trans|, but we also need a function that just scales a picture. +That routine is |scale_edges|. Both it and the underlying routine |edges_trans| +should be thought of as procedures that update an edge structure |h|, except +that they have to return a (possibly new) structure because of the need to call +|private_edges|. + +@<Declare binary action...@>= +function edges_trans(@!h:pointer):pointer; +label done1; +var @!q:pointer; {the object being transformed} +@!r,@!s:pointer; {for list manipulation} +@!sx,@!sy:scaled; {saved transformation parameters} +@!sqdet:scaled; {square root of determinant for |dash_scale|} +@!sgndet:integer; {sign of the determinant} +@!v:scaled; {a temporary value} +begin h:=private_edges(h);@/ +sqdet:=sqrt_det(txx,txy,tyx,tyy); +sgndet:=ab_vs_cd(txx,tyy,txy,tyx); +if dash_list(h)<>null_dash then + @<Try to transform the dash list of |h|@>; +@<Make the bounding box of |h| unknown if it can't be updated properly + without scanning the whole structure@>; +q:=link(dummy_loc(h)); +while q<>null do + begin @<Transform graphical object |q|@>;@/ + q:=link(q); + end; +edges_trans:=h; +end; +@# +procedure do_edges_trans(@!p:pointer;@!c:quarterword); +begin set_up_known_trans(c); +value(p):=edges_trans(value(p)); +unstash_cur_exp(p); +end; +@# +procedure scale_edges; +begin txx:=se_sf; tyy:=se_sf; +txy:=0; tyx:=0; tx:=0; ty:=0; +se_pic:=edges_trans(se_pic); +end; + +@ @<Try to transform the dash list of |h|@>= +if (txy<>0)or(tyx<>0)or(ty<>0)or(abs(txx)<>abs(tyy)) then + flush_dash_list(h) +else begin if txx<0 then @<Reverse the dash list of |h|@>; + @<Scale the dash list by |txx| and shift it by |tx|@>; + dash_y(h):=take_scaled(dash_y(h),abs(tyy)); + end + +@ @<Reverse the dash list of |h|@>= +begin r:=dash_list(h); +dash_list(h):=null_dash; +while r<>null_dash do + begin s:=r; r:=link(r);@/ + v:=start_x(s); start_x(s):=stop_x(s); stop_x(s):=v;@/ + link(s):=dash_list(h); + dash_list(h):=s; + end; +end + +@ @<Scale the dash list by |txx| and shift it by |tx|@>= +r:=dash_list(h); +while r<>null_dash do + begin start_x(r):=take_scaled(start_x(r),txx)+tx; + stop_x(r):=take_scaled(stop_x(r),txx)+tx;@/ + r:=link(r); + end + +@ @<Make the bounding box of |h| unknown if it can't be updated properly...@>= +if (txx=0)and(tyy=0) then + @<Swap the $x$ and $y$ parameters in the bounding box of |h|@> +else if (txy<>0)or(tyx<>0) then + begin init_bbox(h); + goto done1; + end; +if minx_val(h)<=maxx_val(h) then + @<Scale the bounding box by |txx+txy| and |tyx+tyy|; then shift by + |(tx,ty)|@>; +done1: + +@ @<Swap the $x$ and $y$ parameters in the bounding box of |h|@>= +begin v:=minx_val(h); minx_val(h):=miny_val(h); miny_val(h):=v;@/ +v:=maxx_val(h); maxx_val(h):=maxy_val(h); maxy_val(h):=v; +end + +@ The sum ``|txx+txy|'' is whichever of |txx| or |txy| is nonzero. The other +sum is similar. + +@<Scale the bounding box by |txx+txy| and |tyx+tyy|; then shift...@>= +begin minx_val(h):=take_scaled(minx_val(h),txx+txy)+tx;@/ +maxx_val(h):=take_scaled(maxx_val(h),txx+txy)+tx;@/ +miny_val(h):=take_scaled(miny_val(h),tyx+tyy)+ty;@/ +maxy_val(h):=take_scaled(maxy_val(h),tyx+tyy)+ty;@/ +if txx+txy<0 then + begin v:=minx_val(h); minx_val(h):=maxx_val(h); maxx_val(h):=v; + end; +if tyx+tyy<0 then + begin v:=miny_val(h); miny_val(h):=maxy_val(h); maxy_val(h):=v; + end; +end + +@ Now we ready for the main task of transforming the graphical objects in edge +structure~|h|. + +@<Transform graphical object |q|@>= +case type(q) of +fill_code,stroked_code: begin + do_path_trans(path_p(q)); + @<Transform |pen_p(q)|, making sure polygonal pens stay counter-clockwise@>; + end; +start_clip_code,start_bounds_code: do_path_trans(path_p(q)); +text_code:begin r:=text_tx_loc(q); + @<Transform the compact transformation starting at |r|@>; + end; +stop_clip_code,stop_bounds_code: do_nothing; +end {there are no other cases} + +@ Note that the shift parameters |(tx,ty)| apply only to the path being stroked. +The |dash_scale| has to be adjusted to scale the dash lengths in |dash_p(q)| +since the \ps\ output procedures will try to compensate for the transformation +we are applying to |pen_p(q)|. Since this compensation is based on the square +root of the determinant, |sqdet| is the appropriate factor. + +@<Transform |pen_p(q)|, making sure...@>= +if pen_p(q)<>null then + begin sx:=tx; sy:=ty; + tx:=0; ty:=0;@/ + do_pen_trans(pen_p(q)); + if ((type(q)=stroked_code)and(dash_p(q)<>null)) then + dash_scale(q):=take_scaled(dash_scale(q),sqdet); + if not pen_is_elliptical(pen_p(q)) then + if sgndet<0 then + pen_p(q):=make_pen(copy_path(pen_p(q)),true); {this unreverses the pen} + tx:=sx; ty:=sy; + end + +@ This uses the fact that transformations are stored in the order +|(tx,ty,txx,txy,tyx,tyy)|. +@^data structure assumptions@> + +@<Transform the compact transformation starting at |r|@>= +trans(r,r+1); +sx:=tx; sy:=ty; +tx:=0; ty:=0; +trans(r+2,r+4); +trans(r+3,r+5); +tx:=sx; ty:=sy + +@ The hard cases of transformation occur when big nodes are involved, +and when some of their components are unknown. + +@<Declare binary action...@>= +@t\4@>@<Declare subroutines needed by |big_trans|@>@; +procedure big_trans(@!p:pointer;@!c:quarterword); +label exit; +var @!q,@!r,@!pp,@!qq:pointer; {list manipulation registers} +@!s:small_number; {size of a big node} +begin s:=big_node_size[type(p)]; q:=value(p); r:=q+s; +repeat r:=r-2; +if type(r)<>known then @<Transform an unknown big node and |return|@>; +until r=q; +@<Transform a known big node@>; +exit:end; {node |p| will now be recycled by |do_binary|} + +@ @<Transform an unknown big node and |return|@>= +begin set_up_known_trans(c); make_exp_copy(p); r:=value(cur_exp); +if cur_type=transform_type then + begin bilin1(yy_part_loc(r),tyy,xy_part_loc(q),tyx,0); + bilin1(yx_part_loc(r),tyy,xx_part_loc(q),tyx,0); + bilin1(xy_part_loc(r),txx,yy_part_loc(q),txy,0); + bilin1(xx_part_loc(r),txx,yx_part_loc(q),txy,0); + end; +bilin1(y_part_loc(r),tyy,x_part_loc(q),tyx,ty); +bilin1(x_part_loc(r),txx,y_part_loc(q),txy,tx); +return; +end + +@ Let |p| point to a two-word value field inside a big node of |cur_exp|, +and let |q| point to a another value field. The |bilin1| procedure +replaces |p| by $p\cdot t+q\cdot u+\delta$. + +@<Declare subroutines needed by |big_trans|@>= +procedure bilin1(@!p:pointer;@!t:scaled;@!q:pointer;@!u,@!delta:scaled); +var @!r:pointer; {list traverser} +begin if t<>unity then dep_mult(p,t,true); +if u<>0 then + if type(q)=known then delta:=delta+take_scaled(value(q),u) + else begin @<Ensure that |type(p)=proto_dependent|@>; + dep_list(p):=p_plus_fq(dep_list(p),u,dep_list(q),proto_dependent,type(q)); + end; +if type(p)=known then value(p):=value(p)+delta +else begin r:=dep_list(p); + while info(r)<>null do r:=link(r); + delta:=value(r)+delta; + if r<>dep_list(p) then value(r):=delta + else begin recycle_value(p); type(p):=known; value(p):=delta; + end; + end; +if fix_needed then fix_dependencies; +end; + +@ @<Ensure that |type(p)=proto_dependent|@>= +if type(p)<>proto_dependent then + begin if type(p)=known then new_dep(p,const_dependency(value(p))) + else dep_list(p):=p_times_v(dep_list(p),unity,dependent,proto_dependent,true); + type(p):=proto_dependent; + end + +@ @<Transform a known big node@>= +set_up_trans(c); +if cur_type=known then @<Transform known by known@> +else begin pp:=stash_cur_exp; qq:=value(pp); + make_exp_copy(p); r:=value(cur_exp); + if cur_type=transform_type then + begin bilin2(yy_part_loc(r),yy_part_loc(qq), + value(xy_part_loc(q)),yx_part_loc(qq),null); + bilin2(yx_part_loc(r),yy_part_loc(qq), + value(xx_part_loc(q)),yx_part_loc(qq),null); + bilin2(xy_part_loc(r),xx_part_loc(qq), + value(yy_part_loc(q)),xy_part_loc(qq),null); + bilin2(xx_part_loc(r),xx_part_loc(qq), + value(yx_part_loc(q)),xy_part_loc(qq),null); + end; + bilin2(y_part_loc(r),yy_part_loc(qq), + value(x_part_loc(q)),yx_part_loc(qq),y_part_loc(qq)); + bilin2(x_part_loc(r),xx_part_loc(qq), + value(y_part_loc(q)),xy_part_loc(qq),x_part_loc(qq)); + recycle_value(pp); free_node(pp,value_node_size); + end; + +@ Let |p| be a |proto_dependent| value whose dependency list ends +at |dep_final|. The following procedure adds |v| times another +numeric quantity to~|p|. + +@<Declare subroutines needed by |big_trans|@>= +procedure add_mult_dep(@!p:pointer;@!v:scaled;@!r:pointer); +begin if type(r)=known then + value(dep_final):=value(dep_final)+take_scaled(value(r),v) +else begin dep_list(p):= + p_plus_fq(dep_list(p),v,dep_list(r),proto_dependent,type(r)); + if fix_needed then fix_dependencies; + end; +end; + +@ The |bilin2| procedure is something like |bilin1|, but with known +and unknown quantities reversed. Parameter |p| points to a value field +within the big node for |cur_exp|; and |type(p)=known|. Parameters +|t| and~|u| point to value fields elsewhere; so does parameter~|q|, +unless it is |null| (which stands for zero). Location~|p| will be +replaced by $p\cdot t+v\cdot u+q$. + +@<Declare subroutines needed by |big_trans|@>= +procedure bilin2(@!p,@!t:pointer;@!v:scaled;@!u,@!q:pointer); +var @!vv:scaled; {temporary storage for |value(p)|} +begin vv:=value(p); type(p):=proto_dependent; +new_dep(p,const_dependency(0)); {this sets |dep_final|} +if vv<>0 then add_mult_dep(p,vv,t); {|dep_final| doesn't change} +if v<>0 then add_mult_dep(p,v,u); +if q<>null then add_mult_dep(p,unity,q); +if dep_list(p)=dep_final then + begin vv:=value(dep_final); recycle_value(p); + type(p):=known; value(p):=vv; + end; +end; + +@ @<Transform known by known@>= +begin make_exp_copy(p); r:=value(cur_exp); +if cur_type=transform_type then + begin bilin3(yy_part_loc(r),tyy,value(xy_part_loc(q)),tyx,0); + bilin3(yx_part_loc(r),tyy,value(xx_part_loc(q)),tyx,0); + bilin3(xy_part_loc(r),txx,value(yy_part_loc(q)),txy,0); + bilin3(xx_part_loc(r),txx,value(yx_part_loc(q)),txy,0); + end; +bilin3(y_part_loc(r),tyy,value(x_part_loc(q)),tyx,ty); +bilin3(x_part_loc(r),txx,value(y_part_loc(q)),txy,tx); +end + +@ Finally, in |bilin3| everything is |known|. + +@<Declare subroutines needed by |big_trans|@>= +procedure bilin3(@!p:pointer;@!t,@!v,@!u,@!delta:scaled); +begin if t<>unity then delta:=delta+take_scaled(value(p),t) +else delta:=delta+value(p); +if u<>0 then value(p):=delta+take_scaled(v,u) +else value(p):=delta; +end; + +@ @<Additional cases of binary operators@>= +concatenate: if (cur_type=string_type)and(type(p)=string_type) then cat(p) + else bad_binary(p,concatenate); +substring_of: if nice_pair(p,type(p))and(cur_type=string_type) then + chop_string(value(p)) + else bad_binary(p,substring_of); +subpath_of: begin if cur_type=pair_type then pair_to_path; + if nice_pair(p,type(p))and(cur_type=path_type) then + chop_path(value(p)) + else bad_binary(p,subpath_of); + end; + +@ @<Declare binary action...@>= +procedure cat(@!p:pointer); +var @!a,@!b:str_number; {the strings being concatenated} +@!k:pool_pointer; {index into |str_pool|} +begin a:=value(p); b:=cur_exp; str_room(length(a)+length(b)); +for k:=str_start[a] to str_stop(a)-1 do append_char(so(str_pool[k])); +for k:=str_start[b] to str_stop(b)-1 do append_char(so(str_pool[k])); +cur_exp:=make_string; delete_str_ref(b); +end; + +@ @<Declare binary action...@>= +procedure chop_string(@!p:pointer); +var @!a,@!b:integer; {start and stop points} +@!l:integer; {length of the original string} +@!k:integer; {runs from |a| to |b|} +@!s:str_number; {the original string} +@!reversed:boolean; {was |a>b|?} +begin a:=round_unscaled(value(x_part_loc(p))); +b:=round_unscaled(value(y_part_loc(p))); +if a<=b then reversed:=false +else begin reversed:=true; k:=a; a:=b; b:=k; + end; +s:=cur_exp; l:=length(s); +if a<0 then + begin a:=0; + if b<0 then b:=0; + end; +if b>l then + begin b:=l; + if a>l then a:=l; + end; +str_room(b-a); +if reversed then + for k:=str_start[s]+b-1 downto str_start[s]+a do append_char(so(str_pool[k])) +else for k:=str_start[s]+a to str_start[s]+b-1 do append_char(so(str_pool[k])); +cur_exp:=make_string; delete_str_ref(s); +end; + +@ @<Declare binary action...@>= +procedure chop_path(@!p:pointer); +var @!q:pointer; {a knot in the original path} +@!pp,@!qq,@!rr,@!ss:pointer; {link variables for copies of path nodes} +@!a,@!b,@!k,@!l:scaled; {indices for chopping} +@!reversed:boolean; {was |a>b|?} +begin l:=path_length; a:=value(x_part_loc(p)); b:=value(y_part_loc(p)); +if a<=b then reversed:=false +else begin reversed:=true; k:=a; a:=b; b:=k; + end; +@<Dispense with the cases |a<0| and/or |b>l|@>; +q:=cur_exp; +while a>=unity do + begin q:=link(q); a:=a-unity; b:=b-unity; + end; +if b=a then @<Construct a path from |pp| to |qq| of length zero@> +else @<Construct a path from |pp| to |qq| of length $\lceil b\rceil$@>; +left_type(pp):=endpoint; right_type(qq):=endpoint; link(qq):=pp; +toss_knot_list(cur_exp); +if reversed then + begin cur_exp:=link(htap_ypoc(pp)); toss_knot_list(pp); + end +else cur_exp:=pp; +end; + +@ @<Dispense with the cases |a<0| and/or |b>l|@>= +if a<0 then + if left_type(cur_exp)=endpoint then + begin a:=0; if b<0 then b:=0; + end + else repeat a:=a+l; b:=b+l; + until a>=0; {a cycle always has length |l>0|} +if b>l then if left_type(cur_exp)=endpoint then + begin b:=l; if a>l then a:=l; + end + else while a>=l do + begin a:=a-l; b:=b-l; + end + +@ @<Construct a path from |pp| to |qq| of length $\lceil b\rceil$@>= +begin pp:=copy_knot(q); qq:=pp; +repeat q:=link(q); rr:=qq; qq:=copy_knot(q); link(rr):=qq; b:=b-unity; +until b<=0; +if a>0 then + begin ss:=pp; pp:=link(pp); + split_cubic(ss,a*@'10000); pp:=link(ss); + free_node(ss,knot_node_size); + if rr=ss then + begin b:=make_scaled(b,unity-a); rr:=pp; + end; + end; +if b<0 then + begin split_cubic(rr,(b+unity)*@'10000); + free_node(qq,knot_node_size); + qq:=link(rr); + end; +end + +@ @<Construct a path from |pp| to |qq| of length zero@>= +begin if a>0 then + begin split_cubic(q,a*@'10000); q:=link(q); + end; +pp:=copy_knot(q); qq:=pp; +end + +@ @<Additional cases of binary operators@>= +point_of,precontrol_of,postcontrol_of: begin if cur_type=pair_type then + pair_to_path; + if (cur_type=path_type)and(type(p)=known) then + find_point(value(p),c) + else bad_binary(p,c); + end; +pen_offset_of: if (cur_type=pen_type)and nice_pair(p,type(p)) then + set_up_offset(value(p)) + else bad_binary(p,pen_offset_of); +direction_time_of: begin if cur_type=pair_type then pair_to_path; + if (cur_type=path_type)and nice_pair(p,type(p)) then + set_up_direction_time(value(p)) + else bad_binary(p,direction_time_of); + end; + +@ @<Declare binary action...@>= +procedure set_up_offset(@!p:pointer); +begin find_offset(value(x_part_loc(p)),value(y_part_loc(p)),cur_exp); +pair_value(cur_x,cur_y); +end; +@# +procedure set_up_direction_time(@!p:pointer); +begin flush_cur_exp(find_direction_time(value(x_part_loc(p)), + value(y_part_loc(p)),cur_exp)); +end; + +@ @<Declare binary action...@>= +procedure find_point(@!v:scaled;@!c:quarterword); +var @!p:pointer; {the path} +@!n:scaled; {its length} +begin p:=cur_exp;@/ +if left_type(p)=endpoint then n:=-unity@+else n:=0; +repeat p:=link(p); n:=n+unity; +until p=cur_exp; +if n=0 then v:=0 +else if v<0 then + if left_type(p)=endpoint then v:=0 + else v:=n-1-((-v-1) mod n) +else if v>n then + if left_type(p)=endpoint then v:=n + else v:=v mod n; +p:=cur_exp; +while v>=unity do + begin p:=link(p); v:=v-unity; + end; +if v<>0 then @<Insert a fractional node by splitting the cubic@>; +@<Set the current expression to the desired path coordinates@>; +end; + +@ @<Insert a fractional node...@>= +begin split_cubic(p,v*@'10000); p:=link(p); +end + +@ @<Set the current expression to the desired path coordinates...@>= +case c of +point_of: pair_value(x_coord(p),y_coord(p)); +precontrol_of: if left_type(p)=endpoint then pair_value(x_coord(p),y_coord(p)) + else pair_value(left_x(p),left_y(p)); +postcontrol_of: if right_type(p)=endpoint then pair_value(x_coord(p),y_coord(p)) + else pair_value(right_x(p),right_y(p)); +end {there are no other cases} + +@ @<Additional cases of binary operators@>= +arc_time_of: begin if cur_type=pair_type then + pair_to_path; + if (cur_type=path_type)and(type(p)=known) then + flush_cur_exp(get_arc_time(cur_exp,value(p))) + else bad_binary(p,c); + end; + +@ @<Additional cases of bin...@>= +intersect: begin if type(p)=pair_type then + begin q:=stash_cur_exp; unstash_cur_exp(p); + pair_to_path; p:=stash_cur_exp; unstash_cur_exp(q); + end; + if cur_type=pair_type then pair_to_path; + if (cur_type=path_type)and(type(p)=path_type) then + begin path_intersection(value(p),cur_exp); + pair_value(cur_t,cur_tt); + end + else bad_binary(p,intersect); + end; + +@ @<Additional cases of bin...@>= +in_font:if (cur_type<>string_type)or(type(p)<>string_type) + then bad_binary(p,in_font) + else begin do_infont(p); return; + end; + +@ Function |new_text_node| owns the reference count for its second argument +(the text string) but not its first (the font name). + +@<Declare binary action...@>= +procedure do_infont(@!p:pointer); +var @!q:pointer; +begin q:=get_node(edge_header_size); +init_edges(q); +link(obj_tail(q)):=new_text_node(cur_exp,value(p)); +obj_tail(q):=link(obj_tail(q)); +free_node(p,value_node_size);@/ +flush_cur_exp(q); +cur_type:=picture_type; +end; + +@* \[40] Statements and commands. +The chief executive of \MP\ is the |do_statement| routine, which +contains the master switch that causes all the various pieces of \MP\ +to do their things, in the right order. + +In a sense, this is the grand climax of the program: It applies all the +tools that we have worked so hard to construct. In another sense, this is +the messiest part of the program: It necessarily refers to other pieces +of code all over the place, so that a person can't fully understand what is +going on without paging back and forth to be reminded of conventions that +are defined elsewhere. We are now at the hub of the web. + +The structure of |do_statement| itself is quite simple. The first token +of the statement is fetched using |get_x_next|. If it can be the first +token of an expression, we look for an equation, an assignment, or a +title. Otherwise we use a \&{case} construction to branch at high speed to +the appropriate routine for various and sundry other types of commands, +each of which has an ``action procedure'' that does the necessary work. + +The program uses the fact that +$$\hbox{|min_primary_command=max_statement_command=type_name|}$$ +to interpret a statement that starts with, e.g., `\&{string}', +as a type declaration rather than a boolean expression. + +@p @<Declare action procedures for use by |do_statement|@>@; +procedure do_statement; {governs \MP's activities} +begin cur_type:=vacuous; get_x_next; +if cur_cmd>max_primary_command then @<Worry about bad statement@> +else if cur_cmd>max_statement_command then + @<Do an equation, assignment, title, or + `$\langle\,$expression$\,\rangle\,$\&{endgroup}'@> +else @<Do a statement that doesn't begin with an expression@>; +if cur_cmd<semicolon then + @<Flush unparsable junk that was found after the statement@>; +error_count:=0; +end; + +@ The only command codes |>max_primary_command| that can be present +at the beginning of a statement are |semicolon| and higher; these +occur when the statement is null. + +@<Worry about bad statement@>= +begin if cur_cmd<semicolon then + begin print_err("A statement can't begin with `"); +@.A statement can't begin with x@> + print_cmd_mod(cur_cmd,cur_mod); print_char("'"); + help5("I was looking for the beginning of a new statement.")@/ + ("If you just proceed without changing anything, I'll ignore")@/ + ("everything up to the next `;'. Please insert a semicolon")@/ + ("now in front of anything that you don't want me to delete.")@/ + ("(See Chapter 27 of The METAFONTbook for an example.)");@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> + back_error; get_x_next; + end; +end + +@ The help message printed here says that everything is flushed up to +a semicolon, but actually the commands |end_group| and |stop| will +also terminate a statement. + +@<Flush unparsable junk that was found after the statement@>= +begin print_err("Extra tokens will be flushed"); +@.Extra tokens will be flushed@> +help6("I've just read as much of that statement as I could fathom,")@/ +("so a semicolon should have been next. It's very puzzling...")@/ +("but I'll try to get myself back together, by ignoring")@/ +("everything up to the next `;'. Please insert a semicolon")@/ +("now in front of anything that you don't want me to delete.")@/ +("(See Chapter 27 of The METAFONTbook for an example.)");@/ +@:METAFONTbook}{\sl The {\logos METAFONT\/}book@> +back_error; scanner_status:=flushing; +repeat get_t_next; +@<Decrease the string reference count...@>; +until end_of_statement; {|cur_cmd=semicolon|, |end_group|, or |stop|} +scanner_status:=normal; +end + +@ If |do_statement| ends with |cur_cmd=end_group|, we should have +|cur_type=vacuous| unless the statement was simply an expression; +in the latter case, |cur_type| and |cur_exp| should represent that +expression. + +@<Do a statement that doesn't...@>= +begin if internal[tracing_commands]>0 then show_cur_cmd_mod; +case cur_cmd of +type_name:do_type_declaration; +macro_def:if cur_mod>var_def then make_op_def + else if cur_mod>end_def then scan_def; +@t\4@>@<Cases of |do_statement| that invoke particular commands@>@; +end; {there are no other cases} +cur_type:=vacuous; +end + +@ The most important statements begin with expressions. + +@<Do an equation, assignment, title, or...@>= +begin var_flag:=assignment; scan_expression; +if cur_cmd<end_group then + begin if cur_cmd=equals then do_equation + else if cur_cmd=assignment then do_assignment + else if cur_type=string_type then @<Do a title@> + else if cur_type<>vacuous then + begin exp_err("Isolated expression"); +@.Isolated expression@> + help3("I couldn't find an `=' or `:=' after the")@/ + ("expression that is shown above this error message,")@/ + ("so I guess I'll just ignore it and carry on."); + put_get_error; + end; + flush_cur_exp(0); cur_type:=vacuous; + end; +end + +@ @<Do a title@>= +begin if internal[tracing_titles]>0 then + begin print_nl(""); print(cur_exp); update_terminal; + end; +end + +@ Equations and assignments are performed by the pair of mutually recursive +@^recursion@> +routines |do_equation| and |do_assignment|. These routines are called when +|cur_cmd=equals| and when |cur_cmd=assignment|, respectively; the left-hand +side is in |cur_type| and |cur_exp|, while the right-hand side is yet +to be scanned. After the routines are finished, |cur_type| and |cur_exp| +will be equal to the right-hand side (which will normally be equal +to the left-hand side). + +@<Declare action procedures for use by |do_statement|@>= +@t\4@>@<Declare the procedure called |try_eq|@>@; +@t\4@>@<Declare the procedure called |make_eq|@>@; +procedure@?do_assignment; forward;@t\2@>@/ +procedure do_equation; +var @!lhs:pointer; {capsule for the left-hand side} +@!p:pointer; {temporary register} +begin lhs:=stash_cur_exp; get_x_next; var_flag:=assignment; scan_expression; +if cur_cmd=equals then do_equation +else if cur_cmd=assignment then do_assignment; +if internal[tracing_commands]>two then @<Trace the current equation@>; +if cur_type=unknown_path then if type(lhs)=pair_type then + begin p:=stash_cur_exp; unstash_cur_exp(lhs); lhs:=p; + end; {in this case |make_eq| will change the pair to a path} +make_eq(lhs); {equate |lhs| to |(cur_type,cur_exp)|} +end; + +@ And |do_assignment| is similar to |do_expression|: + +@<Declare action procedures for use by |do_statement|@>= +procedure do_assignment; +var @!lhs:pointer; {token list for the left-hand side} +@!p:pointer; {where the left-hand value is stored} +@!q:pointer; {temporary capsule for the right-hand value} +begin if cur_type<>token_list then + begin exp_err("Improper `:=' will be changed to `='"); +@.Improper `:='@> + help2("I didn't find a variable name at the left of the `:=',")@/ + ("so I'm going to pretend that you said `=' instead.");@/ + error; do_equation; + end +else begin lhs:=cur_exp; cur_type:=vacuous;@/ + get_x_next; var_flag:=assignment; scan_expression; + if cur_cmd=equals then do_equation + else if cur_cmd=assignment then do_assignment; + if internal[tracing_commands]>two then @<Trace the current assignment@>; + if info(lhs)>hash_end then + @<Assign the current expression to an internal variable@> + else @<Assign the current expression to the variable |lhs|@>; + flush_node_list(lhs); + end; +end; + +@ @<Trace the current equation@>= +begin begin_diagnostic; print_nl("{("); print_exp(lhs,0); +print(")=("); print_exp(null,0); print(")}"); end_diagnostic(false); +end + +@ @<Trace the current assignment@>= +begin begin_diagnostic; print_nl("{"); +if info(lhs)>hash_end then print(int_name[info(lhs)-(hash_end)]) +else show_token_list(lhs,null,1000,0); +print(":="); print_exp(null,0); print_char("}"); end_diagnostic(false); +end + +@ @<Assign the current expression to an internal variable@>= +if cur_type=known then internal[info(lhs)-(hash_end)]:=cur_exp +else begin exp_err("Internal quantity `"); +@.Internal quantity...@> + print(int_name[info(lhs)-(hash_end)]); + print("' must receive a known value"); + help2("I can't set an internal quantity to anything but a known")@/ + ("numeric value, so I'll have to ignore this assignment."); + put_get_error; + end + +@ @<Assign the current expression to the variable |lhs|@>= +begin p:=find_variable(lhs); +if p<>null then + begin q:=stash_cur_exp; cur_type:=und_type(p); recycle_value(p); + type(p):=cur_type; value(p):=null; make_exp_copy(p); + p:=stash_cur_exp; unstash_cur_exp(q); make_eq(p); + end +else begin obliterated(lhs); put_get_error; + end; +end + + +@ And now we get to the nitty-gritty. The |make_eq| procedure is given +a pointer to a capsule that is to be equated to the current expression. + +@<Declare the procedure called |make_eq|@>= +procedure make_eq(@!lhs:pointer); +label restart,done, not_found; +var @!t:small_number; {type of the left-hand side} +@!v:integer; {value of the left-hand side} +@!p,@!q:pointer; {pointers inside of big nodes} +begin restart: t:=type(lhs); +if t<=pair_type then v:=value(lhs); +case t of +@t\4@>@<For each type |t|, make an equation and |goto done| unless |cur_type| + is incompatible with~|t|@>@; +end; {all cases have been listed} +@<Announce that the equation cannot be performed@>; +done:check_arith; recycle_value(lhs); free_node(lhs,value_node_size); +end; + +@ @<Announce that the equation cannot be performed@>= +disp_err(lhs,""); exp_err("Equation cannot be performed ("); +@.Equation cannot be performed@> +if type(lhs)<=pair_type then print_type(type(lhs))@+else print("numeric"); +print_char("="); +if cur_type<=pair_type then print_type(cur_type)@+else print("numeric"); +print_char(")");@/ +help2("I'm sorry, but I don't know how to make such things equal.")@/ + ("(See the two expressions just above the error message.)"); +put_get_error + +@ @<For each type |t|, make an equation and |goto done| unless...@>= +boolean_type,string_type,pen_type,path_type,picture_type: + if cur_type=t+unknown_tag then + begin nonlinear_eq(v,cur_exp,false); goto done; + end + else if cur_type=t then + @<Report redundant or inconsistent equation and |goto done|@>; +unknown_types:if cur_type=t-unknown_tag then + begin nonlinear_eq(cur_exp,lhs,true); goto done; + end + else if cur_type=t then + begin ring_merge(lhs,cur_exp); goto done; + end + else if cur_type=pair_type then if t=unknown_path then + begin pair_to_path; goto restart; + end; +transform_type,color_type,pair_type:if cur_type=t then + @<Do multiple equations and |goto done|@>; +known,dependent,proto_dependent,independent:if cur_type>=known then + begin try_eq(lhs,null); goto done; + end; +vacuous:do_nothing; + +@ @<Report redundant or inconsistent equation and |goto done|@>= +begin if cur_type<=string_type then + begin if cur_type=string_type then + begin if str_vs_str(v,cur_exp)<>0 then goto not_found; + end + else if v<>cur_exp then goto not_found; + @<Exclaim about a redundant equation@>; goto done; + end; +print_err("Redundant or inconsistent equation"); +@.Redundant or inconsistent equation@> +help2("An equation between already-known quantities can't help.")@/ + ("But don't worry; continue and I'll just ignore it."); +put_get_error; goto done; +not_found: print_err("Inconsistent equation"); +@.Inconsistent equation@> +help2("The equation I just read contradicts what was said before.")@/ + ("But don't worry; continue and I'll just ignore it."); +put_get_error; goto done; +end + +@ @<Do multiple equations and |goto done|@>= +begin p:=v+big_node_size[t]; q:=value(cur_exp)+big_node_size[t]; +repeat p:=p-2; q:=q-2; try_eq(p,q); +until p=v; +goto done; +end + +@ The first argument to |try_eq| is the location of a value node +in a capsule that will soon be recycled. The second argument is +either a location within a pair or transform node pointed to by +|cur_exp|, or it is |null| (which means that |cur_exp| itself +serves as the second argument). The idea is to leave |cur_exp| unchanged, +but to equate the two operands. + +@<Declare the procedure called |try_eq|@>= +procedure try_eq(@!l,@!r:pointer); +label done,done1; +var @!p:pointer; {dependency list for right operand minus left operand} +@!t:known..independent; {the type of list |p|} +@!q:pointer; {the constant term of |p| is here} +@!pp:pointer; {dependency list for right operand} +@!tt:dependent..independent; {the type of list |pp|} +@!copied:boolean; {have we copied a list that ought to be recycled?} +begin @<Remove the left operand from its container, negate it, and + put it into dependency list~|p| with constant term~|q|@>; +@<Add the right operand to list |p|@>; +if info(p)=null then @<Deal with redundant or inconsistent equation@> +else begin linear_eq(p,t); + if r=null then if cur_type<>known then if type(cur_exp)=known then + begin pp:=cur_exp; cur_exp:=value(cur_exp); cur_type:=known; + free_node(pp,value_node_size); + end; + end; +end; + +@ @<Remove the left operand from its container, negate it, and...@>= +t:=type(l); +if t=known then + begin t:=dependent; p:=const_dependency(-value(l)); q:=p; + end +else if t=independent then + begin t:=dependent; p:=single_dependency(l); negate(value(p)); + q:=dep_final; + end +else begin p:=dep_list(l); q:=p; + loop@+ begin negate(value(q)); + if info(q)=null then goto done; + q:=link(q); + end; + done: link(prev_dep(l)):=link(q); prev_dep(link(q)):=prev_dep(l); + type(l):=known; + end + +@ @<Deal with redundant or inconsistent equation@>= +begin if abs(value(p))>64 then {off by .001 or more} + begin print_err("Inconsistent equation");@/ +@.Inconsistent equation@> + print(" (off by "); print_scaled(value(p)); print_char(")"); + help2("The equation I just read contradicts what was said before.")@/ + ("But don't worry; continue and I'll just ignore it."); + put_get_error; + end +else if r=null then @<Exclaim about a redundant equation@>; +free_node(p,dep_node_size); +end + +@ @<Add the right operand to list |p|@>= +if r=null then + if cur_type=known then + begin value(q):=value(q)+cur_exp; goto done1; + end + else begin tt:=cur_type; + if tt=independent then pp:=single_dependency(cur_exp) + else pp:=dep_list(cur_exp); + end +else if type(r)=known then + begin value(q):=value(q)+value(r); goto done1; + end + else begin tt:=type(r); + if tt=independent then pp:=single_dependency(r) + else pp:=dep_list(r); + end; +if tt<>independent then copied:=false +else begin copied:=true; tt:=dependent; + end; +@<Add dependency list |pp| of type |tt| to dependency list~|p| of type~|t|@>; +if copied then flush_node_list(pp); +done1: + +@ @<Add dependency list |pp| of type |tt| to dependency list~|p| of type~|t|@>= +watch_coefs:=false; +if t=tt then p:=p_plus_q(p,pp,t) +else if t=proto_dependent then + p:=p_plus_fq(p,unity,pp,proto_dependent,dependent) +else begin q:=p; + while info(q)<>null do + begin value(q):=round_fraction(value(q)); q:=link(q); + end; + t:=proto_dependent; p:=p_plus_q(p,pp,t); + end; +watch_coefs:=true; + +@ Our next goal is to process type declarations. For this purpose it's +convenient to have a procedure that scans a $\langle\,$declared +variable$\,\rangle$ and returns the corresponding token list. After the +following procedure has acted, the token after the declared variable +will have been scanned, so it will appear in |cur_cmd|, |cur_mod|, +and~|cur_sym|. + +@<Declare the function called |scan_declared_variable|@>= +function scan_declared_variable:pointer; +label done; +var @!x:pointer; {hash address of the variable's root} +@!h,@!t:pointer; {head and tail of the token list to be returned} +@!l:pointer; {hash address of left bracket} +begin get_symbol; x:=cur_sym; +if cur_cmd<>tag_token then clear_symbol(x,false); +h:=get_avail; info(h):=x; t:=h;@/ +loop@+ begin get_x_next; + if cur_sym=0 then goto done; + if cur_cmd<>tag_token then if cur_cmd<>internal_quantity then + if cur_cmd=left_bracket then @<Descend past a collective subscript@> + else goto done; + link(t):=get_avail; t:=link(t); info(t):=cur_sym; + end; +done: if eq_type(x)<>tag_token then clear_symbol(x,false); +if equiv(x)=null then new_root(x); +scan_declared_variable:=h; +end; + +@ If the subscript isn't collective, we don't accept it as part of the +declared variable. + +@<Descend past a collective subscript@>= +begin l:=cur_sym; get_x_next; +if cur_cmd<>right_bracket then + begin back_input; cur_sym:=l; cur_cmd:=left_bracket; goto done; + end +else cur_sym:=collective_subscript; +end + +@ Type declarations are introduced by the following primitive operations. + +@<Put each...@>= +primitive("numeric",type_name,numeric_type);@/ +@!@:numeric_}{\&{numeric} primitive@> +primitive("string",type_name,string_type);@/ +@!@:string_}{\&{string} primitive@> +primitive("boolean",type_name,boolean_type);@/ +@!@:boolean_}{\&{boolean} primitive@> +primitive("path",type_name,path_type);@/ +@!@:path_}{\&{path} primitive@> +primitive("pen",type_name,pen_type);@/ +@!@:pen_}{\&{pen} primitive@> +primitive("picture",type_name,picture_type);@/ +@!@:picture_}{\&{picture} primitive@> +primitive("transform",type_name,transform_type);@/ +@!@:transform_}{\&{transform} primitive@> +primitive("color",type_name,color_type);@/ +@!@:color_}{\&{color} primitive@> +primitive("pair",type_name,pair_type);@/ +@!@:pair_}{\&{pair} primitive@> + +@ @<Cases of |print_cmd...@>= +type_name: print_type(m); + +@ Now we are ready to handle type declarations, assuming that a +|type_name| has just been scanned. + +@<Declare action procedures for use by |do_statement|@>= +procedure do_type_declaration; +var @!t:small_number; {the type being declared} +@!p:pointer; {token list for a declared variable} +@!q:pointer; {value node for the variable} +begin if cur_mod>=transform_type then t:=cur_mod@+else t:=cur_mod+unknown_tag; +repeat p:=scan_declared_variable; +flush_variable(equiv(info(p)),link(p),false);@/ +q:=find_variable(p); +if q<>null then + begin type(q):=t; value(q):=null; + end +else begin print_err("Declared variable conflicts with previous vardef"); +@.Declared variable conflicts...@> + help2("You can't use, e.g., `numeric foo[]' after `vardef foo'.")@/ + ("Proceed, and I'll ignore the illegal redeclaration."); + put_get_error; + end; +flush_list(p); +if cur_cmd<comma then @<Flush spurious symbols after the declared variable@>; +until end_of_statement; +end; + +@ @<Flush spurious symbols after the declared variable@>= +begin print_err("Illegal suffix of declared variable will be flushed"); +@.Illegal suffix...flushed@> +help5("Variables in declarations must consist entirely of")@/ + ("names and collective subscripts, e.g., `x[]a'.")@/ + ("Are you trying to use a reserved word in a variable name?")@/ + ("I'm going to discard the junk I found here,")@/ + ("up to the next comma or the end of the declaration."); +if cur_cmd=numeric_token then + help_line[2]:="Explicit subscripts like `x15a' aren't permitted."; +put_get_error; scanner_status:=flushing; +repeat get_t_next; +@<Decrease the string reference count...@>; +until cur_cmd>=comma; {either |end_of_statement| or |cur_cmd=comma|} +scanner_status:=normal; +end + +@ \MP's |main_control| procedure just calls |do_statement| repeatedly +until coming to the end of the user's program. +Each execution of |do_statement| concludes with +|cur_cmd=semicolon|, |end_group|, or |stop|. + +@p procedure main_control; +begin repeat do_statement; +if cur_cmd=end_group then + begin print_err("Extra `endgroup'"); +@.Extra `endgroup'@> + help2("I'm not currently working on a `begingroup',")@/ + ("so I had better not try to end anything."); + flush_error(0); + end; +until cur_cmd=stop; +end; + +@ @<Put each...@>= +primitive("end",stop,0);@/ +@!@:end_}{\&{end} primitive@> +primitive("dump",stop,1);@/ +@!@:dump_}{\&{dump} primitive@> + +@ @<Cases of |print_cmd...@>= +stop:if m=0 then print("end")@+else print("dump"); + +@* \[41] Commands. +Let's turn now to statements that are classified as ``commands'' because +of their imperative nature. We'll begin with simple ones, so that it +will be clear how to hook command processing into the |do_statement| routine; +then we'll tackle the tougher commands. + +Here's one of the simplest: + +@<Cases of |do_statement|...@>= +random_seed: do_random_seed; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_random_seed; +begin get_x_next; +if cur_cmd<>assignment then + begin missing_err(":="); +@.Missing `:='@> + help1("Always say `randomseed:=<numeric expression>'."); + back_error; + end; +get_x_next; scan_expression; +if cur_type<>known then + begin exp_err("Unknown value will be ignored"); +@.Unknown value...ignored@> + help2("Your expression was too random for me to handle,")@/ + ("so I won't change the random seed just now.");@/ + put_get_flush_error(0); + end +else @<Initialize the random seed to |cur_exp|@>; +end; + +@ @<Initialize the random seed to |cur_exp|@>= +begin init_randoms(cur_exp); +if selector>=log_only then + begin old_setting:=selector; selector:=log_only; + print_nl("{randomseed:="); print_scaled(cur_exp); print_char("}"); + print_nl(""); selector:=old_setting; + end; +end + +@ And here's another simple one (somewhat different in flavor): + +@<Cases of |do_statement|...@>= +mode_command: begin print_ln; interaction:=cur_mod; + @<Initialize the print |selector| based on |interaction|@>; + if log_opened then selector:=selector+2; + get_x_next; + end; + +@ @<Put each...@>= +primitive("batchmode",mode_command,batch_mode); +@!@:batch_mode_}{\&{batchmode} primitive@> +primitive("nonstopmode",mode_command,nonstop_mode); +@!@:nonstop_mode_}{\&{nonstopmode} primitive@> +primitive("scrollmode",mode_command,scroll_mode); +@!@:scroll_mode_}{\&{scrollmode} primitive@> +primitive("errorstopmode",mode_command,error_stop_mode); +@!@:error_stop_mode_}{\&{errorstopmode} primitive@> + +@ @<Cases of |print_cmd_mod|...@>= +mode_command: case m of + batch_mode: print("batchmode"); + nonstop_mode: print("nonstopmode"); + scroll_mode: print("scrollmode"); + othercases print("errorstopmode") + endcases; + +@ The `\&{inner}' and `\&{outer}' commands are only slightly harder. + +@<Cases of |do_statement|...@>= +protection_command: do_protection; + +@ @<Put each...@>= +primitive("inner",protection_command,0);@/ +@!@:inner_}{\&{inner} primitive@> +primitive("outer",protection_command,1);@/ +@!@:outer_}{\&{outer} primitive@> + +@ @<Cases of |print_cmd...@>= +protection_command: if m=0 then print("inner")@+else print("outer"); + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_protection; +var @!m:0..1; {0 to unprotect, 1 to protect} +@!t:halfword; {the |eq_type| before we change it} +begin m:=cur_mod; +repeat get_symbol; t:=eq_type(cur_sym); + if m=0 then + begin if t>=outer_tag then eq_type(cur_sym):=t-outer_tag; + end + else if t<outer_tag then eq_type(cur_sym):=t+outer_tag; + get_x_next; +until cur_cmd<>comma; +end; + +@ \MP\ never defines the tokens `\.(' and `\.)' to be primitives, but +plain \MP\ begins with the declaration `\&{delimiters} \.{()}'. Such a +declaration assigns the command code |left_delimiter| to `\.{(}' and +|right_delimiter| to `\.{)}'; the |equiv| of each delimiter is the +hash address of its mate. + +@<Cases of |do_statement|...@>= +delimiters: def_delims; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure def_delims; +var l_delim,r_delim:pointer; {the new delimiter pair} +begin get_clear_symbol; l_delim:=cur_sym;@/ +get_clear_symbol; r_delim:=cur_sym;@/ +eq_type(l_delim):=left_delimiter; equiv(l_delim):=r_delim;@/ +eq_type(r_delim):=right_delimiter; equiv(r_delim):=l_delim;@/ +get_x_next; +end; + +@ Here is a procedure that is called when \MP\ has reached a point +where some right delimiter is mandatory. + +@<Declare the procedure called |check_delimiter|@>= +procedure check_delimiter(@!l_delim,@!r_delim:pointer); +label exit; +begin if cur_cmd=right_delimiter then if cur_mod=l_delim then return; +if cur_sym<>r_delim then + begin missing_err(text(r_delim));@/ +@.Missing `)'@> + help2("I found no right delimiter to match a left one. So I've")@/ + ("put one in, behind the scenes; this may fix the problem."); + back_error; + end +else begin print_err("The token `"); print(text(r_delim)); +@.The token...delimiter@> + print("' is no longer a right delimiter"); + help3("Strange: This token has lost its former meaning!")@/ + ("I'll read it as a right delimiter this time;")@/ + ("but watch out, I'll probably miss it later."); + error; + end; +exit:end; + +@ The next four commands save or change the values associated with tokens. + +@<Cases of |do_statement|...@>= +save_command: repeat get_symbol; save_variable(cur_sym); get_x_next; + until cur_cmd<>comma; +interim_command: do_interim; +let_command: do_let; +new_internal: do_new_internal; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure@?do_statement; forward;@t\2@>@/ +procedure do_interim; +begin get_x_next; +if cur_cmd<>internal_quantity then + begin print_err("The token `"); +@.The token...quantity@> + if cur_sym=0 then print("(%CAPSULE)") + else print(text(cur_sym)); + print("' isn't an internal quantity"); + help1("Something like `tracingonline' should follow `interim'."); + back_error; + end +else begin save_internal(cur_mod); back_input; + end; +do_statement; +end; + +@ The following procedure is careful not to undefine the left-hand symbol +too soon, lest commands like `{\tt let x=x}' have a surprising effect. + +@<Declare action procedures for use by |do_statement|@>= +procedure do_let; +var @!l:pointer; {hash location of the left-hand symbol} +begin get_symbol; l:=cur_sym; get_x_next; +if cur_cmd<>equals then if cur_cmd<>assignment then + begin missing_err("="); +@.Missing `='@> + help3("You should have said `let symbol = something'.")@/ + ("But don't worry; I'll pretend that an equals sign")@/ + ("was present. The next token I read will be `something'."); + back_error; + end; +get_symbol; +case cur_cmd of +defined_macro,secondary_primary_macro,tertiary_secondary_macro, + expression_tertiary_macro: add_mac_ref(cur_mod); +othercases do_nothing +endcases;@/ +clear_symbol(l,false); eq_type(l):=cur_cmd; +if cur_cmd=tag_token then equiv(l):=null +else equiv(l):=cur_mod; +get_x_next; +end; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_new_internal; +begin repeat if int_ptr=max_internal then + overflow("number of internals",max_internal); +@:MetaPost capacity exceeded number of int}{\quad number of internals@> +get_clear_symbol; incr(int_ptr); +eq_type(cur_sym):=internal_quantity; equiv(cur_sym):=int_ptr; +int_name[int_ptr]:=text(cur_sym); internal[int_ptr]:=0; +get_x_next; +until cur_cmd<>comma; +end; + +@ The various `\&{show}' commands are distinguished by modifier fields +in the usual way. + +@d show_token_code=0 {show the meaning of a single token} +@d show_stats_code=1 {show current memory and string usage} +@d show_code=2 {show a list of expressions} +@d show_var_code=3 {show a variable and its descendents} +@d show_dependencies_code=4 {show dependent variables in terms of independents} + +@<Put each...@>= +primitive("showtoken",show_command,show_token_code);@/ +@!@:show_token_}{\&{showtoken} primitive@> +primitive("showstats",show_command,show_stats_code);@/ +@!@:show_stats_}{\&{showstats} primitive@> +primitive("show",show_command,show_code);@/ +@!@:show_}{\&{show} primitive@> +primitive("showvariable",show_command,show_var_code);@/ +@!@:show_var_}{\&{showvariable} primitive@> +primitive("showdependencies",show_command,show_dependencies_code);@/ +@!@:show_dependencies_}{\&{showdependencies} primitive@> + +@ @<Cases of |print_cmd...@>= +show_command: case m of + show_token_code:print("showtoken"); + show_stats_code:print("showstats"); + show_code:print("show"); + show_var_code:print("showvariable"); + othercases print("showdependencies") + endcases; + +@ @<Cases of |do_statement|...@>= +show_command:do_show_whatever; + +@ The value of |cur_mod| controls the |verbosity| in the |print_exp| routine: +if it's |show_code|, complicated structures are abbreviated, otherwise +they aren't. + +@<Declare action procedures for use by |do_statement|@>= +procedure do_show; +begin repeat get_x_next; scan_expression; +print_nl(">> "); +@.>>@> +print_exp(null,2); flush_cur_exp(0); +until cur_cmd<>comma; +end; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure disp_token; +begin print_nl("> "); +@.>\relax@> +if cur_sym=0 then @<Show a numeric or string or capsule token@> +else begin print(text(cur_sym)); print_char("="); + if eq_type(cur_sym)>=outer_tag then print("(outer) "); + print_cmd_mod(cur_cmd,cur_mod); + if cur_cmd=defined_macro then + begin print_ln; show_macro(cur_mod,null,100000); + end; {this avoids recursion between |show_macro| and |print_cmd_mod|} +@^recursion@> + end; +end; + +@ @<Show a numeric or string or capsule token@>= +begin if cur_cmd=numeric_token then print_scaled(cur_mod) +else if cur_cmd=capsule_token then + begin g_pointer:=cur_mod; print_capsule; + end +else begin print_char(""""); print(cur_mod); print_char(""""); + delete_str_ref(cur_mod); + end; +end + +@ The following cases of |print_cmd_mod| might arise in connection +with |disp_token|, although they don't correspond to any +primitive tokens. + +@<Cases of |print_cmd_...@>= +left_delimiter,right_delimiter: begin if c=left_delimiter then print("lef") + else print("righ"); + print("t delimiter that matches "); print(text(m)); + end; +tag_token:if m=null then print("tag")@+else print("variable"); +defined_macro: print("macro:"); +secondary_primary_macro,tertiary_secondary_macro,expression_tertiary_macro: + begin print_cmd_mod(macro_def,c); print("'d macro:"); + print_ln; show_token_list(link(link(m)),null,1000,0); + end; +repeat_loop:print("[repeat the loop]"); +internal_quantity:print(int_name[m]); + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_show_token; +begin repeat get_t_next; disp_token; +get_x_next; +until cur_cmd<>comma; +end; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_show_stats; +begin print_nl("Memory usage "); +@.Memory usage...@> +@!stat print_int(var_used); print_char("&"); print_int(dyn_used); +if false then@+tats@t@>@;@/ +print("unknown"); +print(" ("); print_int(hi_mem_min-lo_mem_max-1); +print(" still untouched)"); print_ln; +print_nl("String usage "); +stat print_int(strs_in_use-init_str_use); +print_char("&"); print_int(pool_in_use-init_pool_ptr); +if false then@+tats@t@>@;@/ +print("unknown"); +print(" ("); +print_int(max_strings-1-strs_used_up); print_char("&"); +print_int(pool_size-pool_ptr); print(" now untouched)"); print_ln; +get_x_next; +end; + +@ Here's a recursive procedure that gives an abbreviated account +of a variable, for use by |do_show_var|. + +@<Declare action procedures for use by |do_statement|@>= +procedure disp_var(@!p:pointer); +var @!q:pointer; {traverses attributes and subscripts} +@!n:0..max_print_line; {amount of macro text to show} +begin if type(p)=structured then @<Descend the structure@> +else if type(p)>=unsuffixed_macro then @<Display a variable macro@> +else if type(p)<>undefined then + begin print_nl(""); print_variable_name(p); print_char("="); + print_exp(p,0); + end; +end; + +@ @<Descend the structure@>= +begin q:=attr_head(p); +repeat disp_var(q); q:=link(q); +until q=end_attr; +q:=subscr_head(p); +while name_type(q)=subscr do + begin disp_var(q); q:=link(q); + end; +end + +@ @<Display a variable macro@>= +begin print_nl(""); print_variable_name(p); +if type(p)>unsuffixed_macro then print("@@#"); {|suffixed_macro|} +print("=macro:"); +if file_offset>=max_print_line-20 then n:=5 +else n:=max_print_line-file_offset-15; +show_macro(value(p),null,n); +end + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_show_var; +label done; +begin repeat get_t_next; +if cur_sym>0 then if cur_sym<=hash_end then + if cur_cmd=tag_token then if cur_mod<>null then + begin disp_var(cur_mod); goto done; + end; +disp_token; +done:get_x_next; +until cur_cmd<>comma; +end; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_show_dependencies; +var @!p:pointer; {link that runs through all dependencies} +begin p:=link(dep_head); +while p<>dep_head do + begin if interesting(p) then + begin print_nl(""); print_variable_name(p); + if type(p)=dependent then print_char("=") + else print(" = "); {extra spaces imply proto-dependency} + print_dependency(dep_list(p),type(p)); + end; + p:=dep_list(p); + while info(p)<>null do p:=link(p); + p:=link(p); + end; +get_x_next; +end; + +@ Finally we are ready for the procedure that governs all of the +show commands. + +@<Declare action procedures for use by |do_statement|@>= +procedure do_show_whatever; +begin if interaction=error_stop_mode then wake_up_terminal; +case cur_mod of +show_token_code:do_show_token; +show_stats_code:do_show_stats; +show_code:do_show; +show_var_code:do_show_var; +show_dependencies_code:do_show_dependencies; +end; {there are no other cases} +if internal[showstopping]>0 then + begin print_err("OK"); +@.OK@> + if interaction<error_stop_mode then + begin help0; decr(error_count); + end + else help1("This isn't an error message; I'm just showing something."); + if cur_cmd=semicolon then error@+else put_get_error; + end; +end; + +@ The `\&{addto}' command needs the following additional primitives: + +@d double_path_code=0 {command modifier for `\&{doublepath}'} +@d contour_code=1 {command modifier for `\&{contour}'} +@d also_code=2 {command modifier for `\&{also}'} + +@<Put each...@>= +primitive("doublepath",thing_to_add,double_path_code);@/ +@!@:double_path_}{\&{doublepath} primitive@> +primitive("contour",thing_to_add,contour_code);@/ +@!@:contour_}{\&{contour} primitive@> +primitive("also",thing_to_add,also_code);@/ +@!@:also_}{\&{also} primitive@> +primitive("withpen",with_option,pen_type);@/ +@!@:with_pen_}{\&{withpen} primitive@> +primitive("dashed",with_option,picture_type);@/ +@!@:dashed_}{\&{dashed} primitive@> +primitive("withcolor",with_option,color_type);@/ +@!@:with_color_}{\&{withcolor} primitive@> + +@ @<Cases of |print_cmd...@>= +thing_to_add:if m=contour_code then print("contour") + else if m=double_path_code then print("doublepath") + else print("also"); +with_option:if m=pen_type then print("withpen") + else if m=color_type then print("withcolor") + else print("dashed"); + +@ The |scan_with_list| procedure parses a $\langle$with list$\rangle$ and +updates the list of graphical objects starting at |p|. Each $\langle$with +clause$\rangle$ updates all graphical objects whose |type| is compatible. +Other objects are ignored. + +@<Declare action procedures for use by |do_statement|@>= +procedure scan_with_list(@!p:pointer); +label done, done1, done2; +var @!t:small_number; {|cur_mod| of the |with_option| (should match |cur_type|)} +@!q:pointer; {for list manipulation} +@!cp,@!pp,@!dp:pointer; + {objects being updated; |void| initially; |null| to suppress update} +begin cp:=void; pp:=void; dp:=void; +while cur_cmd=with_option do + begin t:=cur_mod; get_x_next; scan_expression; + if cur_type<>t then @<Complain about improper type@> + else if t=color_type then + begin if cp=void then @<Make |cp| a colored object in object list~|p|@>; + if cp<>null then + @<Transfer a color from the current expression to object~|cp|@>; + flush_cur_exp(0); + end + else if t=pen_type then + begin if pp=void then @<Make |pp| an object in list~|p| that needs + a pen@>; + if pp<>null then + begin if pen_p(pp)<>null then toss_knot_list(pen_p(pp)); + pen_p(pp):=cur_exp; cur_type:=vacuous; + end; + end + else begin if dp=void then @<Make |dp| a stroked node in list~|p|@>; + if dp<>null then + begin if dash_p(dp)<>null then delete_edge_ref(dash_p(dp)); + dash_p(dp):=make_dashes(cur_exp); + dash_scale(dp):=unity; + cur_type:=vacuous; + end; + end; + end; + @<Copy the information from objects |cp|, |pp|, and |dp| into the rest + of the list@>; +end; + +@ @<Complain about improper type@>= +begin exp_err("Improper type"); +@.Improper type@> +help2("Next time say `withpen <known pen expression>';")@/ + ("I'll ignore the bad `with' clause and look for another."); +if t=picture_type then + help_line[1]:="Next time say `dashed <known picture expression>';" +else if t=color_type then + help_line[1]:="Next time say `withcolor <known color expression>';"; +put_get_flush_error(0); +end + +@ Forcing the color to be between |0| and |unity| here guarantees that no +picture will ever contain a color outside the legal range for \ps\ graphics. + +@<Transfer a color from the current expression to object~|cp|@>= +begin q:=value(cur_exp); +red_val(cp):=value(red_part_loc(q)); +green_val(cp):=value(green_part_loc(q)); +blue_val(cp):=value(blue_part_loc(q));@/ +if red_val(cp)<0 then red_val(cp):=0; +if green_val(cp)<0 then green_val(cp):=0; +if blue_val(cp)<0 then blue_val(cp):=0; +if red_val(cp)>unity then red_val(cp):=unity; +if green_val(cp)>unity then green_val(cp):=unity; +if blue_val(cp)>unity then blue_val(cp):=unity; +end + +@ @<Make |cp| a colored object in object list~|p|@>= +begin cp:=p; +while cp<>null do + begin if has_color(cp) then goto done; + cp:=link(cp); + end; +done:do_nothing; +end + +@ @<Make |pp| an object in list~|p| that needs a pen@>= +begin pp:=p; +while pp<>null do + begin if has_pen(pp) then goto done1; + pp:=link(pp); + end; +done1:do_nothing; +end + +@ @<Make |dp| a stroked node in list~|p|@>= +begin dp:=p; +while dp<>null do + begin if type(dp)=stroked_code then goto done2; + dp:=link(dp); + end; +done2:do_nothing; +end + +@ @<Copy the information from objects |cp|, |pp|, and |dp| into...@>= +if cp>void then @<Copy |cp|'s color into the colored objects linked to~|cp|@>; +if pp>void then + @<Copy |pen_p(pp)| into stroked and filled nodes linked to |pp|@>; +if dp>void then @<Make stroked nodes linked to |dp| refer to |dash_p(dp)|@> + +@ @<Copy |cp|'s color into the colored objects linked to~|cp|@>= +begin q:=link(cp); +while q<>null do + begin if has_color(q) then + begin red_val(q):=red_val(cp); + green_val(q):=green_val(cp); + blue_val(q):=blue_val(cp);@/ + end; + q:=link(q); + end; +end + +@ @<Copy |pen_p(pp)| into stroked and filled nodes linked to |pp|@>= +begin q:=link(pp); +while q<>null do + begin if has_pen(q) then + begin if pen_p(q)<>null then toss_knot_list(pen_p(q)); + pen_p(q):=copy_pen(pen_p(pp)); + end; + q:=link(q); + end; +end + +@ @<Make stroked nodes linked to |dp| refer to |dash_p(dp)|@>= +begin q:=link(dp); +while q<>null do + begin if type(q)=stroked_code then + begin if dash_p(q)<>null then delete_edge_ref(dash_p(q)); + dash_p(q):=dash_p(dp); + dash_scale(q):=unity; + if dash_p(q)<>null then add_edge_ref(dash_p(q)); + end; + q:=link(q); + end; +end + +@ One of the things we need to do when we've parsed an \&{addto} or +similar command is find the header of a supposed \&{picture} variable, given +a token list for that variable. Since the edge structure is about to be +updated, we use |private_edges| to make sure that this is possible. + +@<Declare action procedures for use by |do_statement|@>= +function find_edges_var(@!t:pointer):pointer; +var @!p:pointer; +@!cur_edges:pointer; {the return value} +begin p:=find_variable(t); cur_edges:=null; +if p=null then + begin obliterated(t); put_get_error; + end +else if type(p)<>picture_type then + begin print_err("Variable "); show_token_list(t,null,1000,0); +@.Variable x is the wrong type@> + print(" is the wrong type ("); print_type(type(p)); print_char(")"); + help2("I was looking for a ""known"" picture variable.")@/ + ("So I'll not change anything just now."); put_get_error; + end +else begin value(p):=private_edges(value(p)); + cur_edges:=value(p); + end; +flush_node_list(t); +find_edges_var:=cur_edges; +end; + +@ @<Cases of |do_statement|...@>= +add_to_command: do_add_to; +bounds_command:do_bounds; + +@ @<Put each...@>= +primitive("clip",bounds_command,start_clip_code);@/ +@!@:clip_}{\&{clip} primitive@> +primitive("setbounds",bounds_command,start_bounds_code);@/ +@!@:set_bounds_}{\&{setbounds} primitive@> + +@ @<Cases of |print_cmd...@>= +bounds_command: if m=start_clip_code then print("clip") + else print("setbounds"); + +@ The following function parses the beginning of an \&{addto} or \&{clip} +command: it expects a variable name followed by a token with |cur_cmd=sep| +and then an expression. The function returns the token list for the variable +and stores the command modifier for the separator token in the global variable +|last_add_type|. We must be careful because this variable might get overwritten +any time we call |get_x_next|. + +@<Glob...@>= +@!last_add_type:quarterword; + {command modifier that identifies the last \&{addto} command} + +@ @<Declare action procedures for use by |do_statement|@>= +function start_draw_cmd(@!sep:quarterword):pointer; +var @!lhv:pointer; {variable to add to left} +@!add_type:quarterword; {value to be returned in |last_add_type|} +begin lhv:=null;@/ +get_x_next; var_flag:=sep; scan_primary; +if cur_type<>token_list then + @<Abandon edges command because there's no variable@> +else begin lhv:=cur_exp; add_type:=cur_mod;@/ + cur_type:=vacuous; get_x_next; scan_expression; + end; +last_add_type:=add_type; +start_draw_cmd:=lhv; +end; + +@ @<Abandon edges command because there's no variable@>= +begin exp_err("Not a suitable variable"); +@.Not a suitable variable@> +help4("At this point I needed to see the name of a picture variable.")@/ + ("(Or perhaps you have indeed presented me with one; I might")@/ + ("have missed it, if it wasn't followed by the proper token.)")@/ + ("So I'll not change anything just now."); +put_get_flush_error(0); +end + +@ Here is an example of how to use |start_draw_cmd|. + +@<Declare action procedures for use by |do_statement|@>= +procedure do_bounds; +var @!lhv,@!lhe:pointer; {variable on left, the corresponding edge structure} +@!p:pointer; {for list manipulation} +@!m:integer; {initial value of |cur_mod|} +begin m:=cur_mod; +lhv:=start_draw_cmd(to_token);@/ +if lhv<>null then + begin lhe:=find_edges_var(lhv); + if lhe=null then flush_cur_exp(0) + else if cur_type<>path_type then + begin exp_err("Improper `clip'"); +@.Improper `addto'@> + help2("This expression should have specified a known path.")@/ + ("So I'll not change anything just now."); put_get_flush_error(0); + end + else if left_type(cur_exp)=endpoint then @<Complain about a non-cycle@> + else @<Make |cur_exp| into a \&{setbounds} or clipping path and add + it to |lhe|@>; + end; +end; + +@ @<Complain about a non-cycle@>= +begin print_err("Not a cycle"); +@.Not a cycle@> +help2("That contour should have ended with `..cycle' or `&cycle'.")@/ + ("So I'll not change anything just now."); put_get_error; +end + +@ @<Make |cur_exp| into a \&{setbounds} or clipping path and add...@>= +begin p:=new_bounds_node(cur_exp,m); +link(p):=link(dummy_loc(lhe)); +link(dummy_loc(lhe)):=p;@/ +if obj_tail(lhe)=dummy_loc(lhe) then obj_tail(lhe):=p; +p:=get_node(gr_object_size[stop_type(m)]); +type(p):=stop_type(m); +link(obj_tail(lhe)):=p; +obj_tail(lhe):=p;@/ +init_bbox(lhe); +end + +@ The |do_add_to| procedure is a little like |do_clip| but there are a lot more +cases to deal with. + +@<Declare action procedures for use by |do_statement|@>= +procedure do_add_to; +var @!lhv,@!lhe:pointer; {variable on left, the corresponding edge structure} +@!p:pointer; {the graphical object or list for |scan_with_list| to update} +@!e:pointer; {an edge structure to be merged} +@!add_type:quarterword; {|also_code|, |contour_code|, or |double_path_code|} +begin lhv:=start_draw_cmd(thing_to_add); add_type:=last_add_type;@/ +if lhv<>null then + begin if add_type=also_code then + @<Make sure the current expression is a suitable picture and set |e| and |p| + appropriately@> + else @<Create a graphical object |p| based on |add_type| and the current + expression@>; + scan_with_list(p); + @<Use |p|, |e|, and |add_type| to augment |lhv| as requested@>; + end; +end; + +@ Setting |p:=null| causes the $\langle$with list$\rangle$ to be ignored; +setting |e:=null| prevents anything from being added to |lhe|. + +@ @<Make sure the current expression is a suitable picture and set |e|...@>= +begin p:=null; e:=null; +if cur_type<>picture_type then + begin exp_err("Improper `addto'"); +@.Improper `addto'@> + help2("This expression should have specified a known picture.")@/ + ("So I'll not change anything just now."); put_get_flush_error(0); + end +else begin e:=private_edges(cur_exp); cur_type:=vacuous; + p:=link(dummy_loc(e)); + end; +end + +@ In this case |add_type<>also_code| so setting |p:=null| suppresses future +attempts to add to the edge structure. + +@<Create a graphical object |p| based on |add_type| and the current...@>= +begin e:=null; p:=null; +if cur_type=pair_type then pair_to_path; +if cur_type<>path_type then + begin exp_err("Improper `addto'"); +@.Improper `addto'@> + help2("This expression should have specified a known path.")@/ + ("So I'll not change anything just now."); put_get_flush_error(0); + end +else if add_type=contour_code then + if left_type(cur_exp)=endpoint then + @<Complain about a non-cycle@> + else begin p:=new_fill_node(cur_exp); + cur_type:=vacuous; + end + else begin p:=new_stroked_node(cur_exp); + cur_type:=vacuous; + end; +end + +@ @<Use |p|, |e|, and |add_type| to augment |lhv| as requested@>= +lhe:=find_edges_var(lhv); +if lhe=null then + begin if (e=null)and(p<>null) then e:=toss_gr_object(p); + if e<>null then delete_edge_ref(e); + end +else if add_type=also_code then + if e<>null then @<Merge |e| into |lhe| and delete |e|@> + else do_nothing +else if p<>null then + begin link(obj_tail(lhe)):=p; + obj_tail(lhe):=p; + if add_type=double_path_code then + if pen_p(p)=null then pen_p(p):=get_pen_circle(0); + end + +@ @<Merge |e| into |lhe| and delete |e|@>= +begin if link(dummy_loc(e))<>null then + begin link(obj_tail(lhe)):=link(dummy_loc(e)); + obj_tail(lhe):=obj_tail(e);@/ + obj_tail(e):=dummy_loc(e); + link(dummy_loc(e)):=null; + flush_dash_list(lhe); + end; +toss_edges(e); +end + +@ @<Cases of |do_statement|...@>= +ship_out_command: do_ship_out; + +@ @<Declare action procedures for use by |do_statement|@>= +@t\4@>@<Declare the function called |tfm_check|@>@; +@t\4@>@<Declare the \ps\ output procedures@>@; +procedure do_ship_out; +var @!c:integer; {the character code} +begin get_x_next; scan_expression; +if cur_type<>picture_type then + @<Complain that it's not a known picture@> +else begin c:=round_unscaled(internal[char_code]) mod 256; + if c<0 then c:=c+256; + @<Store the width information for character code~|c|@>;@/ + ship_out(cur_exp); + flush_cur_exp(0); + end; +end; + +@ @<Complain that it's not a known picture@>= +begin exp_err("Not a known picture"); +help1("I can only output known pictures."); +put_get_flush_error(0); +end + +@ The \&{everyjob} command simply assigns a nonzero value to the global variable +|start_sym|. + +@<Cases of |do_statement|...@>= +every_job_command: begin get_symbol; start_sym:=cur_sym; get_x_next; + end; + +@ @<Glob...@>= +@!start_sym:halfword; {a symbolic token to insert at beginning of job} + +@ @<Set init...@>= +start_sym:=0; + +@ Finally, we have only the ``message'' commands remaining. + +@d message_code=0 +@d err_message_code=1 +@d err_help_code=2 + +@<Put each...@>= +primitive("message",message_command,message_code);@/ +@!@:message_}{\&{message} primitive@> +primitive("errmessage",message_command,err_message_code);@/ +@!@:err_message_}{\&{errmessage} primitive@> +primitive("errhelp",message_command,err_help_code);@/ +@!@:err_help_}{\&{errhelp} primitive@> + +@ @<Cases of |print_cmd...@>= +message_command: if m<err_message_code then print("message") + else if m=err_message_code then print("errmessage") + else print("errhelp"); + +@ @<Cases of |do_statement|...@>= +message_command: do_message; + +@ @<Declare action procedures for use by |do_statement|@>= +@<Declare a procedure called |no_string_err|@>@; +procedure do_message; +var @!m:message_code..err_help_code; {the type of message} +begin m:=cur_mod; get_x_next; scan_expression; +if cur_type<>string_type then + no_string_err("A message should be a known string expression.") +else case m of + message_code:begin print_nl(""); print(cur_exp); + end; + err_message_code:@<Print string |cur_exp| as an error message@>; + err_help_code:@<Save string |cur_exp| as the |err_help|@>; + end; {there are no other cases} +flush_cur_exp(0); +end; + +@ @<Declare a procedure called |no_string_err|@>= +procedure no_string_err(s:str_number); +begin exp_err("Not a string"); +@.Not a string@> +help1(s); +put_get_error; +end; + +@ The global variable |err_help| is zero when the user has most recently +given an empty help string, or if none has ever been given. + +@<Save string |cur_exp| as the |err_help|@>= +begin if err_help<>0 then delete_str_ref(err_help); +if length(cur_exp)=0 then err_help:=0 +else begin err_help:=cur_exp; add_str_ref(err_help); + end; +end + +@ If \&{errmessage} occurs often in |scroll_mode|, without user-defined +\&{errhelp}, we don't want to give a long help message each time. So we +give a verbose explanation only once. + +@<Glob...@>= +@!long_help_seen:boolean; {has the long \.{\\errmessage} help been used?} + +@ @<Set init...@>=long_help_seen:=false; + +@ @<Print string |cur_exp| as an error message@>= +begin print_err(""); print(cur_exp); +if err_help<>0 then use_err_help:=true +else if long_help_seen then help1("(That was another `errmessage'.)") +else begin if interaction<error_stop_mode then long_help_seen:=true; + help4("This error message was generated by an `errmessage'")@/ + ("command, so I can't give any explicit help.")@/ + ("Pretend that you're Miss Marple: Examine all clues,")@/ +@^Marple, Jane@> + ("and deduce the truth by inspired guesses."); + end; +put_get_error; use_err_help:=false; +end + +@ @<Cases of |do_statement|...@>= +write_command: do_write; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_write; +label continue; +var @!t:str_number; {the line of text to be written} + @!n,@!n0:write_index; {for searching |wr_fname| and |wr_file| arrays} + @!old_setting:0..max_selector; {for saving |selector| during output} +begin get_x_next; +scan_expression; +if cur_type<>string_type then + no_string_err("The text to be written should be a known string expression") +else if cur_cmd<>to_token then + begin print_err("Missing `to' clause"); + help1("A write command should end with `to <filename>'"); + put_get_error; + end +else begin t:=cur_exp; cur_type:=vacuous; + get_x_next; + scan_expression; + if cur_type<>string_type then + no_string_err("I can't write to that file name. It isn't a known string") + else @<Write |t| to the file named by |cur_exp|@>; + delete_str_ref(t); + end; +flush_cur_exp(0); +end; + +@ @<Write |t| to the file named by |cur_exp|@>= +begin @<Find |n| where |wr_fname[n]=cur_exp| and call |open_write_file| if + |cur_exp| must be inserted@>; +@<Make sure |eof_line| is initialized@>; +if str_vs_str(t,eof_line)=0 then + @<Record the end of file on |wr_file[n]|@> +else begin old_setting:=selector; + selector:=n; + print(t); print_ln; + selector := old_setting; + end; +end + +@ @<Find |n| where |wr_fname[n]=cur_exp| and call |open_write_file| if...@>= +n:=write_files; +n0:=write_files; +repeat +continue:if n=0 then + @<Insert |cur_exp| at index |n0| and call |open_write_file|@> +else begin decr(n); + if wr_fname[n]=0 then + begin n0:=n; goto continue; + end; + end; +until str_vs_str(cur_exp,wr_fname[n])=0 + +@ @<Insert |cur_exp| at index |n0| and call |open_write_file|@>= +begin if n0=write_files then + if write_files<max_write_files then incr(write_files) + else overflow("write files",max_write_files); +n:=n0; +open_write_file(cur_exp,n); +end + +@ @<Record the end of file on |wr_file[n]|@>= +begin a_close(wr_file[n]); +delete_str_ref(wr_fname[n]); +wr_fname[n]:=0; +if n=write_files-1 then write_files:=n; +end + + +@* \[42] Writing font metric data. +\TeX\ gets its knowledge about fonts from font metric files, also called +\.{TFM} files; the `\.T' in `\.{TFM}' stands for \TeX, +but other programs know about them too. One of \MP's duties is to +write \.{TFM} files so that the user's fonts can readily be +applied to typesetting. +@:TFM files}{\.{TFM} files@> +@^font metric files@> + +The information in a \.{TFM} file appears in a sequence of 8-bit bytes. +Since the number of bytes is always a multiple of~4, we could +also regard the file as a sequence of 32-bit words, but \MP\ uses the +byte interpretation. The format of \.{TFM} files was designed by +Lyle Ramshaw in 1980. The intent is to convey a lot of different kinds +@^Ramshaw, Lyle Harold@> +of information in a compact but useful form. + +@<Glob...@>= +@!tfm_file:byte_file; {the font metric output goes here} +@!metric_file_name: str_number; {full name of the font metric file} + +@ The first 24 bytes (6 words) of a \.{TFM} file contain twelve 16-bit +integers that give the lengths of the various subsequent portions +of the file. These twelve integers are, in order: +$$\vbox{\halign{\hfil#&$\null=\null$#\hfil\cr +|lf|&length of the entire file, in words;\cr +|lh|&length of the header data, in words;\cr +|bc|&smallest character code in the font;\cr +|ec|&largest character code in the font;\cr +|nw|&number of words in the width table;\cr +|nh|&number of words in the height table;\cr +|nd|&number of words in the depth table;\cr +|ni|&number of words in the italic correction table;\cr +|nl|&number of words in the lig/kern table;\cr +|nk|&number of words in the kern table;\cr +|ne|&number of words in the extensible character table;\cr +|np|&number of font parameter words.\cr}}$$ +They are all nonnegative and less than $2^{15}$. We must have |bc-1<=ec<=255|, +|ne<=256|, and +$$\hbox{|lf=6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+nk+ne+np|.}$$ +Note that a font may contain as many as 256 characters (if |bc=0| and |ec=255|), +and as few as 0 characters (if |bc=ec+1|). + +Incidentally, when two or more 8-bit bytes are combined to form an integer of +16 or more bits, the most significant bytes appear first in the file. +This is called BigEndian order. +@!@^BigEndian order@> + +@ The rest of the \.{TFM} file may be regarded as a sequence of ten data +arrays having the informal specification +$$\def\arr$[#1]#2${\&{array} $[#1]$ \&{of} #2} +\tabskip\centering +\halign to\displaywidth{\hfil\\{#}\tabskip=0pt&$\,:\,$\arr#\hfil + \tabskip\centering\cr +header&|[0..lh-1]@t\\{stuff}@>|\cr +char\_info&|[bc..ec]char_info_word|\cr +width&|[0..nw-1]fix_word|\cr +height&|[0..nh-1]fix_word|\cr +depth&|[0..nd-1]fix_word|\cr +italic&|[0..ni-1]fix_word|\cr +lig\_kern&|[0..nl-1]lig_kern_command|\cr +kern&|[0..nk-1]fix_word|\cr +exten&|[0..ne-1]extensible_recipe|\cr +param&|[1..np]fix_word|\cr}$$ +The most important data type used here is a |@!fix_word|, which is +a 32-bit representation of a binary fraction. A |fix_word| is a signed +quantity, with the two's complement of the entire word used to represent +negation. Of the 32 bits in a |fix_word|, exactly 12 are to the left of the +binary point; thus, the largest |fix_word| value is $2048-2^{-20}$, and +the smallest is $-2048$. We will see below, however, that all but two of +the |fix_word| values must lie between $-16$ and $+16$. + +@ The first data array is a block of header information, which contains +general facts about the font. The header must contain at least two words, +|header[0]| and |header[1]|, whose meaning is explained below. Additional +header information of use to other software routines might also be +included, and \MP\ will generate it if the \.{headerbyte} command occurs. +For example, 16 more words of header information are in use at the Xerox +Palo Alto Research Center; the first ten specify the character coding +scheme used (e.g., `\.{XEROX TEXT}' or `\.{TEX MATHSY}'), the next five +give the font family name (e.g., `\.{HELVETICA}' or `\.{CMSY}'), and the +last gives the ``face byte.'' + +\yskip\hang|header[0]| is a 32-bit check sum that \MP\ will copy into +the \.{GF} output file. This helps ensure consistency between files, +since \TeX\ records the check sums from the \.{TFM}'s it reads, and these +should match the check sums on actual fonts that are used. The actual +relation between this check sum and the rest of the \.{TFM} file is not +important; the check sum is simply an identification number with the +property that incompatible fonts almost always have distinct check sums. +@^check sum@> + +\yskip\hang|header[1]| is a |fix_word| containing the design size of the +font, in units of \TeX\ points. This number must be at least 1.0; it is +fairly arbitrary, but usually the design size is 10.0 for a ``10 point'' +font, i.e., a font that was designed to look best at a 10-point size, +whatever that really means. When a \TeX\ user asks for a font `\.{at} +$\delta$ \.{pt}', the effect is to override the design size and replace it +by $\delta$, and to multiply the $x$ and~$y$ coordinates of the points in +the font image by a factor of $\delta$ divided by the design size. {\sl +All other dimensions in the\/ \.{TFM} file are |fix_word|\kern-1pt\ +numbers in design-size units.} Thus, for example, the value of |param[6]|, +which defines the \.{em} unit, is often the |fix_word| value $2^{20}=1.0$, +since many fonts have a design size equal to one em. The other dimensions +must be less than 16 design-size units in absolute value; thus, +|header[1]| and |param[1]| are the only |fix_word| entries in the whole +\.{TFM} file whose first byte might be something besides 0 or 255. + +@ Next comes the |char_info| array, which contains one |@!char_info_word| +per character. Each word in this part of the file contains six fields +packed into four bytes as follows. + +\yskip\hang first byte: |@!width_index| (8 bits)\par +\hang second byte: |@!height_index| (4 bits) times 16, plus |@!depth_index| + (4~bits)\par +\hang third byte: |@!italic_index| (6 bits) times 4, plus |@!tag| + (2~bits)\par +\hang fourth byte: |@!remainder| (8 bits)\par +\yskip\noindent +The actual width of a character is \\{width}|[width_index]|, in design-size +units; this is a device for compressing information, since many characters +have the same width. Since it is quite common for many characters +to have the same height, depth, or italic correction, the \.{TFM} format +imposes a limit of 16 different heights, 16 different depths, and +64 different italic corrections. + +Incidentally, the relation $\\{width}[0]=\\{height}[0]=\\{depth}[0]= +\\{italic}[0]=0$ should always hold, so that an index of zero implies a +value of zero. The |width_index| should never be zero unless the +character does not exist in the font, since a character is valid if and +only if it lies between |bc| and |ec| and has a nonzero |width_index|. + +@ The |tag| field in a |char_info_word| has four values that explain how to +interpret the |remainder| field. + +\yskip\hang|tag=0| (|no_tag|) means that |remainder| is unused.\par +\hang|tag=1| (|lig_tag|) means that this character has a ligature/kerning +program starting at location |remainder| in the |lig_kern| array.\par +\hang|tag=2| (|list_tag|) means that this character is part of a chain of +characters of ascending sizes, and not the largest in the chain. The +|remainder| field gives the character code of the next larger character.\par +\hang|tag=3| (|ext_tag|) means that this character code represents an +extensible character, i.e., a character that is built up of smaller pieces +so that it can be made arbitrarily large. The pieces are specified in +|@!exten[remainder]|.\par +\yskip\noindent +Characters with |tag=2| and |tag=3| are treated as characters with |tag=0| +unless they are used in special circumstances in math formulas. For example, +\TeX's \.{\\sum} operation looks for a |list_tag|, and the \.{\\left} +operation looks for both |list_tag| and |ext_tag|. + +@d no_tag=0 {vanilla character} +@d lig_tag=1 {character has a ligature/kerning program} +@d list_tag=2 {character has a successor in a charlist} +@d ext_tag=3 {character is extensible} + +@ The |lig_kern| array contains instructions in a simple programming language +that explains what to do for special letter pairs. Each word in this array is a +|@!lig_kern_command| of four bytes. + +\yskip\hang first byte: |skip_byte|, indicates that this is the final program + step if the byte is 128 or more, otherwise the next step is obtained by + skipping this number of intervening steps.\par +\hang second byte: |next_char|, ``if |next_char| follows the current character, + then perform the operation and stop, otherwise continue.''\par +\hang third byte: |op_byte|, indicates a ligature step if less than~128, + a kern step otherwise.\par +\hang fourth byte: |remainder|.\par +\yskip\noindent +In a kern step, an +additional space equal to |kern[256*(op_byte-128)+remainder]| is inserted +between the current character and |next_char|. This amount is +often negative, so that the characters are brought closer together +by kerning; but it might be positive. + +There are eight kinds of ligature steps, having |op_byte| codes $4a+2b+c$ where +$0\le a\le b+c$ and $0\le b,c\le1$. The character whose code is +|remainder| is inserted between the current character and |next_char|; +then the current character is deleted if $b=0$, and |next_char| is +deleted if $c=0$; then we pass over $a$~characters to reach the next +current character (which may have a ligature/kerning program of its own). + +If the very first instruction of the |lig_kern| array has |skip_byte=255|, +the |next_char| byte is the so-called right boundary character of this font; +the value of |next_char| need not lie between |bc| and~|ec|. +If the very last instruction of the |lig_kern| array has |skip_byte=255|, +there is a special ligature/kerning program for a left boundary character, +beginning at location |256*op_byte+remainder|. +The interpretation is that \TeX\ puts implicit boundary characters +before and after each consecutive string of characters from the same font. +These implicit characters do not appear in the output, but they can affect +ligatures and kerning. + +If the very first instruction of a character's |lig_kern| program has +|skip_byte>128|, the program actually begins in location +|256*op_byte+remainder|. This feature allows access to large |lig_kern| +arrays, because the first instruction must otherwise +appear in a location |<=255|. + +Any instruction with |skip_byte>128| in the |lig_kern| array must satisfy +the condition +$$\hbox{|256*op_byte+remainder<nl|.}$$ +If such an instruction is encountered during +normal program execution, it denotes an unconditional halt; no ligature +command is performed. + +@d stop_flag=128+min_quarterword + {value indicating `\.{STOP}' in a lig/kern program} +@d kern_flag=128+min_quarterword {op code for a kern step} +@d skip_byte(#)==lig_kern[#].b0 +@d next_char(#)==lig_kern[#].b1 +@d op_byte(#)==lig_kern[#].b2 +@d rem_byte(#)==lig_kern[#].b3 + +@ Extensible characters are specified by an |@!extensible_recipe|, which +consists of four bytes called |@!top|, |@!mid|, |@!bot|, and |@!rep| (in this +order). These bytes are the character codes of individual pieces used to +build up a large symbol. If |top|, |mid|, or |bot| are zero, they are not +present in the built-up result. For example, an extensible vertical line is +like an extensible bracket, except that the top and bottom pieces are missing. + +Let $T$, $M$, $B$, and $R$ denote the respective pieces, or an empty box +if the piece isn't present. Then the extensible characters have the form +$TR^kMR^kB$ from top to bottom, for some |k>=0|, unless $M$ is absent; +in the latter case we can have $TR^kB$ for both even and odd values of~|k|. +The width of the extensible character is the width of $R$; and the +height-plus-depth is the sum of the individual height-plus-depths of the +components used, since the pieces are butted together in a vertical list. + +@d ext_top(#)==exten[#].b0 {|top| piece in a recipe} +@d ext_mid(#)==exten[#].b1 {|mid| piece in a recipe} +@d ext_bot(#)==exten[#].b2 {|bot| piece in a recipe} +@d ext_rep(#)==exten[#].b3 {|rep| piece in a recipe} + +@ The final portion of a \.{TFM} file is the |param| array, which is another +sequence of |fix_word| values. + +\yskip\hang|param[1]=slant| is the amount of italic slant, which is used +to help position accents. For example, |slant=.25| means that when you go +up one unit, you also go .25 units to the right. The |slant| is a pure +number; it is the only |fix_word| other than the design size itself that is +not scaled by the design size. + +\hang|param[2]=space| is the normal spacing between words in text. +Note that character @'40 in the font need not have anything to do with +blank spaces. + +\hang|param[3]=space_stretch| is the amount of glue stretching between words. + +\hang|param[4]=space_shrink| is the amount of glue shrinking between words. + +\hang|param[5]=x_height| is the size of one ex in the font; it is also +the height of letters for which accents don't have to be raised or lowered. + +\hang|param[6]=quad| is the size of one em in the font. + +\hang|param[7]=extra_space| is the amount added to |param[2]| at the +ends of sentences. + +\yskip\noindent +If fewer than seven parameters are present, \TeX\ sets the missing parameters +to zero. + +@d slant_code=1 +@d space_code=2 +@d space_stretch_code=3 +@d space_shrink_code=4 +@d x_height_code=5 +@d quad_code=6 +@d extra_space_code=7 + +@ So that is what \.{TFM} files hold. One of \MP's duties is to output such +information, and it does this all at once at the end of a job. +In order to prepare for such frenetic activity, it squirrels away the +necessary facts in various arrays as information becomes available. + +Character dimensions (\&{charwd}, \&{charht}, \&{chardp}, and \&{charic}) +are stored respectively in |tfm_width|, |tfm_height|, |tfm_depth|, and +|tfm_ital_corr|. Other information about a character (e.g., about +its ligatures or successors) is accessible via the |char_tag| and +|char_remainder| arrays. Other information about the font as a whole +is kept in additional arrays called |header_byte|, |lig_kern|, +|kern|, |exten|, and |param|. + +@d undefined_label==lig_table_size {an undefined local label} + +@<Glob...@>= +@!bc,@!ec:eight_bits; {smallest and largest character codes shipped out} +@!tfm_width:array[eight_bits] of scaled; {\&{charwd} values} +@!tfm_height:array[eight_bits] of scaled; {\&{charht} values} +@!tfm_depth:array[eight_bits] of scaled; {\&{chardp} values} +@!tfm_ital_corr:array[eight_bits] of scaled; {\&{charic} values} +@!char_exists:array[eight_bits] of boolean; {has this code been shipped out?} +@!char_tag:array[eight_bits] of no_tag..ext_tag; {|remainder| category} +@!char_remainder:array[eight_bits] of 0..lig_table_size; {the |remainder| byte} +@!header_byte:array[1..header_size] of -1..255; + {bytes of the \.{TFM} header, or $-1$ if unset} +@!lig_kern:array[0..lig_table_size] of four_quarters; {the ligature/kern table} +@!nl:0..32767-256; {the number of ligature/kern steps so far} +@!kern:array[0..max_kerns] of scaled; {distinct kerning amounts} +@!nk:0..max_kerns; {the number of distinct kerns so far} +@!exten:array[eight_bits] of four_quarters; {extensible character recipes} +@!ne:0..256; {the number of extensible characters so far} +@!param:array[1..max_font_dimen] of scaled; {\&{fontinfo} parameters} +@!np:0..max_font_dimen; {the largest \&{fontinfo} parameter specified so far} +@!nw,@!nh,@!nd,@!ni:0..256; {sizes of \.{TFM} subtables} +@!skip_table:array[eight_bits] of 0..lig_table_size; {local label status} +@!lk_started:boolean; {has there been a lig/kern step in this command yet?} +@!bchar:integer; {right boundary character} +@!bch_label:0..lig_table_size; {left boundary starting location} +@!ll,@!lll:0..lig_table_size; {registers used for lig/kern processing} +@!label_loc:array[0..256] of -1..lig_table_size; {lig/kern starting addresses} +@!label_char:array[1..256] of eight_bits; {characters for |label_loc|} +@!label_ptr:0..256; {highest position occupied in |label_loc|} + +@ @<Set init...@>= +for k:=0 to 255 do + begin tfm_width[k]:=0; tfm_height[k]:=0; tfm_depth[k]:=0; tfm_ital_corr[k]:=0; + char_exists[k]:=false; char_tag[k]:=no_tag; char_remainder[k]:=0; + skip_table[k]:=undefined_label; + end; +for k:=1 to header_size do header_byte[k]:=-1; +bc:=255; ec:=0; nl:=0; nk:=0; ne:=0; np:=0;@/ +internal[boundary_char]:=-unity; +bch_label:=undefined_label;@/ +label_loc[0]:=-1; label_ptr:=0; + +@ @<Declare the function called |tfm_check|@>= +function tfm_check(@!m:small_number):scaled; +begin if abs(internal[m])>=fraction_half then + begin print_err("Enormous "); print(int_name[m]); +@.Enormous charwd...@> +@.Enormous chardp...@> +@.Enormous charht...@> +@.Enormous charic...@> +@.Enormous designsize...@> + print(" has been reduced"); + help1("Font metric dimensions must be less than 2048pt."); + put_get_error; + if internal[m]>0 then tfm_check:=fraction_half-1 + else tfm_check:=1-fraction_half; + end +else tfm_check:=internal[m]; +end; + +@ @<Store the width information for character code~|c|@>= +if c<bc then bc:=c; +if c>ec then ec:=c; +char_exists[c]:=true; +tfm_width[c]:=tfm_check(char_wd); +tfm_height[c]:=tfm_check(char_ht); +tfm_depth[c]:=tfm_check(char_dp); +tfm_ital_corr[c]:=tfm_check(char_ic) + +@ Now let's consider \MP's special \.{TFM}-oriented commands. + +@<Cases of |do_statement|...@>= +tfm_command: do_tfm_command; + +@ @d char_list_code=0 +@d lig_table_code=1 +@d extensible_code=2 +@d header_byte_code=3 +@d font_dimen_code=4 + +@<Put each...@>= +primitive("charlist",tfm_command,char_list_code);@/ +@!@:char_list_}{\&{charlist} primitive@> +primitive("ligtable",tfm_command,lig_table_code);@/ +@!@:lig_table_}{\&{ligtable} primitive@> +primitive("extensible",tfm_command,extensible_code);@/ +@!@:extensible_}{\&{extensible} primitive@> +primitive("headerbyte",tfm_command,header_byte_code);@/ +@!@:header_byte_}{\&{headerbyte} primitive@> +primitive("fontdimen",tfm_command,font_dimen_code);@/ +@!@:font_dimen_}{\&{fontdimen} primitive@> + +@ @<Cases of |print_cmd...@>= +tfm_command: case m of + char_list_code:print("charlist"); + lig_table_code:print("ligtable"); + extensible_code:print("extensible"); + header_byte_code:print("headerbyte"); + othercases print("fontdimen") + endcases; + +@ @<Declare action procedures for use by |do_statement|@>= +function get_code:eight_bits; {scans a character code value} +label found; +var @!c:integer; {the code value found} +begin get_x_next; scan_expression; +if cur_type=known then + begin c:=round_unscaled(cur_exp); + if c>=0 then if c<256 then goto found; + end +else if cur_type=string_type then if length(cur_exp)=1 then + begin c:=so(str_pool[str_start[cur_exp]]); goto found; + end; +exp_err("Invalid code has been replaced by 0"); +@.Invalid code...@> +help2("I was looking for a number between 0 and 255, or for a")@/ + ("string of length 1. Didn't find it; will use 0 instead."); +put_get_flush_error(0); c:=0; +found: get_code:=c; +end; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure set_tag(@!c:halfword;@!t:small_number;@!r:halfword); +begin if char_tag[c]=no_tag then + begin char_tag[c]:=t; char_remainder[c]:=r; + if t=lig_tag then + begin incr(label_ptr); label_loc[label_ptr]:=r; label_char[label_ptr]:=c; + end; + end +else @<Complain about a character tag conflict@>; +end; + +@ @<Complain about a character tag conflict@>= +begin print_err("Character "); +if (c>" ")and(c<127) then print(c) +else if c=256 then print("||") +else begin print("code "); print_int(c); + end; +print(" is already "); +@.Character c is already...@> +case char_tag[c] of +lig_tag: print("in a ligtable"); +list_tag: print("in a charlist"); +ext_tag: print("extensible"); +end; {there are no other cases} +help2("It's not legal to label a character more than once.")@/ + ("So I'll not change anything just now."); +put_get_error; end + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_tfm_command; +label continue,done; +var @!c,@!cc:0..256; {character codes} +@!k:0..max_kerns; {index into the |kern| array} +@!j:integer; {index into |header_byte| or |param|} +begin case cur_mod of +char_list_code: begin c:=get_code; + {we will store a list of character successors} + while cur_cmd=colon do + begin cc:=get_code; set_tag(c,list_tag,cc); c:=cc; + end; + end; +lig_table_code: @<Store a list of ligature/kern steps@>; +extensible_code: @<Define an extensible recipe@>; +header_byte_code, font_dimen_code: begin c:=cur_mod; get_x_next; + scan_expression; + if (cur_type<>known)or(cur_exp<half_unit) then + begin exp_err("Improper location"); +@.Improper location@> + help2("I was looking for a known, positive number.")@/ + ("For safety's sake I'll ignore the present command."); + put_get_error; + end + else begin j:=round_unscaled(cur_exp); + if cur_cmd<>colon then + begin missing_err(":"); +@.Missing `:'@> + help1("A colon should follow a headerbyte or fontinfo location."); + back_error; + end; + if c=header_byte_code then @<Store a list of header bytes@> + else @<Store a list of font dimensions@>; + end; + end; +end; {there are no other cases} +end; + +@ @<Store a list of ligature/kern steps@>= +begin lk_started:=false; +continue: get_x_next; +if(cur_cmd=skip_to)and lk_started then + @<Process a |skip_to| command and |goto done|@>; +if cur_cmd=bchar_label then + begin c:=256; cur_cmd:=colon;@+end +else begin back_input; c:=get_code;@+end; +if(cur_cmd=colon)or(cur_cmd=double_colon)then + @<Record a label in a lig/kern subprogram and |goto continue|@>; +if cur_cmd=lig_kern_token then @<Compile a ligature/kern command@> +else begin print_err("Illegal ligtable step"); +@.Illegal ligtable step@> + help1("I was looking for `=:' or `kern' here."); + back_error; next_char(nl):=qi(0); op_byte(nl):=qi(0); rem_byte(nl):=qi(0);@/ + skip_byte(nl):=stop_flag+1; {this specifies an unconditional stop} + end; +if nl=lig_table_size then overflow("ligtable size",lig_table_size); +@:MetaPost capacity exceeded ligtable size}{\quad ligtable size@> +incr(nl); +if cur_cmd=comma then goto continue; +if skip_byte(nl-1)<stop_flag then skip_byte(nl-1):=stop_flag; +done:end + +@ @<Put each...@>= +primitive("=:",lig_kern_token,0); +@!@:=:_}{\.{=:} primitive@> +primitive("=:|",lig_kern_token,1); +@!@:=:/_}{\.{=:\char'174} primitive@> +primitive("=:|>",lig_kern_token,5); +@!@:=:/>_}{\.{=:\char'174>} primitive@> +primitive("|=:",lig_kern_token,2); +@!@:=:/_}{\.{\char'174=:} primitive@> +primitive("|=:>",lig_kern_token,6); +@!@:=:/>_}{\.{\char'174=:>} primitive@> +primitive("|=:|",lig_kern_token,3); +@!@:=:/_}{\.{\char'174=:\char'174} primitive@> +primitive("|=:|>",lig_kern_token,7); +@!@:=:/>_}{\.{\char'174=:\char'174>} primitive@> +primitive("|=:|>>",lig_kern_token,11); +@!@:=:/>_}{\.{\char'174=:\char'174>>} primitive@> +primitive("kern",lig_kern_token,128); +@!@:kern_}{\&{kern} primitive@> + +@ @<Cases of |print_cmd...@>= +lig_kern_token: case m of +0:print("=:"); +1:print("=:|"); +2:print("|=:"); +3:print("|=:|"); +5:print("=:|>"); +6:print("|=:>"); +7:print("|=:|>"); +11:print("|=:|>>"); +othercases print("kern") +endcases; + +@ Local labels are implemented by maintaining the |skip_table| array, +where |skip_table[c]| is either |undefined_label| or the address of the +most recent lig/kern instruction that skips to local label~|c|. In the +latter case, the |skip_byte| in that instruction will (temporarily) +be zero if there were no prior skips to this label, or it will be the +distance to the prior skip. + +We may need to cancel skips that span more than 127 lig/kern steps. + +@d cancel_skips(#)==ll:=#; + repeat lll:=qo(skip_byte(ll)); skip_byte(ll):=stop_flag; ll:=ll-lll; + until lll=0 +@d skip_error(#)==begin print_err("Too far to skip"); +@.Too far to skip@> + help1("At most 127 lig/kern steps can separate skipto1 from 1::."); + error; cancel_skips(#); + end + +@<Process a |skip_to| command and |goto done|@>= +begin c:=get_code; +if nl-skip_table[c]>128 then {|skip_table[c]<<nl<=undefined_label|} + begin skip_error(skip_table[c]); skip_table[c]:=undefined_label; + end; +if skip_table[c]=undefined_label then skip_byte(nl-1):=qi(0) +else skip_byte(nl-1):=qi(nl-skip_table[c]-1); +skip_table[c]:=nl-1; goto done; +end + +@ @<Record a label in a lig/kern subprogram and |goto continue|@>= +begin if cur_cmd=colon then + if c=256 then bch_label:=nl + else set_tag(c,lig_tag,nl) +else if skip_table[c]<undefined_label then + begin ll:=skip_table[c]; skip_table[c]:=undefined_label; + repeat lll:=qo(skip_byte(ll)); + if nl-ll>128 then + begin skip_error(ll); goto continue; + end; + skip_byte(ll):=qi(nl-ll-1); ll:=ll-lll; + until lll=0; + end; +goto continue; +end + +@ @<Compile a ligature/kern...@>= +begin next_char(nl):=qi(c); skip_byte(nl):=qi(0); +if cur_mod<128 then {ligature op} + begin op_byte(nl):=qi(cur_mod); rem_byte(nl):=qi(get_code); + end +else begin get_x_next; scan_expression; + if cur_type<>known then + begin exp_err("Improper kern"); +@.Improper kern@> + help2("The amount of kern should be a known numeric value.")@/ + ("I'm zeroing this one. Proceed, with fingers crossed."); + put_get_flush_error(0); + end; + kern[nk]:=cur_exp; + k:=0;@+while kern[k]<>cur_exp do incr(k); + if k=nk then + begin if nk=max_kerns then overflow("kern",max_kerns); +@:MetaPost capacity exceeded kern}{\quad kern@> + incr(nk); + end; + op_byte(nl):=kern_flag+(k div 256); + rem_byte(nl):=qi((k mod 256)); + end; +lk_started:=true; +end + +@ @d missing_extensible_punctuation(#)== + begin missing_err(#); +@.Missing `\char`\#'@> + help1("I'm processing `extensible c: t,m,b,r'."); back_error; + end + +@<Define an extensible recipe@>= +begin if ne=256 then overflow("extensible",256); +@:MetaPost capacity exceeded extensible}{\quad extensible@> +c:=get_code; set_tag(c,ext_tag,ne); +if cur_cmd<>colon then missing_extensible_punctuation(":"); +ext_top(ne):=qi(get_code); +if cur_cmd<>comma then missing_extensible_punctuation(","); +ext_mid(ne):=qi(get_code); +if cur_cmd<>comma then missing_extensible_punctuation(","); +ext_bot(ne):=qi(get_code); +if cur_cmd<>comma then missing_extensible_punctuation(","); +ext_rep(ne):=qi(get_code); +incr(ne); +end + +@ @<Store a list of header bytes@>= +repeat if j>header_size then overflow("headerbyte",header_size); +@:MetaPost capacity exceeded headerbyte}{\quad headerbyte@> +header_byte[j]:=get_code; incr(j); +until cur_cmd<>comma + +@ @<Store a list of font dimensions@>= +repeat if j>max_font_dimen then overflow("fontdimen",max_font_dimen); +@:MetaPost capacity exceeded fontdimen}{\quad fontdimen@> +while j>np do + begin incr(np); param[np]:=0; + end; +get_x_next; scan_expression; +if cur_type<>known then + begin exp_err("Improper font parameter"); +@.Improper font parameter@> + help1("I'm zeroing this one. Proceed, with fingers crossed."); + put_get_flush_error(0); + end; +param[j]:=cur_exp; incr(j); +until cur_cmd<>comma + +@ OK: We've stored all the data that is needed for the \.{TFM} file. +All that remains is to output it in the correct format. + +An interesting problem needs to be solved in this connection, because +the \.{TFM} format allows at most 256~widths, 16~heights, 16~depths, +and 64~italic corrections. If the data has more distinct values than +this, we want to meet the necessary restrictions by perturbing the +given values as little as possible. + +\MP\ solves this problem in two steps. First the values of a given +kind (widths, heights, depths, or italic corrections) are sorted; +then the list of sorted values is perturbed, if necessary. + +The sorting operation is facilitated by having a special node of +essentially infinite |value| at the end of the current list. + +@<Initialize table entries...@>= +value(inf_val):=fraction_four; + +@ Straight linear insertion is good enough for sorting, since the lists +are usually not terribly long. As we work on the data, the current list +will start at |link(temp_head)| and end at |inf_val|; the nodes in this +list will be in increasing order of their |value| fields. + +Given such a list, the |sort_in| function takes a value and returns a pointer +to where that value can be found in the list. The value is inserted in +the proper place, if necessary. + +At the time we need to do these operations, most of \MP's work has been +completed, so we will have plenty of memory to play with. The value nodes +that are allocated for sorting will never be returned to free storage. + +@d clear_the_list==link(temp_head):=inf_val + +@p function sort_in(@!v:scaled):pointer; +label found; +var @!p,@!q,@!r:pointer; {list manipulation registers} +begin p:=temp_head; +loop@+ begin q:=link(p); + if v<=value(q) then goto found; + p:=q; + end; +found: if v<value(q) then + begin r:=get_node(value_node_size); value(r):=v; link(r):=q; link(p):=r; + end; +sort_in:=link(p); +end; + +@ Now we come to the interesting part, where we reduce the list if necessary +until it has the required size. The |min_cover| routine is basic to this +process; it computes the minimum number~|m| such that the values of the +current sorted list can be covered by |m|~intervals of width~|d|. It +also sets the global value |perturbation| to the smallest value $d'>d$ +such that the covering found by this algorithm would be different. + +In particular, |min_cover(0)| returns the number of distinct values in the +current list and sets |perturbation| to the minimum distance between +adjacent values. + +@p function min_cover(@!d:scaled):integer; +var @!p:pointer; {runs through the current list} +@!l:scaled; {the least element covered by the current interval} +@!m:integer; {lower bound on the size of the minimum cover} +begin m:=0; p:=link(temp_head); perturbation:=el_gordo; +while p<>inf_val do + begin incr(m); l:=value(p); + repeat p:=link(p); + until value(p)>l+d; + if value(p)-l<perturbation then perturbation:=value(p)-l; + end; +min_cover:=m; +end; + +@ @<Glob...@>= +@!perturbation:scaled; {quantity related to \.{TFM} rounding} +@!excess:integer; {the list is this much too long} + +@ The smallest |d| such that a given list can be covered with |m| intervals +is determined by the |threshold| routine, which is sort of an inverse +to |min_cover|. The idea is to increase the interval size rapidly until +finding the range, then to go sequentially until the exact borderline has +been discovered. + +@p function threshold(@!m:integer):scaled; +var @!d:scaled; {lower bound on the smallest interval size} +begin excess:=min_cover(0)-m; +if excess<=0 then threshold:=0 +else begin repeat d:=perturbation; + until min_cover(d+d)<=m; + while min_cover(d)>m do d:=perturbation; + threshold:=d; + end; +end; + +@ The |skimp| procedure reduces the current list to at most |m| entries, +by changing values if necessary. It also sets |info(p):=k| if |value(p)| +is the |k|th distinct value on the resulting list, and it sets +|perturbation| to the maximum amount by which a |value| field has +been changed. The size of the resulting list is returned as the +value of |skimp|. + +@p function skimp(@!m:integer):integer; +var @!d:scaled; {the size of intervals being coalesced} +@!p,@!q,@!r:pointer; {list manipulation registers} +@!l:scaled; {the least value in the current interval} +@!v:scaled; {a compromise value} +begin d:=threshold(m); perturbation:=0; +q:=temp_head; m:=0; p:=link(temp_head); +while p<>inf_val do + begin incr(m); l:=value(p); info(p):=m; + if value(link(p))<=l+d then + @<Replace an interval of values by its midpoint@>; + q:=p; p:=link(p); + end; +skimp:=m; +end; + +@ @<Replace an interval...@>= +begin repeat p:=link(p); info(p):=m; +decr(excess);@+if excess=0 then d:=0; +until value(link(p))>l+d; +v:=l+halfp(value(p)-l); +if value(p)-v>perturbation then perturbation:=value(p)-v; +r:=q; +repeat r:=link(r); value(r):=v; +until r=p; +link(q):=p; {remove duplicate values from the current list} +end + +@ A warning message is issued whenever something is perturbed by +more than 1/16\thinspace pt. + +@p procedure tfm_warning(@!m:small_number); +begin print_nl("(some "); print(int_name[m]); +@.some charwds...@> +@.some chardps...@> +@.some charhts...@> +@.some charics...@> +print(" values had to be adjusted by as much as "); +print_scaled(perturbation); print("pt)"); +end; + +@ Here's an example of how we use these routines. +The width data needs to be perturbed only if there are 256 distinct +widths, but \MP\ must check for this case even though it is +highly unusual. + +An integer variable |k| will be defined when we use this code. +The |dimen_head| array will contain pointers to the sorted +lists of dimensions. + +@<Massage the \.{TFM} widths@>= +clear_the_list; +for k:=bc to ec do if char_exists[k] then + tfm_width[k]:=sort_in(tfm_width[k]); +nw:=skimp(255)+1; dimen_head[1]:=link(temp_head); +if perturbation>=@'10000 then tfm_warning(char_wd) + +@ @<Glob...@>= +@!dimen_head:array[1..4] of pointer; {lists of \.{TFM} dimensions} + +@ Heights, depths, and italic corrections are different from widths +not only because their list length is more severely restricted, but +also because zero values do not need to be put into the lists. + +@<Massage the \.{TFM} heights, depths, and italic corrections@>= +clear_the_list; +for k:=bc to ec do if char_exists[k] then + if tfm_height[k]=0 then tfm_height[k]:=zero_val + else tfm_height[k]:=sort_in(tfm_height[k]); +nh:=skimp(15)+1; dimen_head[2]:=link(temp_head); +if perturbation>=@'10000 then tfm_warning(char_ht); +clear_the_list; +for k:=bc to ec do if char_exists[k] then + if tfm_depth[k]=0 then tfm_depth[k]:=zero_val + else tfm_depth[k]:=sort_in(tfm_depth[k]); +nd:=skimp(15)+1; dimen_head[3]:=link(temp_head); +if perturbation>=@'10000 then tfm_warning(char_dp); +clear_the_list; +for k:=bc to ec do if char_exists[k] then + if tfm_ital_corr[k]=0 then tfm_ital_corr[k]:=zero_val + else tfm_ital_corr[k]:=sort_in(tfm_ital_corr[k]); +ni:=skimp(63)+1; dimen_head[4]:=link(temp_head); +if perturbation>=@'10000 then tfm_warning(char_ic) + +@ @<Initialize table entries...@>= +value(zero_val):=0; info(zero_val):=0; + +@ Bytes 5--8 of the header are set to the design size, unless the user has +some crazy reason for specifying them differently. + +Error messages are not allowed at the time this procedure is called, +so a warning is printed instead. + +The value of |max_tfm_dimen| is calculated so that +$$\hbox{|make_scaled(16*max_tfm_dimen,internal[design_size])|} + < \\{three\_bytes}.$$ + +@d three_bytes==@'100000000 {$2^{24}$} + +@p procedure fix_design_size; +var @!d:scaled; {the design size} +begin d:=internal[design_size]; +if (d<unity)or(d>=fraction_half) then + begin if d<>0 then + print_nl("(illegal design size has been changed to 128pt)"); +@.illegal design size...@> + d:=@'40000000; internal[design_size]:=d; + end; +if header_byte[5]<0 then if header_byte[6]<0 then + if header_byte[7]<0 then if header_byte[8]<0 then + begin header_byte[5]:=d div @'4000000; + header_byte[6]:=(d div 4096) mod 256; + header_byte[7]:=(d div 16) mod 256; + header_byte[8]:=(d mod 16)*16; + end; +max_tfm_dimen:=16*internal[design_size]-internal[design_size] div @'10000000; +if max_tfm_dimen>=fraction_half then max_tfm_dimen:=fraction_half-1; +end; + +@ The |dimen_out| procedure computes a |fix_word| relative to the +design size. If the data was out of range, it is corrected and the +global variable |tfm_changed| is increased by~one. + +@p function dimen_out(@!x:scaled):integer; +begin if abs(x)>max_tfm_dimen then + begin incr(tfm_changed); + if x>0 then x:=three_bytes-1@+else x:=1-three_bytes; + end +else x:=make_scaled(x*16,internal[design_size]); +dimen_out:=x; +end; + +@ @<Glob...@>= +@!max_tfm_dimen:scaled; {bound on widths, heights, kerns, etc.} +@!tfm_changed:integer; {the number of data entries that were out of bounds} + +@ If the user has not specified any of the first four header bytes, +the |fix_check_sum| procedure replaces them by a ``check sum'' computed +from the |tfm_width| data relative to the design size. +@^check sum@> + +@p procedure fix_check_sum; +label exit; +var @!k:eight_bits; {runs through character codes} +@!b1,@!b2,@!b3,@!b4:eight_bits; {bytes of the check sum} +@!x:integer; {hash value used in check sum computation} +begin if header_byte[1]<0 then if header_byte[2]<0 then + if header_byte[3]<0 then if header_byte[4]<0 then + begin @<Compute a check sum in |(b1,b2,b3,b4)|@>; + header_byte[1]:=b1; header_byte[2]:=b2; + header_byte[3]:=b3; header_byte[4]:=b4; return; + end; +for k:=1 to 4 do if header_byte[k]<0 then header_byte[k]:=0; +exit:end; + +@ @<Compute a check sum in |(b1,b2,b3,b4)|@>= +b1:=bc; b2:=ec; b3:=bc; b4:=ec; tfm_changed:=0; +for k:=bc to ec do if char_exists[k] then + begin x:=dimen_out(value(tfm_width[k]))+(k+4)*@'20000000; {this is positive} + b1:=(b1+b1+x) mod 255; + b2:=(b2+b2+x) mod 253; + b3:=(b3+b3+x) mod 251; + b4:=(b4+b4+x) mod 247; + end + +@ Finally we're ready to actually write the \.{TFM} information. +Here are some utility routines for this purpose. + +@d tfm_out(#)==write(tfm_file,#) {output one byte to |tfm_file|} + +@p procedure tfm_two(@!x:integer); {output two bytes to |tfm_file|} +begin tfm_out(x div 256); tfm_out(x mod 256); +end; +@# +procedure tfm_four(@!x:integer); {output four bytes to |tfm_file|} +begin if x>=0 then tfm_out(x div three_bytes) +else begin x:=x+@'10000000000; {use two's complement for negative values} + x:=x+@'10000000000; + tfm_out((x div three_bytes) + 128); + end; +x:=x mod three_bytes; tfm_out(x div unity); +x:=x mod unity; tfm_out(x div @'400); +tfm_out(x mod @'400); +end; +@# +procedure tfm_qqqq(@!x:four_quarters); {output four quarterwords to |tfm_file|} +begin tfm_out(qo(x.b0)); tfm_out(qo(x.b1)); tfm_out(qo(x.b2)); +tfm_out(qo(x.b3)); +end; + +@ @<Finish the \.{TFM} file@>= +if job_name=0 then open_log_file; +pack_job_name(".tfm"); +while not b_open_out(tfm_file) do + prompt_file_name("file name for font metrics",".tfm"); +metric_file_name:=b_make_name_string(tfm_file); +@<Output the subfile sizes and header bytes@>; +@<Output the character information bytes, then + output the dimensions themselves@>; +@<Output the ligature/kern program@>; +@<Output the extensible character recipes and the font metric parameters@>; +@!stat if internal[tracing_stats]>0 then + @<Log the subfile sizes of the \.{TFM} file@>;@;@+tats@/ +print_nl("Font metrics written on "); print(metric_file_name); print_char("."); +@.Font metrics written...@> +b_close(tfm_file) + +@ Integer variables |lh|, |k|, and |lk_offset| will be defined when we use +this code. + +@<Output the subfile sizes and header bytes@>= +k:=header_size; +while header_byte[k]<0 do decr(k); +lh:=(k+3) div 4; {this is the number of header words} +if bc>ec then bc:=1; {if there are no characters, |ec=0| and |bc=1|} +@<Compute the ligature/kern program offset and implant the + left boundary label@>; +tfm_two(6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+lk_offset+nk+ne+np); + {this is the total number of file words that will be output} +tfm_two(lh); tfm_two(bc); tfm_two(ec); tfm_two(nw); tfm_two(nh); +tfm_two(nd); tfm_two(ni); tfm_two(nl+lk_offset); tfm_two(nk); tfm_two(ne); +tfm_two(np); +for k:=1 to 4*lh do + begin if header_byte[k]<0 then header_byte[k]:=0; + tfm_out(header_byte[k]); + end + +@ @<Output the character information bytes...@>= +for k:=bc to ec do + if not char_exists[k] then tfm_four(0) + else begin tfm_out(info(tfm_width[k])); {the width index} + tfm_out((info(tfm_height[k]))*16+info(tfm_depth[k])); + tfm_out((info(tfm_ital_corr[k]))*4+char_tag[k]); + tfm_out(char_remainder[k]); + end; +tfm_changed:=0; +for k:=1 to 4 do + begin tfm_four(0); p:=dimen_head[k]; + while p<>inf_val do + begin tfm_four(dimen_out(value(p))); p:=link(p); + end; + end + +@ We need to output special instructions at the beginning of the +|lig_kern| array in order to specify the right boundary character +and/or to handle starting addresses that exceed 255. The |label_loc| +and |label_char| arrays have been set up to record all the +starting addresses; we have $-1=|label_loc|[0]<|label_loc|[1]\le\cdots +\le|label_loc|[|label_ptr]|$. + +@<Compute the ligature/kern program offset...@>= +bchar:=round_unscaled(internal[boundary_char]); +if(bchar<0)or(bchar>255)then + begin bchar:=-1; lk_started:=false; lk_offset:=0;@+end +else begin lk_started:=true; lk_offset:=1;@+end; +@<Find the minimum |lk_offset| and adjust all remainders@>; +if bch_label<undefined_label then + begin skip_byte(nl):=qi(255); next_char(nl):=qi(0); + op_byte(nl):=qi(((bch_label+lk_offset)div 256)); + rem_byte(nl):=qi(((bch_label+lk_offset)mod 256)); + incr(nl); {possibly |nl=lig_table_size+1|} + end + +@ @<Find the minimum |lk_offset|...@>= +k:=label_ptr; {pointer to the largest unallocated label} +if label_loc[k]+lk_offset>255 then + begin lk_offset:=0; lk_started:=false; {location 0 can do double duty} + repeat char_remainder[label_char[k]]:=lk_offset; + while label_loc[k-1]=label_loc[k] do + begin decr(k); char_remainder[label_char[k]]:=lk_offset; + end; + incr(lk_offset); decr(k); + until lk_offset+label_loc[k]<256; + {N.B.: |lk_offset=256| satisfies this when |k=0|} + end; +if lk_offset>0 then + while k>0 do + begin char_remainder[label_char[k]] + :=char_remainder[label_char[k]]+lk_offset; + decr(k); + end + +@ @<Output the ligature/kern program@>= +for k:=0 to 255 do if skip_table[k]<undefined_label then + begin print_nl("(local label "); print_int(k); print(":: was missing)"); +@.local label l:: was missing@> + cancel_skips(skip_table[k]); + end; +if lk_started then {|lk_offset=1| for the special |bchar|} + begin tfm_out(255); tfm_out(bchar); tfm_two(0); + end +else for k:=1 to lk_offset do {output the redirection specs} + begin ll:=label_loc[label_ptr]; + if bchar<0 then + begin tfm_out(254); tfm_out(0); + end + else begin tfm_out(255); tfm_out(bchar); + end; + tfm_two(ll+lk_offset); + repeat decr(label_ptr); + until label_loc[label_ptr]<ll; + end; +for k:=0 to nl-1 do tfm_qqqq(lig_kern[k]); +for k:=0 to nk-1 do tfm_four(dimen_out(kern[k])) + +@ @<Output the extensible character recipes...@>= +for k:=0 to ne-1 do tfm_qqqq(exten[k]); +for k:=1 to np do + if k=1 then + if abs(param[1])<fraction_half then tfm_four(param[1]*16) + else begin incr(tfm_changed); + if param[1]>0 then tfm_four(el_gordo) + else tfm_four(-el_gordo); + end + else tfm_four(dimen_out(param[k])); +if tfm_changed>0 then + begin if tfm_changed=1 then print_nl("(a font metric dimension") +@.a font metric dimension...@> + else begin print_nl("("); print_int(tfm_changed); +@.font metric dimensions...@> + print(" font metric dimensions"); + end; + print(" had to be decreased)"); + end + +@ @<Log the subfile sizes of the \.{TFM} file@>= +begin wlog_ln(' '); +if bch_label<undefined_label then decr(nl); +wlog_ln('(You used ',nw:1,'w,',@| nh:1,'h,',@| nd:1,'d,',@| ni:1,'i,',@| + nl:1,'l,',@| nk:1,'k,',@| ne:1,'e,',@| + np:1,'p metric file positions'); +wlog_ln(' out of ',@| '256w,16h,16d,64i,',@| + lig_table_size:1,'l,',max_kerns:1,'k,256e,',@| + max_font_dimen:1,'p)'); +end + +@* \[43] Reading font metric data. + +\MP\ isn't a typesetting program but it does need to find the bounding box +of a sequence of typeset characters. Thus it needs to read \.{TFM} files as +well as write them. + +@<Glob...@>= +tfm_infile:byte_file; + +@ All the width, height, and depth information is stored in an array called +|font_info|. This array is allocated sequentially and each font is stored +as a series of |char_info| words followed by the width, height, and depth +tables. Since |font_name| entries are permanent, their |str_ref| values are +set to |max_str_ref|. + +@<Types...@>= +font_number=0..font_max; + +@ @<Glob...@>= +font_info:array[0..font_mem_size] of memory_word; + {height, width, and depth data} +next_fmem:0..font_mem_size; {next unused entry in |font_info|} +last_fnum:font_number; {last font number used so far} +font_dsize:array[font_number] of scaled; + {16 times the ``design'' size in \ps\ points} +font_name:array[font_number] of str_number; + {name as specified in the \&{infont} command} +font_ps_name:array[font_number] of str_number; + {PostScript name for use when |internal[prologues]>0|} +last_ps_fnum:font_number; {last valid |font_ps_name| index} +font_bc,font_ec:array[font_number] of eight_bits; + {first and last character code} + +@ The |font_info| array is indexed via a group directory arrays. +For example, the |char_info| data for character~|c| in font~|f| will be +in |font_info[char_base[f]+c].qqqq|. + +@<Glob...@>= +char_base:array[font_number] of 0..font_mem_size; + {base address for |char_info|} +width_base:array[font_number] of 0..font_mem_size; + {index for zeroth character width} +height_base:array[font_number] of 0..font_mem_size; + {index for zeroth character height} +depth_base:array[font_number] of 0..font_mem_size; + {index for zeroth character depth} + +@ A |null_font| containing no characters is useful for error recovery. Its +|font_name| entry starts out empty but is reset each time an erroneous font is +found. This helps to cut down on the number of duplicate error messages without +wasting a lot of space. + +@d null_font=0 {the |font_number| for an empty font} + +@<Initialize table...@>= +font_dsize[null_font]:=0; +font_name[null_font]:=""; +font_ps_name[null_font]:=""; +font_bc[null_font]:=1; +font_ec[null_font]:=0;@/ +char_base[null_font]:=0; +width_base[null_font]:=0; +height_base[null_font]:=0; +depth_base[null_font]:=0;@/ +next_fmem:=0; +last_fnum:=null_font; +last_ps_fnum:=null_font; + +@ Each |char_info| word is of type |four_quarters|. The |b0| field contains +|min_quarter_word| plus the |width index|; the |b1| field contains the height +index; the |b2| fields contains the depth index, and the |b3| field used only +for temporary storage. (It is used to keep track of which characters occur in +an edge structure that is being shipped out.) +The corresponding words in the width, height, and depth tables are stored as +|scaled| values in units of \ps\ points. + +With the macros below, the |char_info| word for character~|c| in font~|f| is +|char_info(f)(c)| and the width is +$$\hbox{|char_width(f)(char_info(f)(c)).sc|.}$$ + +@d char_info_end(#)==#].qqqq +@d char_info(#)==font_info[char_base[#]+char_info_end +@d char_width_end(#)==#.b0].sc +@d char_width(#)==font_info[width_base[#]+char_width_end +@d char_height_end(#)==#.b1].sc +@d char_height(#)==font_info[height_base[#]+char_height_end +@d char_depth_end(#)==#.b2].sc +@d char_depth(#)==font_info[depth_base[#]+char_depth_end +@d ichar_exists(#)==(#.b0>min_quarterword) + +@ The |font_ps_name| for a built-in font should be what PostScript expects. +A preliminary name is obtained here from the \.{TFM} name as given in the +|fname| argument. This gets updated later from an external table if necessary. + +@d bad_tfm=11 {go here if the \.{TFM} file is bad} + +@<Declare text measuring subroutines@>= +@<Declare subroutines for parsing file names@>@; +function read_font_info(fname:str_number):font_number; +label bad_tfm,done; +var @!file_opened:boolean; {has |tfm_infile| been opened?} +@!n:font_number; {the number to return} +@!lf,@!lh,@!bc,@!ec,@!nw,@!nh,@!nd:halfword; {subfile size parameters} +@!whd_size:integer; {words needed for heights, widths, and depths} +@!i,@!ii:0..font_mem_size; {|font_info| indices} +@!jj:0..font_mem_size; {counts bytes to be ignored} +@!z:scaled; {used to compute the design size} +@!d:fraction; + {height, width, or depth as a fraction of design size times $2^{-8}$} +@!h_and_d:eight_bits; {height and depth indices being unpacked} +begin n:=null_font; +@<Open |tfm_infile| for input@>; +@<Read data from |tfm_infile|; if there is no room, say so and |goto done|; + otherwise |goto bad_tfm| or |goto done| as appropriate@>; +bad_tfm:@<Complain that the \.{TFM} file is bad@>; +done:if file_opened then b_close(tfm_infile); +if n<>null_font then + begin font_ps_name[n]:=fname; + font_name[n]:=fname; + str_ref[fname]:=max_str_ref; + end; +read_font_info:=n; +end; + +@ \MP\ doesn't bother to check the entire \.{TFM} file for errors or explain +precisely what is wrong if it does find a problem. Programs called \.{TFtoPL} +@.TFtoPL@> @.PLtoTF@> +and \.{PLtoTF} can be used to debug \.{TFM} files. + +@<Complain that the \.{TFM} file is bad@>= +print_err("Font "); +print(fname); +if file_opened then print(" not usable: TFM file is bad") +else print(" not usable: TFM file not found"); +help3("I wasn't able to read the size data for this font so this")@/ + ("`infont' operation won't produce anything. If the font name")@/ + ("is right, you might ask an expert to make a TFM file"); +if file_opened then + help_line[0]:="is right, try asking an expert to fix the TFM file"; +error + +@ @<Read data from |tfm_infile|; if there is no room, say so...@>= +@<Read the \.{TFM} size fields@>; +@<Use the size fields to allocate space in |font_info|@>; +@<Read the \.{TFM} header@>; +@<Read the character data and the width, height, and depth tables and + |goto done|@> + +@ A bad \.{TFM} file can be shorter than it claims to be. The code given here +might try to read past the end of the file if this happens. Changes will be +needed if it causes a system error to refer to |tfm_infile^| or call +|get_tfm_infile| when |eof(tfm_infile)| is true. For example, the definition +@^system dependencies@> +of |tfget| could be changed to +``|begin get(tfm_infile); if eof(tfm_infile) then goto bad_tfm; end|.'' + +@d tfget==get(tfm_infile) +@d tfbyte==tfm_infile^ +@d read_two(#)==begin #:=tfbyte; + if #>127 then goto bad_tfm; + tfget; #:=#*@'400+tfbyte; + end +@d tf_ignore(#)==for jj:=# downto 1 do tfget + +@<Read the \.{TFM} size fields@>= +read_two(lf); +tfget; read_two(lh); +tfget; read_two(bc); +tfget; read_two(ec); +if (bc>1+ec)or(ec>255) then goto bad_tfm; +tfget; read_two(nw); +tfget; read_two(nh); +tfget; read_two(nd); +whd_size:=(ec+1-bc)+nw+nh+nd; +if lf<6+lh+whd_size then goto bad_tfm; +tf_ignore(10) + +@ Offsets are added to |char_base[n]| and |width_base[n]| so that is not +necessary to apply the |so| and |qo| macros when looking up the width of a +character in the string pool. In order to ensure nonnegative |char_base| +values when |bc>0|, it may be necessary to reserve a few unused |font_info| +elements. + +@<Use the size fields to allocate space in |font_info|@>= +if next_fmem<bc+min_pool_ASCII then next_fmem:=bc+min_pool_ASCII; + {ensure nonnegative |char_base|} +if (last_fnum=font_max)or(next_fmem+whd_size>=font_mem_size) then + @<Explain that there isn't enough space and |goto done|@>; +incr(last_fnum); +n:=last_fnum; +font_bc[n]:=bc; +font_ec[n]:=ec; +char_base[n]:=next_fmem-bc-min_pool_ASCII; +width_base[n]:=next_fmem+ec-bc+1-min_quarterword; +height_base[n]:=width_base[n]+min_quarterword+nw; +depth_base[n]:=height_base[n]+nh; +next_fmem:=next_fmem+whd_size; + +@ @<Explain that there isn't enough space and |goto done|@>= +begin print_err("Font "); +print(fname); +print(" not usable: Not enough space"); +help3("This `infont' operation won't produce anything because I")@/ + ("don't have enough room to store the character-size data for")@/ + ("the font. You may have to ask a wizard to enlarge me."); +error; +goto done; +end + +@ @<Read the \.{TFM} header@>= +if lh<2 then goto bad_tfm; +tf_ignore(4); +tfget; read_two(z); +tfget; z:=z*@'400+tfbyte; +tfget; z:=z*@'400+tfbyte; {now |z| is 16 times the design size} +font_dsize[n]:=take_fraction(z,267432584); + {times ${72\over72.27}2^{28}$ to convert from \TeX\ points} +tf_ignore(4*(lh-2)) + +@ @<Read the character data and the width, height, and depth tables...@>= +ii:=width_base[n]+min_quarterword; +i:=char_base[n]+min_pool_ASCII+bc; +while i<ii do + begin tfget; font_info[i].qqqq.b0:=qi(tfbyte);@/ + tfget; h_and_d:=tfbyte; + font_info[i].qqqq.b1:=h_and_d div 16; + font_info[i].qqqq.b2:=h_and_d mod 16;@/ + tfget; tfget; + incr(i); + end; +while i<next_fmem do + @<Read a four byte dimension, scale it by the design size, store it in + |font_info[i]|, and increment |i|@>; +if eof(tfm_infile) then goto bad_tfm; +goto done + +@ The raw dimension read into |d| should have magnitude at most $2^{24}$ when +interpreted as an integer, and this includes a scale factor of $2^{20}$. Thus +we can multiply it by sixteen and think of it as a |fraction| that has been +divided by sixteen. This cancels the extra scale factor contained in +|font_dsize[n|. + +@<Read a four byte dimension, scale it by the design size, store it in...@>= +begin tfget; d:=tfbyte; +if d>=@'200 then d:=d-@'400; +tfget; d:=d*@'400+tfbyte;@/ +tfget; d:=d*@'400+tfbyte;@/ +tfget; d:=d*@'400+tfbyte;@/ +font_info[i].sc:=take_fraction(d*16,font_dsize[n]); +incr(i); +end + +@ @<Open |tfm_infile| for input@>= +file_opened:=false; +str_scan_file(fname); +if cur_area="" then cur_area:=MP_font_area; +if cur_ext="" then cur_ext:=".tfm"; +pack_cur_name; +if not b_open_in(tfm_infile) then goto bad_tfm; +file_opened:=true + +@ When we have a font name and we don't know whether it has been loaded yet, +we scan the |font_name| array before calling |read_font_info|. + +@<Declare text measuring subroutines@>= +function find_font(@!f:str_number):font_number; +label exit,found; +var @!n:font_number; +begin for n:=0 to last_fnum do + if str_vs_str(f,font_name[n])=0 then goto found; +find_font:=read_font_info(f); +return; +found:find_font:=n; +exit:end; + +@ One simple application of |find_font| is the implementation of the |font_size| +operator that gets the design size for a given font name. + +@<Find the design size of the font whose name is |cur_exp|@>= +flush_cur_exp((font_dsize[find_font(cur_exp)]+8) div 16) + +@ If we discover that the font doesn't have a requested character, we omit it +from the bounding box computation and expect the \ps\ interpreter to drop it. +This routine issues a warning message if the user has asked for it. + +@<Declare text measuring subroutines@>= +procedure lost_warning(@!f:font_number;@!k:pool_pointer); +begin if internal[tracing_lost_chars]>0 then + begin begin_diagnostic; + print_nl("Missing character: There is no "); +@.Missing character@> + print(so(str_pool[k])); print(" in font "); + print(font_name[f]); print_char("!"); end_diagnostic(false); + end; +end; + +@ The whole purpose of saving the height, width, and depth information is to be +able to find the bounding box of an item of text in an edge structure. The +|set_text_box| procedure takes a text node and adds this information. + +@<Declare text measuring subroutines@>= +procedure set_text_box(@!p:pointer); +var @!f:font_number; {|font_n(p)|} +@!bc,@!ec:pool_ASCII_code; {range of valid characters for font |f|} +@!k,kk:pool_pointer; {current character and character to stop at} +@!cc:four_quarters; {the |char_info| for the current character} +@!h,@!d:scaled; {dimensions of the current character} +begin width_val(p):=0; +height_val(p):=-el_gordo; +depth_val(p):=-el_gordo;@/ +f:=font_n(p); +bc:=si(font_bc[f]); +ec:=si(font_ec[f]);@/ +kk:=str_stop(text_p(p)); +k:=str_start[text_p(p)]; +while k<kk do + @<Adjust |p|'s bounding box to contain |str_pool[k]|; advance |k|@>; +@<Set the height and depth to zero if the bounding box is empty@>; +end; + +@ @<Adjust |p|'s bounding box to contain |str_pool[k]|; advance |k|@>= +begin if (str_pool[k]<bc)or(str_pool[k]>ec) then lost_warning(f,k) +else begin cc:=char_info(f)(str_pool[k]); + if not ichar_exists(cc) then lost_warning(f,k) + else begin width_val(p):=width_val(p)+char_width(f)(cc); + h:=char_height(f)(cc); + d:=char_depth(f)(cc); + if h>height_val(p) then height_val(p):=h; + if d>depth_val(p) then depth_val(p):=d; + end; + end; +incr(k); +end + +@ Let's hope modern compilers do comparisons correctly when the difference would +overflow. + +@<Set the height and depth to zero if the bounding box is empty@>= +if height_val(p)<-depth_val(p) then + begin height_val(p):=0; + depth_val(p):=0; + end + +@ The file |ps_tab_file| gives a table of \TeX\ font names and corresponding +PostScript names for fonts that do not have to be downloaded, i.e., fonts that +can be used when |internal[prologues]>0|. Each line consists of a \TeX\ name, +one or more spaces, a PostScript name, and possibly a space and some other junk. +This routine reads the table, updates |font_ps_name| entries starting after +|last_ps_fnum|, and sets |last_ps_fnum:=last_fnum|. If the file |ps_tab_file| +is missing, we assume that the existing font names are OK and nothing needs to +be done. + +@<Declare the \ps\ output procedures@>= +procedure read_psname_table; +label common_ending, done; +var @!k:font_number; {font for possible name match} +@!lmax:integer; {upper limit on length of name to match} +@!j:integer; {characters left to read before string gets too long} +@!c:text_char; {character being read from |ps_tab_file|} +@!s:str_number; {possible font name to match} +begin name_of_file:=ps_tab_name; +if a_open_in(ps_tab_file) then + begin @<Set |lmax| to the maximum |font_name| length for fonts + |last_ps_fnum+1| through |last_fnum|@>; + while not eof(ps_tab_file) do + begin @<Read at most |lmax| characters from |ps_tab_file| into string |s| + but |goto common_ending| if there is trouble@>; + for k:=last_ps_fnum+1 to last_fnum do + if str_vs_str(s,font_name[k])=0 then + @<|flush_string(s)|, read in |font_ps_name[k]|, and + |goto common_ending|@>; + flush_string(s); +common_ending:read_ln(ps_tab_file); + end; + last_ps_fnum:=last_fnum; + a_close(ps_tab_file); + end; +end; + +@ @<Glob...@>= +@!ps_tab_file:alpha_file; {file for font name translation table} + +@ @<Set |lmax| to the maximum |font_name| length for fonts...@>= +lmax:=0; +for k:=last_ps_fnum+1 to last_fnum do + if length(font_name[k])>lmax then lmax:=length(font_name[k]) + +@ @<Read at most |lmax| characters from |ps_tab_file| into string |s|...@>= +str_room(lmax); +j:=lmax; +loop @+begin if eoln(ps_tab_file) then + fatal_error("The psfont map file is bad!"); + read(ps_tab_file,c); + if c=' ' then goto done; + decr(j); + if j>=0 then append_char(xord[c]) + else begin flush_cur_string; + goto common_ending; + end; + end; +done:s:=make_string + +@ PostScript font names should be at most 28 characters long but we allow 32 +just to be safe. + +@<|flush_string(s)|, read in |font_ps_name[k]|, and...@>= +begin flush_string(s); +j:=32; +str_room(j); +repeat if eoln(ps_tab_file) then fatal_error("The psfont map file is bad!"); + read(ps_tab_file,c); +until c<>' '; +repeat decr(j); + if j<0 then fatal_error("The psfont map file is bad!"); + append_char(xord[c]); + if eoln(ps_tab_file) then c:=' ' @+else read(ps_tab_file,c); +until c=' '; +delete_str_ref(font_ps_name[k]); +font_ps_name[k]:=make_string; +goto common_ending; +end + +@* \[44] Shipping pictures out. +The |ship_out| procedure, to be described below, is given a pointer to +an edge structure. Its mission is to output a file containing the \ps\ +description of an edge structure. + +@ Each time an edge structure is shipped out we write a new \ps\ output +file named according to the current \&{charcode}. +@:char_code_}{\&{charcode} primitive@> + +@<Declare the \ps\ output procedures@>= +procedure open_output_file; +var @!c:integer; {\&{charcode} rounded to the nearest integer} +@!old_setting:0..max_selector; {previous |selector| setting} +@!s:str_number; {a file extension derived from |c|} +begin if job_name=0 then open_log_file; +c:=round_unscaled(internal[char_code]); +if c<0 then s:=".ps" +else @<Use |c| to compute the file extension |s|@>; +pack_job_name(s); +while not a_open_out(ps_file) do + prompt_file_name("file name for output",s); +delete_str_ref(s); +@<Store the true output file name if appropriate@>; +@<Begin the progress report for the ouput of picture~|c|@>; +end; + +@ The file extension created here could be up to five characters long in +extreme cases so it may have to be shortened on some systems. +@^system dependencies@> + +@<Use |c| to compute the file extension |s|@>= +begin old_setting:=selector; selector:=new_string; +print_char("."); print_int(c); +s:=make_string; +selector:=old_setting; +end + +@ The user won't want to see all the output file names so we only save the +first and last ones and a count of how many there were. For this purpose +files are ordered primarily by \&{charcode} and secondarily by order of +creation. +@:char_code_}{\&{charcode} primitive@> + +@<Store the true output file name if appropriate@>= +if (c<first_output_code)and(first_output_code>=0) then + begin first_output_code:=c; + delete_str_ref(first_file_name); + first_file_name:=a_make_name_string(ps_file); + end; +if c>=last_output_code then + begin last_output_code:=c; + delete_str_ref(last_file_name); + last_file_name:=a_make_name_string(ps_file); + end + +@ @<Glob...@>= +@!first_file_name,@!last_file_name:str_number; {full file names} +@!first_output_code,@!last_output_code:integer; {rounded \&{charcode} values} +@:char_code_}{\&{charcode} primitive@> +@!total_shipped:integer; {total number of |ship_out| operations completed} + +@ @<Set init...@>= +first_file_name:=""; +last_file_name:="";@/ +first_output_code:=32768; +last_output_code:=-32768;@/ +total_shipped:=0; + +@ @<Begin the progress report for the ouput of picture~|c|@>= +if term_offset>max_print_line-6 then print_ln +else if (term_offset>0)or(file_offset>0) then print_char(" "); +print_char("["); +if c>=0 then print_int(c) + +@ @<End progress report@>= +print_char("]"); +update_terminal; +incr(total_shipped) + +@ @<Explain what output files were written@>= +if total_shipped>0 then + begin print_nl(""); + print_int(total_shipped); + print(" output file"); + if total_shipped>1 then print_char("s"); + print(" written: "); + print(first_file_name); + if total_shipped>1 then + begin if 31+length(first_file_name)+length(last_file_name)>@| + max_print_line + then print_ln; + print(" .. "); + print(last_file_name); + end; + end + +@ We often need to print a pair of coordinates. + +@d ps_room(#)==if ps_offset+#>max_print_line then print_ln {optional line break} + +@<Declare the \ps\ output procedures@>= +procedure ps_pair_out(@!x,@!y:scaled); +begin ps_room(26); +print_scaled(x); print_char(" "); +print_scaled(y); print_char(" ") +end; + +@ @<Declare the \ps\ output procedures@>= +procedure ps_print(@!s:str_number); +begin ps_room(length(s)); +print(s); +end; + +@ The most important output procedure is the one that gives the \ps\ version of +a \MP\ path. + +@<Declare the \ps\ output procedures@>= +procedure ps_path_out(@!h:pointer); +label exit; +var @!p,@!q:pointer; {for scanning the path} +@!d:scaled; {a temporary value} +@!curved:boolean; {|true| unless the cubic is almost straight} +begin ps_room(40); +if need_newpath then print("newpath "); +need_newpath:=true; +ps_pair_out(x_coord(h),y_coord(h)); +print("moveto");@/ +p:=h; +repeat if right_type(p)=endpoint then + begin if p=h then ps_print(" 0 0 rlineto"); + return; + end; +q:=link(p); +@<Start a new line and print the \ps\ commands for the curve from + |p| to~|q|@>; +p:=q; +until p=h; +ps_print(" closepath"); +exit:end; + +@ @<Glob...@>= +need_newpath:boolean; + {will |ps_path_out| need to issue a \&{newpath} command next time} +@:newpath_}{\&{newpath} command@> + +@ @<Start a new line and print the \ps\ commands for the curve from...@>= +curved:=true; +@<Set |curved:=false| if the cubic from |p| to |q| is almost straight@>; +print_ln; +if curved then + begin ps_pair_out(right_x(p),right_y(p)); + ps_pair_out(left_x(q),left_y(q)); + ps_pair_out(x_coord(q),y_coord(q)); + ps_print("curveto"); + end +else if q<>h then + begin ps_pair_out(x_coord(q),y_coord(q)); + ps_print("lineto"); + end + +@ Two types of straight lines come up often in \MP\ paths: +cubics with zero initial and final velocity as created by |make_path| or +|make_envelope|, and cubics with control points uniformly spaced on a line +as created by |make_choices|. + +@d bend_tolerance=131 {allow rounding error of $2\cdot10^{-3}$} + +@<Set |curved:=false| if the cubic from |p| to |q| is almost straight@>= +if right_x(p)=x_coord(p) then + if right_y(p)=y_coord(p) then + if left_x(q)=x_coord(q) then + if left_y(q)=y_coord(q) then curved:=false; +d:=left_x(q)-right_x(p); +if abs(right_x(p)-x_coord(p)-d)<=bend_tolerance then + if abs(x_coord(q)-left_x(q)-d)<=bend_tolerance then + begin d:=left_y(q)-right_y(p); + if abs(right_y(p)-y_coord(p)-d)<=bend_tolerance then + if abs(y_coord(q)-left_y(q)-d)<=bend_tolerance then curved:=false; + end + +@ We need to keep track of several parameters from the \ps\ graphics state. +@^graphics state@> +This allows us to be sure that \ps\ has the correct values when they are +needed without wasting time and space setting them unnecessarily. + +@<Glob...@>= +@!gs_red,@!gs_green,@!gs_blue:scaled; + {color from the last \&{setrgbcolor} or \&{setgray} command} +@:setrgbcolor}{\&{setrgbcolor} command@> +@:setgray}{\&{setgray} command@> +@!gs_ljoin,@!gs_lcap:quarterword; + {values from the last \&{setlinejoin} and \&{setlinecap} commands} +@:setlinejoin}{\&{setlinejoin} command@> +@:setlinecap}{\&{setlinecap} command@> +@!gs_miterlim:scaled; {the value from the last \&{setmiterlimit} command} +@:setmiterlimit}{\&{setmiterlimit} command@> +@!gs_dash_p:pointer; {edge structure for last \&{setdash} command} +@:setdash}{\&{setdash} command@> +@!gs_dash_sc:scaled; {scale factor used with |gs_dash_p|} +@!gs_width:scaled; {width setting or $-1$ if no \&{setlinewidth} command so far} +@!gs_adj_wx:boolean; {what resolution-dependent adjustment applies to the width} +@:setlinewidth}{\&{setlinewidth} command@> + +@ To avoid making undue assumptions about the initial graphics state, these +parameters are given special values that are guaranteed not to match anything +in the edge structure being shipped out. On the other hand, the initial color +should be black so that the translation of an all-black picture will have no +\&{setcolor} commands. (These would be undesirable in a font application.) +Hence we use |c=0| when initializing the graphics state and we use |c<0| +to recover from a situation where we have lost track of the graphics state. + +@<Declare the \ps\ output procedures@>= +procedure unknown_graphics_state(c:scaled); +begin gs_red:=c; gs_green:=c; gs_blue:=c;@/ +gs_ljoin:=3; +gs_lcap:=3; +gs_miterlim:=0;@/ +gs_dash_p:=void; +gs_dash_sc:=0; +gs_width:=-1; +end; + +@ When it is time to output a graphical object, |fix_graphics_state| ensures +that \ps's idea of the graphics state agrees with what is stored in the object. + +@<Declare the \ps\ output procedures@>= +@<Declare subroutines needed by |fix_graphics_state|@>@; +procedure fix_graphics_state(p:pointer); + {get ready to output graphical object |p|} +var @!hh,@!pp:pointer; {for list manipulation} +@!wx,@!wy,@!ww:scaled; {dimensions of pen bounding box} +@!adj_wx:boolean; {whether pixel rounding should be based on |wx| or |wy|} +@!tx,@!ty:integer; {temporaries for computing |adj_wx|} +@!scf:scaled; {a scale factor for the dash pattern} +begin if has_color(p) then + @<Make sure \ps\ will use the right color for object~|p|@>; +if (type(p)=fill_code)or(type(p)=stroked_code) then + if pen_p(p)<>null then + if pen_is_elliptical(pen_p(p)) then + begin @<Generate \ps\ code that sets the stroke width to the + appropriate rounded value@>; + @<Make sure \ps\ will use the right dash pattern for |dash_p(p)|@>; + @<Decide whether the line cap parameter matters and set it if necessary@>; + @<Set the other numeric parameters as needed for object~|p|@>; + end; +if ps_offset>0 then print_ln; +end; + +@ @<Decide whether the line cap parameter matters and set it if necessary@>= +if type(p)=stroked_code then + if (left_type(path_p(p))=endpoint)or(dash_p(p)<>null) then + if gs_lcap<>lcap_val(p) then + begin ps_room(13); + print_char(" "); + print_char("0"+lcap_val(p)); print(" setlinecap"); + gs_lcap:=lcap_val(p); + end + +@ @<Set the other numeric parameters as needed for object~|p|@>= +if gs_ljoin<>ljoin_val(p) then + begin ps_room(14); + print_char(" "); + print_char("0"+ljoin_val(p)); print(" setlinejoin"); + gs_ljoin:=ljoin_val(p); + end; +if gs_miterlim<>miterlim_val(p) then + begin ps_room(27); + print_char(" "); + print_scaled(miterlim_val(p)); print(" setmiterlimit"); + gs_miterlim:=miterlim_val(p); + end + +@ @<Make sure \ps\ will use the right color for object~|p|@>= +if (gs_red<>red_val(p))or(gs_green<>green_val(p))or@| + (gs_blue<>blue_val(p)) then + begin gs_red:=red_val(p); + gs_green:=green_val(p); + gs_blue:=blue_val(p);@/ + if (gs_red=gs_green)and(gs_green=gs_blue) then + begin ps_room(16); + print_char(" "); + print_scaled(gs_red); + print(" setgray"); + end + else begin ps_room(36); + print_char(" "); + print_scaled(gs_red); print_char(" "); + print_scaled(gs_green); print_char(" "); + print_scaled(gs_blue); + print(" setrgbcolor"); + end; + end; + +@ In order to get consistent widths for horizontal and vertical pen strokes, we +want \ps\ to use an integer number of pixels for the \&{setwidth} parameter. +@:setwidth}{\&{setwidth}command@> +We set |gs_width| to the ideal horizontal or vertical stroke width and then +generate \ps\ code that computes the rounded value. For non-circular pens, the +pen shape will be rescaled so that horizontal or vertical parts of the stroke +have the computed width. + +Rounding the width to whole pixels is not likely to improve the appearance of +diagonal or curved strokes, but we do it anyway for consistency. The +\&{truncate} command generated here tends to make all the strokes a little +@:truncate}{\&{truncate} command@> +thinner, but this is appropriate for \ps's scan-conversion rules. Even with +truncation, an ideal with of $w$~pixels gets mapped into $\lfloor w\rfloor+1$. +It would be better to have $\lceil w\rceil$ but that is ridiculously expensive +to compute in \ps. + +@<Generate \ps\ code that sets the stroke width...@>= +@<Set |wx| and |wy| to the width and height of the bounding box for + |pen_p(p)|@>; +@<Use |pen_p(p)| and |path_p(p)| to decide whether |wx| or |wy| is more + important and set |adj_wx| and |ww| accordingly@>; +if (ww<>gs_width) or (adj_wx<>gs_adj_wx) then + begin if adj_wx then + begin ps_room(13); + print_char(" "); print_scaled(ww); + ps_print(" 0 dtransform exch truncate exch idtransform pop setlinewidth"); + end + else begin ps_room(15); + print(" 0 "); print_scaled(ww); + ps_print(" dtransform truncate idtransform setlinewidth pop"); + end; + gs_width := ww; + gs_adj_wx := adj_wx; + end + +@ @<Set |wx| and |wy| to the width and height of the bounding box for...@>= +pp:=pen_p(p); +if (right_x(pp)=x_coord(pp)) and (left_y(pp)=y_coord(pp)) then + begin wx := abs(left_x(pp) - x_coord(pp)); + wy := abs(right_y(pp) - y_coord(pp)); + end +else begin + wx := pyth_add(left_x(pp)-x_coord(pp), right_x(pp)-x_coord(pp)); + wy := pyth_add(left_y(pp)-y_coord(pp), right_y(pp)-y_coord(pp)); + end + +@ The path is considered ``essentially horizontal'' if its range of +$y$~coordinates is less than the $y$~range |wy| for the pen. ``Essentially +vertical'' paths are detected similarly. This code ensures that no component +of the pen transformation is more that |aspect_bound*(ww+1)|. + +@d aspect_bound=10 {``less important'' of |wx|, |wy| cannot exceed the other by + more than this factor} + +@<Use |pen_p(p)| and |path_p(p)| to decide whether |wx| or |wy| is more...@>= +tx:=1; ty:=1; +if coord_rangeOK(path_p(p), y_loc(0), wy) then tx:=aspect_bound +else if coord_rangeOK(path_p(p), x_loc(0), wx) then ty:=aspect_bound; +if wy div ty>=wx div tx then + begin ww:=wy; adj_wx:=false; + end +else begin ww:=wx; adj_wx:=true; + end + +@ This routine quickly tests if path |h| is ``essentially horizontal'' or +``essentially vertical,'' where |zoff| is |x_loc(0)| or |y_loc(0)| and |dz| is +allowable range for $x$ or~$y$. We do not need and cannot afford a full +bounding-box computation. + +@<Declare subroutines needed by |fix_graphics_state|@>= +function coord_rangeOK(@!h:pointer; @!zoff:small_number; dz:scaled):boolean; +label found, not_found, exit; +var @!p:pointer; {for scanning the path form |h|} + @!zlo,@!zhi:scaled; {coordinate range so far} + @!z:scaled; {coordinate currently being tested} +begin zlo:=knot_coord(h+zoff); +zhi:=zlo; +p:=h; +while right_type(p)<>endpoint do + begin z:=right_coord(p+zoff);@/ + @<Make |zlo..zhi| include |z| and |goto found| if |zhi-zlo>dz|@>; + p:=link(p); + z:=left_coord(p+zoff);@/ + @<Make |zlo..zhi| include |z| and |goto found| if |zhi-zlo>dz|@>; + z:=knot_coord(p+zoff);@/ + @<Make |zlo..zhi| include |z| and |goto found| if |zhi-zlo>dz|@>; + if p=h then goto not_found; + end; +not_found:coord_rangeOK:=true; +return; +found:coord_rangeOK:=false; +exit:end; + +@ @<Make |zlo..zhi| include |z| and |goto found| if |zhi-zlo>dz|@>= +if z<zlo then zlo:=z +else if z>zhi then zhi:=z; +if zhi-zlo>dz then goto found + +@ Filling with an elliptical pen is implemented via a combination of \&{stroke} +and \&{fill} commands and a nontrivial dash pattern would interfere with this. +@:stroke}{\&{stroke} command@> +@:fill}{\&{fill} command@> +Note that we don't use |delete_edge_ref| because |gs_dash_p| is not counted as +a reference. + +@<Make sure \ps\ will use the right dash pattern for |dash_p(p)|@>= +if type(p)=fill_code then hh:=null +else begin hh:=dash_p(p); + scf:=get_pen_scale(pen_p(p)); + if scf=0 then + if gs_width=0 then scf:=dash_scale(p) @+else hh:=null + else begin scf:=make_scaled(gs_width,scf); + scf:=take_scaled(scf,dash_scale(p)); + end; + end; +if hh=null then + begin if gs_dash_p<>null then + begin ps_print(" [] 0 setdash"); + gs_dash_p:=null; + end; + end +else if (gs_dash_sc<>scf) or not same_dashes(gs_dash_p,hh) then + @<Set the dash pattern from |dash_list(hh)| scaled by |scf|@> + +@ Translating a dash list into \ps\ is very similar to printing it symbolically +in |print_edges|. A dash pattern with |dash_y(hh)=0| has length zero and is +ignored. The same fate applies in the bizarre case of a dash pattern that +cannot be printed without overflow. + +@<Set the dash pattern from |dash_list(hh)| scaled by |scf|@>= +begin gs_dash_p:=hh; +gs_dash_sc:=scf; +if (dash_y(hh)=0) or (abs(dash_y(hh)) div unity >= el_gordo div scf) then + ps_print(" [] 0 setdash") +else begin pp:=dash_list(hh); + start_x(null_dash):=start_x(pp)+dash_y(hh);@/ + ps_room(28); + print(" ["); + while pp<>null_dash do + begin ps_pair_out(take_scaled(stop_x(pp)-start_x(pp),scf),@| + take_scaled(start_x(link(pp))-stop_x(pp),scf)); + pp:=link(pp); + end; + ps_room(22); + print("] "); + print_scaled(take_scaled(dash_offset(hh),scf)); + print(" setdash"); + end; +end + +@ @<Declare subroutines needed by |fix_graphics_state|@>= +function same_dashes(@!h,@!hh:pointer):boolean; + {do |h| and |hh| represent the same dash pattern?} +label done; +var @!p,@!pp:pointer; {dash nodes being compared} +begin if h=hh then same_dashes:=true +else if (h<=void)or(hh<=void) then same_dashes:=false +else if dash_y(h)<>dash_y(hh) then same_dashes:=false +else @<Compare |dash_list(h)| and |dash_list(hh)|@>; +end; + +@ @<Compare |dash_list(h)| and |dash_list(hh)|@>= +begin p:=dash_list(h); +pp:=dash_list(hh); +while (p<>null_dash)and(pp<>null_dash) do + if (start_x(p)<>start_x(pp))or(stop_x(p)<>stop_x(pp)) then goto done + else begin p:=link(p); + pp:=link(pp); + end; +done:same_dashes:=p=pp; +end + +@ When stroking a path with an elliptical pen, it is necessary to transform +the coordinate system so that a unit circular pen will have the desired shape. +To keep this transformation local, we enclose it in a +$$\&{gsave}\ldots\&{grestore}$$ +block. Any translation component must be applied to the path being stroked +while the rest of the transformation must apply only to the pen. +If |fill_also=true|, the path is to be filled as well as stroked so we must +insert commands to do this after giving the path. + +@<Declare the \ps\ output procedures@>= +procedure stroke_ellipse(@!h:pointer;@!fill_also:boolean); + {generate an elliptical pen stroke from object |h|} +var @!txx,@!txy,@!tyx,@!tyy:scaled; {transformation parameters} +@!p:pointer; {the pen to stroke with} +@!d1,@!det:scaled; {for tweaking transformation parameters} +@!s:integer; {also for tweaking transformation paramters} +@!transformed:boolean; {keeps track of whether gsave/grestore are needed} +begin transformed:=false;@/ +@<Use |pen_p(h)| to set the transformation parameters and give the initial + translation@>; +@<Tweak the transformation parameters so the transformation is nonsingular@>; +ps_path_out(path_p(h));@/ +if fill_also then print_nl("gsave fill grestore"); +@<Issue \ps\ commands to transform the coordinate system@>; +ps_print(" stroke"); +if transformed then ps_print(" grestore"); +print_ln; +end; + +@ @<Use |pen_p(h)| to set the transformation parameters and give the...@>= +p:=pen_p(h); +txx:=left_x(p); +tyx:=left_y(p);@/ +txy:=right_x(p); +tyy:=right_y(p); +if (x_coord(p)<>0)or(y_coord(p)<>0) then + begin print_nl("gsave "); + ps_pair_out(x_coord(p),y_coord(p)); + ps_print("translate ");@/ + txx:=txx-x_coord(p); + tyx:=tyx-y_coord(p);@/ + txy:=txy-x_coord(p); + tyy:=tyy-y_coord(p); + transformed:=true; + end +else print_nl(""); +@<Adjust the transformation to account for |gs_width| and output the + initial \&{gsave} if |transformed| should be |true|@> + +@ @<Adjust the transformation to account for |gs_width| and output the...@>= +if gs_width<>unity then + if gs_width=0 then + begin txx:=unity; tyy:=unity; + end + else begin txx:=make_scaled(txx,gs_width); + txy:=make_scaled(txy,gs_width); + tyx:=make_scaled(tyx,gs_width); + tyy:=make_scaled(tyy,gs_width); + end; +if (txy<>0)or(tyx<>0)or(txx<>unity)or(tyy<>unity) then + if (not transformed) then + begin ps_print("gsave "); + transformed:=true; + end + +@ @<Issue \ps\ commands to transform the coordinate system@>= +if (txy<>0)or(tyx<>0) then + begin print_ln; + print_char("["); + ps_pair_out(txx,tyx); + ps_pair_out(txy,tyy);@/ + ps_print("0 0] concat"); + end +else if (txx<>unity)or(tyy<>unity) then + begin print_ln; + ps_pair_out(txx,tyy); + print("scale"); + end + +@ The \ps\ interpreter will probably abort if it encounters a singular +transformation matrix. The determinant must be large enough to ensure that +the printed representation will be nonsingular. Since the printed +representation is always within $2^{-17}$ of the internal |scaled| value, the +total error is at most $4T_{\rm max}2^{-17}$, where $T_{\rm max}$ is a bound on +the magnitudes of |txx/65536|, |txy/65536|, etc. + +The |aspect_bound*(gs_width+1)| bound on the components of the pen +transformation allows $T_{\rm max}$ to be at most |2*aspect_bound|. + +@<Tweak the transformation parameters so the transformation is nonsingular@>= +det:=take_scaled(txx,tyy) - take_scaled(txy,tyx); +d1:=4*aspect_bound+1; +if abs(det)<d1 then + begin if det>=0 then + begin d1:=d1-det; s:=1; @+end + else begin d1:=-d1-det; s:=-1; @+end; + d1:=d1*unity; + if abs(txx)+abs(tyy)>=abs(txy)+abs(tyy) then + if abs(txx)>abs(tyy) then tyy:=tyy+(d1+s*abs(txx)) div txx + else txx:=txx+(d1+s*abs(tyy)) div tyy + else if abs(txy)>abs(tyx) then tyx:=tyx+(d1+s*abs(txy)) div txy + else txy:=txy+(d1+s*abs(tyx)) div tyx; + end + +@ Here is a simple routine that just fills a cycle. + +@<Declare the \ps\ output procedures@>= +procedure ps_fill_out(@!p:pointer); {fill cyclic path~|p|} +begin ps_path_out(p); +ps_print(" fill"); +print_ln; +end; + +@ Given a cyclic path~|p| and a graphical object~|h|, the |do_outer_envelope| +procedure fills the cycle generated by |make_envelope|. It need not do +anything unless some region has positive winding number with respect to~|p|, +but it does not seem worthwhile to for test this. + +@<Declare the \ps\ output procedures@>= +procedure do_outer_envelope(@!p,@!h:pointer); +begin p:=make_envelope(p, pen_p(h), ljoin_val(h), 0, miterlim_val(h)); +ps_fill_out(p); +toss_knot_list(p); +end; + +@ A text node may specify an arbitrary transformation but the usual case +involves only shifting, scaling, and occasionally rotation. The purpose +of |choose_scale| is to select a scale factor so that the remaining +transformation is as ``nice'' as possible. The definition of ``nice'' +is somewhat arbitrary but shifting and $90^\circ$ rotation are especially +nice because they work out well for bitmap fonts. The code here selects +a scale factor equal to $1/\sqrt2$ times the Frobenius norm of the +non-shifting part of the transformation matrix. It is careful to avoid +additions that might cause undetected overflow. + +@<Declare the \ps\ output procedures@>= +function choose_scale(@!p:pointer):scaled; {|p| should point to a text node} +var @!a,@!b,@!c,@!d,@!ad,@!bc:scaled; {temporary values} +begin a:=txx_val(p); +b:=txy_val(p); +c:=tyx_val(p); +d:=tyy_val(p);@/ +if (a<0) then negate(a); +if (b<0) then negate(b); +if (c<0) then negate(c); +if (d<0) then negate(d); +ad:=half(a-d); +bc:=half(b-c);@/ +choose_scale:=pyth_add(pyth_add(d+ad,ad), pyth_add(c+bc,bc)); +end; + +@ @<Declare the \ps\ output procedures@>= +procedure ps_string_out(s:str_number); +var @!i:pool_pointer; {current character code position} +@!k:ASCII_code; {bits to be converted to octal} +begin print("("); +i:=str_start[s]; +while i<str_stop(s) do + begin if ps_offset+5>max_print_line then + begin print_char("\"); + print_ln; + end; + k:=so(str_pool[i]); + if (@<Character |k| is not allowed in PostScript output@>) then + begin print_char("\"); + print_char("0"+(k div 64)); + print_char("0"+((k div 8) mod 8)); + print_char("0"+(k mod 8)); + end + else begin if (k="(")or(k=")")or(k="\") then print_char("\"); + print_char(k); + end; + incr(i); + end; +print(")"); +end; + +@ @<Declare the \ps\ output procedures@>= +function is_ps_name(@!s:str_number):boolean; +label not_found,exit; +var @!i:pool_pointer; {current character code position} +@!k:ASCII_code; {the character being checked} +begin i:=str_start[s]; +while i<str_stop(s) do + begin k:=so(str_pool[i]); + if (k<=" ")or(k>"~") then goto not_found; + if (k="(")or(k=")")or(k="<")or(k=">")or@| + (k="{")or(k="}")or(k="/")or(k="%") then goto not_found; + incr(i); + end; +is_ps_name:=true; +return; +not_found:is_ps_name:=false; +exit:end; + +@ @<Declare the \ps\ output procedures@>= +procedure ps_name_out(@!s:str_number;@!lit:boolean); +begin ps_room(length(s)+2); +print_char(" "); +if is_ps_name(s) then + begin if lit then print_char("/"); + print(s); + end + else begin ps_string_out(s); + if not lit then ps_print("cvx "); + ps_print("cvn"); + end; +end; + +@ We also need to keep track of which characters are used in text nodes +in the edge structure that is being shipped out. This is done by procedures +that use the left-over |b3| field in the |char_info| words; i.e., +|char_info(f)(c).b3| gives the status of character |c| in font |f|. + +@d unused=0 +@d used=1 + +@ @<Declare the \ps\ output procedures@>= +procedure unmark_font(@!f:font_number); +var @!k:0..font_mem_size; {an index into |font_info|} +begin for k:= char_base[f]+si(font_bc[f]) to char_base[f]+si(font_ec[f]) do + font_info[k].qqqq.b3:=unused; +end; + +@ @<Declare the \ps\ output procedures@>= +procedure mark_string_chars(@!f:font_number;@!s:str_number); +var @!b:integer; {|char_base[f]|} +@!bc,@!ec:pool_ASCII_code; {only characters between these bounds are marked} +@!k:pool_pointer; {an index into string |s|} +begin b:=char_base[f]; +bc:=si(font_bc[f]); +ec:=si(font_ec[f]);@/ +k:=str_stop(s); +while k>str_start[s] do + begin decr(k); + if (str_pool[k]>=bc)and(str_pool[k]<=ec) then + font_info[b+str_pool[k]].qqqq.b3:=used; + end +end; + +@ @<Declare the \ps\ output procedures@>= +procedure hex_digit_out(@!d:small_number); +begin if d<10 then print_char(d+"0") +else print_char(d+"a"-10); +end; + +@ We output the marks as a hexadecimal bit string starting at |c| or +|font_bc[f]|, whichever is greater. If the output has to be truncated +to avoid exceeding |emergency_line_length| the return value says where to +start scanning next time. + +@<Declare the \ps\ output procedures@>= +function ps_marks_out(@!f:font_number;@!c:eight_bits):halfword; +var @!bc,@!ec:eight_bits; {only encode characters between these bounds} +@!lim:integer; {the maximum number of marks to encode before truncating} +@!p:0..font_mem_size; {|font_info| index for the current character} +@!d,@!b:0..15; {used to construct a hexadecimal digit} +begin lim:=4*(emergency_line_length-ps_offset-4); +bc:=font_bc[f]; +ec:=font_ec[f]; +if c>bc then bc:=c; +@<Restrict the range |bc..ec| so that it contains no unused characters + at either end and has length at most |lim|@>; +@<Print the initial label indicating that the bitmap starts at |bc|@>; +@<Print a hexadecimal encoding of the marks for characters |bc..ec|@>; +while (ec<font_ec[f])and(font_info[p].qqqq.b3=unused) do + begin incr(p); incr(ec); + end; +ps_marks_out:=ec+1; +end; + +@ We could save time by setting the return value before the loop that +decrements |ec|, but there is no point in being so tricky. + +@<Restrict the range |bc..ec| so that it contains no unused characters...@>= +p:=char_base[f]+si(bc); +while (font_info[p].qqqq.b3=unused)and(bc<ec) do + begin incr(p); incr(bc); + end; +if ec>=bc+lim then ec:=bc+lim-1; +p:=char_base[f]+si(ec); +while (font_info[p].qqqq.b3=unused)and(bc<ec) do + begin decr(p); decr(ec); + end; + +@ @<Print the initial label indicating that the bitmap starts at |bc|@>= +print_char(" "); +hex_digit_out(bc div 16); +hex_digit_out(bc mod 16); +print_char(":") + +@ @<Print a hexadecimal encoding of the marks for characters |bc..ec|@>= +b:=8; d:=0; +for p:=char_base[f]+si(bc) to char_base[f]+si(ec) do + begin if b=0 then + begin hex_digit_out(d); + d:=0; b:=8; + end; + if font_info[p].qqqq.b3<>unused then d:=d+b; + b:=halfp(b); + end; +hex_digit_out(d) + +@ Here is a simple function that determines whether there are any marked +characters in font~|f| with character code at least~|c|. + +@<Declare the \ps\ output procedures@>= +function check_ps_marks(@!f:font_number; @!c:integer):boolean; +label exit; +var @!p:0..font_mem_size; {|font_info| index for the current character} +begin for p:=char_base[f]+si(c) to char_base[f]+si(font_ec[f]) do + if font_info[p].qqqq.b3=used then + begin check_ps_marks:=true; return; + end; +check_ps_marks:=false; +exit: end; + +@ There may be many sizes of one font and we need to keep track of the +characters used for each size. This is done by keeping a linked list of +sizes for each font with a counter in each text node giving the appropriate +position in the size list for its font. + +@d sc_factor(#)==mem[#+1].sc {the scale factor stored in a font size node} +@d font_size_size=2 {size of a font size node} + +@<Glob...@>= +font_sizes:array[font_number] of pointer; + +@ @d fscale_tolerance==65 {that's $.001\times2^{16}$} + +@<Declare the \ps\ output procedures@>= +function size_index(@!f:font_number;@!s:scaled):quarterword; +label found; +var @!p,@!q:pointer; {the previous and current font size nodes} +@!i:quarterword; {the size index for |q|} +begin q:=font_sizes[f]; +i:=0; +while q<>null do + begin if abs(s-sc_factor(q))<=fscale_tolerance then goto found + else begin p:=q; q:=link(q); + incr(i); + end; + if i=max_quarterword then + overflow("sizes per font",max_quarterword); +@:MetaPost capacity exceeded sizes per font}{\quad sizes per font@> + end; +q:=get_node(font_size_size); +sc_factor(q):=s; +if i=0 then font_sizes[f]:=q @+else link(p):=q; +found:size_index:=i; +end; + +@ @<Declare the \ps\ output procedures@>= +function indexed_size(@!f:font_number;@!j:quarterword):scaled; +var @!p:pointer; {a font size node} +@!i:quarterword; {the size index for |p|} +begin p:=font_sizes[f]; +i:=0; +if p=null then confusion("size"); +while (i<>j) do + begin incr(i); p:=link(p); + if p=null then confusion("size"); + end; +indexed_size:=sc_factor(p); +end; + +@ @<Declare the \ps\ output procedures@>= +procedure clear_sizes; +var @!f:font_number; {the font whose size list is being cleared} +@!p:pointer; {current font size nodes} +begin for f:=null_font+1 to last_fnum do + while font_sizes[f]<>null do + begin p:=font_sizes[f]; + font_sizes[f]:=link(p); + free_node(p,font_size_size); + end; +end; + +@ The \&{special} command saves up lines of text to be printed during the next +|ship_out| operation. The saved items are stored as a list of capsule tokens. + +@<Glob...@>= +@!last_pending:pointer; {the last token in a list of pending specials} + +@ @<Set init...@>= +last_pending:=spec_head; + +@ @<Cases of |do_statement|...@>= +special_command:do_special; + +@ @<Declare action procedures for use by |do_statement|@>= +procedure do_special; +begin get_x_next; scan_expression; +if cur_type<>string_type then @<Complain about improper special operation@> +else begin link(last_pending):=stash_cur_exp; + last_pending:=link(last_pending); + link(last_pending):=null; + end; +end; + +@ @<Complain about improper special operation@>= +begin exp_err("Unsuitable expression"); +help1("Only known strings are allowed for output as specials."); +put_get_error; +end + +@ @<Print any pending specials@>= +t:=link(spec_head); +while t<>null do + begin if length(value(t))<=emergency_line_length then print(value(t)) + else overflow("output line length",emergency_line_length); +@:MetaPost capacity exceeded output line length}{\quad output line length@> + print_ln; + t:=link(t); + end; +flush_token_list(link(spec_head)); +link(spec_head):=null; +last_pending:=spec_head + +@ We are now ready for the main output procedure. Note that the |selector| +setting is saved in a global variable so that |begin_diagnostic| can access it. + +@<Declare the \ps\ output procedures@>= +procedure ship_out(@!h:pointer); {output edge structure |h|} +label done,found; +var @!p:pointer; {the current graphical object} +@!q:pointer; {something that |p| points to} +@!t:integer; {a temporary value} +@!f,ff:font_number; {fonts used in a text node or as loop counters} +@!ldf:font_number; {the last \.{DocumentFont} listed (otherwise |null_font|)} +@!done_fonts:boolean; {have we finished listing the fonts in the header?} +@!next_size:quarterword; {the size index for fonts being listed} +@!cur_fsize:array[font_number] of pointer; {current positions in |font_sizes|} +@!ds,@!scf:scaled; {design size and scale factor for a text node} +@!transformed:boolean; {is the coordinate system being transformed?} +begin open_output_file; +if (internal[prologues]>0) and (last_ps_fnum<last_fnum) then + read_psname_table; +non_ps_setting:=selector; selector:=ps_file_only;@/ +@<Print the initial comment and give the bounding box for edge structure~|h|@>; +print("%%BeginProlog"); print_ln; +if internal[prologues]>0 then @<Print the prologue@>; +print("%%EndProlog"); +print_nl("%%Page: 1 1"); print_ln; +@<Print any pending specials@>; +unknown_graphics_state(0); +need_newpath:=true; +p:=link(dummy_loc(h)); +while p<>null do + begin fix_graphics_state(p); + case type(p) of + @<Cases for translating graphical object~|p| into \ps@>@; + start_bounds_code,stop_bounds_code: do_nothing; + end; {all cases are enumerated} + p:=link(p); + end; +print("showpage"); print_ln; +print("%%EOF"); print_ln; +a_close(ps_file); +selector:=non_ps_setting; +if internal[prologues]<=0 then clear_sizes; +@<End progress report@>; +if internal[tracing_output]>0 then print_edges(h," (just shipped out)",true); +end; + +@ These special comments described in the {\sl PostScript Language Reference +Manual}, 2nd.~edition are understood by some \ps-reading programs. +We can't normally output ``conforming'' \ps\ because +the structuring conventions don't allow us to say ``Please make sure the +following characters are downloaded and define the \.{fshow} macro to access +them.'' + +The exact bounding box is written out if |prologues<0|, although this +is not standard \ps, since it allows \TeX\ to calculate the box dimensions +accurately. (Overfull boxes are avoided if an illustration is made to +match a given \.{\char`\\hsize}.) + +@<Print the initial comment and give the bounding box for edge...@>= +print("%!PS"); +if internal[prologues]>0 then print("-Adobe-3.0 EPSF-3.0"); +print_nl("%%BoundingBox: "); +set_bbox(h,true); +if minx_val(h)>maxx_val(h) then print("0 0 0 0") +else if internal[prologues]<0 then + begin ps_pair_out(minx_val(h),miny_val(h)); + ps_pair_out(maxx_val(h),maxy_val(h)); + end +else begin ps_pair_out(floor_scaled(minx_val(h)),floor_scaled(miny_val(h))); + ps_pair_out(-floor_scaled(-maxx_val(h)),-floor_scaled(-maxy_val(h))); + end; +print_nl("%%HiResBoundingBox: "); +if minx_val(h)>maxx_val(h) then print("0 0 0 0") +else begin + ps_pair_out(minx_val(h),miny_val(h)); + ps_pair_out(maxx_val(h),maxy_val(h)); + end; +print_nl("%%Creator: MetaPost "); +print(metapost_version); +print_nl("%%CreationDate: "); +print_int(round_unscaled(internal[year])); print_char("."); +print_dd(round_unscaled(internal[month])); print_char("."); +print_dd(round_unscaled(internal[day])); print_char(":");@/ +t:=round_unscaled(internal[time]); +print_dd(t div 60); print_dd(t mod 60);@/ +print_nl("%%Pages: 1");@/ +@<List all the fonts and magnifications for edge structure~|h|@>; +print_ln + +@ @<List all the fonts and magnifications for edge structure~|h|@>= +@<Scan all the text nodes and set the |font_sizes| lists; + if |internal[prologues]<=0| list the sizes selected by |choose_scale|, + apply |unmark_font| to each font encountered, and call |mark_string| + whenever the size index is zero@>; +if internal[prologues]>0 then + @<Give a \.{DocumentFonts} comment listing all fonts with non-null + |font_sizes| and eliminate duplicates@> +else begin next_size:=0; + @<Make |cur_fsize| a copy of the |font_sizes| array@>; + repeat done_fonts:=true; + for f:=null_font+1 to last_fnum do + begin if cur_fsize[f]<>null then + @<Print the \.{\%*Font} comment for font |f| and advance |cur_fsize[f]|@>; + if cur_fsize[f]<>null then + begin unmark_font(f); done_fonts:=false; @+end; + end; + if not done_fonts then + @<Increment |next_size| and apply |mark_string_chars| to all text nodes with + that size index@>; + until done_fonts; + end + +@ @<Make |cur_fsize| a copy of the |font_sizes| array@>= +for f:=null_font+1 to last_fnum do + cur_fsize[f]:=font_sizes[f] + +@ It's not a good idea to make any assumptions about the |font_ps_name| entries, +so we carefully remove duplicates. There is no harm in using a slow, brute-force +search. + +@<Give a \.{DocumentFonts} comment listing all fonts with non-null...@>= +begin ldf:=null_font; +for f:=null_font+1 to last_fnum do + if font_sizes[f]<>null then + begin if ldf=null_font then print_nl("%%DocumentFonts:"); + for ff:=ldf downto null_font do + if font_sizes[ff]<>null then + if str_vs_str(font_ps_name[f],font_ps_name[ff])=0 then + goto found; + if ps_offset+1+length(font_ps_name[f])>max_print_line then + print_nl("%%+"); + print_char(" "); + print(font_ps_name[f]); + ldf:=f; + found: + end; +end + +@ @<Scan all the text nodes and set the |font_sizes| lists;...@>= +for f:=null_font+1 to last_fnum do font_sizes[f]:=null; +p:=link(dummy_loc(h)); +while p<>null do + begin if type(p)=text_code then + if font_n(p)<>null_font then + begin f:=font_n(p); + if internal[prologues]>0 then font_sizes[f]:=void + else begin if font_sizes[f]=null then unmark_font(f); + name_type(p):=size_index(f,choose_scale(p)); + if name_type(p)=0 then + mark_string_chars(f,text_p(p)); + end; + end; + p:=link(p); + end + +@ If the file name is so long that it can't be printed without exceeding +|emergency_line_length| then there will be missing items in the \.{\%*Font:} +line. We might have to repeat line in order to get the character usage +information to fit within |emergency_line_length|. + +@<Print the \.{\%*Font} comment for font |f| and advance |cur_fsize[f]|@>= +begin t:=0; +while check_ps_marks(f,t) do + begin print_nl("%*Font: "); + if ps_offset+length(font_name[f])+12>emergency_line_length then + goto done; + print(font_name[f]); + print_char(" "); + ds:=(font_dsize[f] + 8) div 16; + print_scaled(take_scaled(ds,sc_factor(cur_fsize[f]))); + if ps_offset+12>emergency_line_length then goto done; + print_char(" "); + print_scaled(ds); + if ps_offset+5>emergency_line_length then goto done; + t:=ps_marks_out(f,t); + end; +done: +cur_fsize[f]:=link(cur_fsize[f]); +end + +@ @<Increment |next_size| and apply |mark_string_chars| to all text nodes...@>= +begin incr(next_size); +p:=link(dummy_loc(h)); +while p<>null do + begin if type(p)=text_code then + if font_n(p)<>null_font then + if name_type(p)=next_size then + mark_string_chars(font_n(p),text_p(p)); + p:=link(p); + end; +end + +@ The prologue defines \.{fshow} and corrects for the fact that \.{fshow} +arguments use |font_name| instead of |font_ps_name|. Downloaded bitmap fonts +might not have reasonable |font_ps_name| entries, but we just charge ahead +anyway. The user should not make \&{prologues} positive if this will cause +trouble. +@:prologues_}{\&{prologues} primitive@> + +@<Print the prologue@>= +begin if ldf<>null_font then + begin for f:=null_font+1 to last_fnum do + if font_sizes[f]<>null then + begin ps_name_out(font_name[f],true); + ps_name_out(font_ps_name[f],true); + ps_print(" def"); + print_ln; + end; + print("/fshow {exch findfont exch scalefont setfont show}bind def"); + print_ln; + end; +end + +@ Since we do not have a stack for the graphics state, it is considered +completely unknown after the \.{grestore} from a stop clip object. Procedure +|unknown_graphics_state| needs a negative argument in this case. + +@<Cases for translating graphical object~|p| into \ps@>= +start_clip_code:begin print_nl("gsave "); + ps_path_out(path_p(p)); + ps_print(" clip"); + print_ln; + end; +stop_clip_code:begin print_nl("grestore"); + print_ln; + unknown_graphics_state(-1); + end; + +@ @<Cases for translating graphical object~|p| into \ps@>= +fill_code: if pen_p(p)=null then ps_fill_out(path_p(p)) + else if pen_is_elliptical(pen_p(p)) then stroke_ellipse(p,true) + else begin do_outer_envelope(copy_path(path_p(p)), p); + do_outer_envelope(htap_ypoc(path_p(p)), p); + end; +stroked_code: if pen_is_elliptical(pen_p(p)) then stroke_ellipse(p,false) + else begin q:=copy_path(path_p(p)); + t:=lcap_val(p); + @<Break the cycle and set |t:=1| if path |q| is cyclic@>; + q:=make_envelope(q,pen_p(p),ljoin_val(p),t,miterlim_val(p)); + ps_fill_out(q); + toss_knot_list(q); + end; + +@ The envelope of a cyclic path~|q| could be computed by calling +|make_envelope| once for |q| and once for its reversal. We don't do this +because it would fail color regions that are covered by the pen regardless +of where it is placed on~|q|. + +@<Break the cycle and set |t:=1| if path |q| is cyclic@>= +if left_type(q)<>endpoint then + begin left_type(insert_knot(q,x_coord(q),y_coord(q))):=endpoint; + right_type(q):=endpoint; + q:=link(q); + t:=1; + end + +@ @<Cases for translating graphical object~|p| into \ps@>= +text_code: if (font_n(p)<>null_font) and (length(text_p(p))>0) then + begin if internal[prologues]>0 then + scf:=choose_scale(p) + else scf:=indexed_size(font_n(p), name_type(p)); + @<Shift or transform as necessary before outputting text node~|p| at scale + factor~|scf|; set |transformed:=true| if the original transformation must + be restored@>; + ps_string_out(text_p(p)); + ps_name_out(font_name[font_n(p)],false); + @<Print the size information and \ps\ commands for text node~|p|@>; + print_ln; + end; + +@ @<Print the size information and \ps\ commands for text node~|p|@>= +ps_room(18); +print_char(" "); +ds:=(font_dsize[font_n(p)]+8) div 16; +print_scaled(take_scaled(ds,scf)); +print(" fshow"); +if transformed then ps_print(" grestore") + +@ @<Shift or transform as necessary before outputting text node~|p| at...@>= +transformed:=(txx_val(p)<>scf)or(tyy_val(p)<>scf)or@| + (txy_val(p)<>0)or(tyx_val(p)<>0); +if transformed then + begin print("gsave ["); + ps_pair_out(make_scaled(txx_val(p),scf),@|make_scaled(tyx_val(p),scf)); + ps_pair_out(make_scaled(txy_val(p),scf),@|make_scaled(tyy_val(p),scf)); + ps_pair_out(tx_val(p),ty_val(p));@/ + ps_print("] concat 0 0 moveto"); + end +else begin ps_pair_out(tx_val(p),ty_val(p)); + ps_print("moveto"); + end; +print_ln + +@ Now that we've finished |ship_out|, let's look at the other commands +by which a user can send things to the \.{GF} file. + +@ @<Determine if a character has been shipped out@>= +begin cur_exp:=round_unscaled(cur_exp) mod 256; +if cur_exp<0 then cur_exp:=cur_exp+256; +boolean_reset(char_exists[cur_exp]); cur_type:=boolean_type; +end + +@* \[45] Dumping and undumping the tables. +After \.{INIMP} has seen a collection of macros, it +can write all the necessary information on an auxiliary file so +that production versions of \MP\ are able to initialize their +memory at high speed. The present section of the program takes +care of such output and input. We shall consider simultaneously +the processes of storing and restoring, +so that the inverse relation between them is clear. +@.INIMP@> + +The global variable |mem_ident| is a string that is printed right +after the |banner| line when \MP\ is ready to start. For \.{INIMP} this +string says simply `\.{(INIMP)}'; for other versions of \MP\ it says, +for example, `\.{(preloaded mem=plain 90.4.14)}', showing the year, +month, and day that the mem file was created. We have |mem_ident=0| +before \MP's tables are loaded. + +@<Glob...@>= +@!mem_ident:str_number; + +@ @<Set init...@>= +mem_ident:=0; + +@ @<Initialize table entries...@>= +mem_ident:=" (INIMP)"; + +@ @<Declare act...@>= +@!init procedure store_mem_file; +label done; +var @!k:integer; {all-purpose index} +@!p,@!q: pointer; {all-purpose pointers} +@!x: integer; {something to dump} +@!w: four_quarters; {four ASCII codes} +@!s: str_number; {all-purpose string} +begin @<Create the |mem_ident|, open the mem file, + and inform the user that dumping has begun@>; +@<Dump constants for consistency check@>; +@<Dump the string pool@>; +@<Dump the dynamic memory@>; +@<Dump the table of equivalents and the hash table@>; +@<Dump a few more things and the closing check word@>; +@<Close the mem file@>; +end; +tini + +@ Corresponding to the procedure that dumps a mem file, we also have a function +that reads~one~in. The function returns |false| if the dumped mem is +incompatible with the present \MP\ table sizes, etc. + +@d off_base=6666 {go here if the mem file is unacceptable} +@d too_small(#)==begin wake_up_terminal; + wterm_ln('---! Must increase the ',#); +@.Must increase the x@> + goto off_base; + end + +@p @t\4@>@<Declare the function called |open_mem_file|@>@; +function load_mem_file:boolean; +label done,off_base,exit; +var @!k:integer; {all-purpose index} +@!p,@!q: pointer; {all-purpose pointers} +@!x: integer; {something undumped} +@!s: str_number; {some temporary string} +@!w: four_quarters; {four ASCII codes} +begin @<Undump constants for consistency check@>; +@<Undump the string pool@>; +@<Undump the dynamic memory@>; +@<Undump the table of equivalents and the hash table@>; +@<Undump a few more things and the closing check word@>; +load_mem_file:=true; return; {it worked!} +off_base: wake_up_terminal; + wterm_ln('(Fatal mem file error; I''m stymied)'); +@.Fatal mem file error@> +load_mem_file:=false; +exit:end; + +@ Mem files consist of |memory_word| items, and we use the following +macros to dump words of different types: + +@d dump_wd(#)==begin mem_file^:=#; put(mem_file);@+end +@d dump_int(#)==begin mem_file^.int:=#; put(mem_file);@+end +@d dump_hh(#)==begin mem_file^.hh:=#; put(mem_file);@+end +@d dump_qqqq(#)==begin mem_file^.qqqq:=#; put(mem_file);@+end + +@<Glob...@>= +@!mem_file:word_file; {for input or output of mem information} + +@ The inverse macros are slightly more complicated, since we need to check +the range of the values we are reading in. We say `|undump(a)(b)(x)|' to +read an integer value |x| that is supposed to be in the range |a<=x<=b|. + +@d undump_wd(#)==begin get(mem_file); #:=mem_file^;@+end +@d undump_int(#)==begin get(mem_file); #:=mem_file^.int;@+end +@d undump_hh(#)==begin get(mem_file); #:=mem_file^.hh;@+end +@d undump_qqqq(#)==begin get(mem_file); #:=mem_file^.qqqq;@+end +@d undump_end_end(#)==#:=x;@+end +@d undump_end(#)==(x>#) then goto off_base@+else undump_end_end +@d undump(#)==begin undump_int(x); if (x<#) or undump_end +@d undump_size_end_end(#)==too_small(#)@+else undump_end_end +@d undump_size_end(#)==if x># then undump_size_end_end +@d undump_size(#)==begin undump_int(x); + if x<# then goto off_base; undump_size_end + +@ The next few sections of the program should make it clear how we use the +dump/undump macros. + +@<Dump constants for consistency check@>= +dump_int(@$);@/ +dump_int(mem_min);@/ +dump_int(mem_top);@/ +dump_int(hash_size);@/ +dump_int(hash_prime);@/ +dump_int(max_in_open) + +@ Sections of a \.{WEB} program that are ``commented out'' still contribute +strings to the string pool; therefore \.{INIMP} and \MP\ will have +the same strings. (And it is, of course, a good thing that they do.) +@.WEB@> +@^string pool@> + +@<Undump constants for consistency check@>= +x:=mem_file^.int; +if x<>@$ then goto off_base; {check that strings are the same} +undump_int(x); +if x<>mem_min then goto off_base; +undump_int(x); +if x<>mem_top then goto off_base; +undump_int(x); +if x<>hash_size then goto off_base; +undump_int(x); +if x<>hash_prime then goto off_base; +undump_int(x); +if x<>max_in_open then goto off_base + +@ We do string pool compaction to avoid dumping unused strings. + +@d dump_four_ASCII== + w.b0:=qi(so(str_pool[k])); w.b1:=qi(so(str_pool[k+1])); + w.b2:=qi(so(str_pool[k+2])); w.b3:=qi(so(str_pool[k+3])); + dump_qqqq(w) + +@<Dump the string pool@>= +do_compaction(pool_size); +dump_int(pool_ptr); +dump_int(max_str_ptr); +dump_int(str_ptr); +k:=0; +while (next_str[k]=k+1) and (k<=max_str_ptr) do incr(k); +dump_int(k); +while k<=max_str_ptr do + begin dump_int(next_str[k]); incr(k); + end; +k:=0; +loop @+begin dump_int(str_start[k]); + if k=str_ptr then goto done else k:=next_str[k]; + end; +done:k:=0; +while k+4<pool_ptr do + begin dump_four_ASCII; k:=k+4; + end; +k:=pool_ptr-4; dump_four_ASCII; +print_ln; print("at most "); print_int(max_str_ptr); +print(" strings of total length "); +print_int(pool_ptr) + +@ @d undump_four_ASCII== + undump_qqqq(w); + str_pool[k]:=si(qo(w.b0)); str_pool[k+1]:=si(qo(w.b1)); + str_pool[k+2]:=si(qo(w.b2)); str_pool[k+3]:=si(qo(w.b3)) + +@<Undump the string pool@>= +undump_size(0)(pool_size)('string pool size')(pool_ptr); +undump_size(0)(max_strings-1)('max strings')(max_str_ptr); +undump(0)(max_str_ptr)(str_ptr); +undump(0)(max_str_ptr+1)(s); +for k:=0 to s-1 do next_str[k]:=k+1; +for k:=s to max_str_ptr do undump(s+1)(max_str_ptr+1)(next_str[k]); +fixed_str_use:=0; +k:=0; +loop @+begin undump(0)(pool_ptr)(str_start[k]); + if k=str_ptr then goto done; + str_ref[k]:=max_str_ref; + incr(fixed_str_use); + last_fixed_str:=k; k:=next_str[k]; + end; +done:k:=0; +while k+4<pool_ptr do + begin undump_four_ASCII; k:=k+4; + end; +k:=pool_ptr-4; undump_four_ASCII; +init_str_use:=fixed_str_use; init_pool_ptr:=pool_ptr; +max_pool_ptr:=pool_ptr; +strs_used_up:=fixed_str_use; +stat pool_in_use:=str_start[str_ptr]; strs_in_use:=fixed_str_use; + max_pl_used:=pool_in_use; max_strs_used:=strs_in_use;@/ + pact_count:=0; pact_chars:=0; pact_strs:=0; +tats + +@ By sorting the list of available spaces in the variable-size portion of +|mem|, we are usually able to get by without having to dump very much +of the dynamic memory. + +We recompute |var_used| and |dyn_used|, so that \.{INIMP} dumps valid +information even when it has not been gathering statistics. + +@<Dump the dynamic memory@>= +sort_avail; var_used:=0; +dump_int(lo_mem_max); dump_int(rover); +p:=mem_min; q:=rover; x:=0; +repeat for k:=p to q+1 do dump_wd(mem[k]); +x:=x+q+2-p; var_used:=var_used+q-p; +p:=q+node_size(q); q:=rlink(q); +until q=rover; +var_used:=var_used+lo_mem_max-p; dyn_used:=mem_end+1-hi_mem_min;@/ +for k:=p to lo_mem_max do dump_wd(mem[k]); +x:=x+lo_mem_max+1-p; +dump_int(hi_mem_min); dump_int(avail); +for k:=hi_mem_min to mem_end do dump_wd(mem[k]); +x:=x+mem_end+1-hi_mem_min; +p:=avail; +while p<>null do + begin decr(dyn_used); p:=link(p); + end; +dump_int(var_used); dump_int(dyn_used); +print_ln; print_int(x); +print(" memory locations dumped; current usage is "); +print_int(var_used); print_char("&"); print_int(dyn_used) + +@ @<Undump the dynamic memory@>= +undump(lo_mem_stat_max+1000)(hi_mem_stat_min-1)(lo_mem_max); +undump(lo_mem_stat_max+1)(lo_mem_max)(rover); +p:=mem_min; q:=rover; +repeat for k:=p to q+1 do undump_wd(mem[k]); +p:=q+node_size(q); +if (p>lo_mem_max)or((q>=rlink(q))and(rlink(q)<>rover)) then goto off_base; +q:=rlink(q); +until q=rover; +for k:=p to lo_mem_max do undump_wd(mem[k]); +undump(lo_mem_max+1)(hi_mem_stat_min)(hi_mem_min); +undump(null)(mem_top)(avail); mem_end:=mem_top; +for k:=hi_mem_min to mem_end do undump_wd(mem[k]); +undump_int(var_used); undump_int(dyn_used) + +@ A different scheme is used to compress the hash table, since its lower region +is usually sparse. When |text(p)<>0| for |p<=hash_used|, we output three +words: |p|, |hash[p]|, and |eqtb[p]|. The hash table is, of course, densely +packed for |p>=hash_used|, so the remaining entries are output in~a~block. + +@<Dump the table of equivalents and the hash table@>= +dump_int(hash_used); st_count:=frozen_inaccessible-1-hash_used; +for p:=1 to hash_used do if text(p)<>0 then + begin dump_int(p); dump_hh(hash[p]); dump_hh(eqtb[p]); incr(st_count); + end; +for p:=hash_used+1 to hash_end do + begin dump_hh(hash[p]); dump_hh(eqtb[p]); + end; +dump_int(st_count);@/ +print_ln; print_int(st_count); print(" symbolic tokens") + +@ @<Undump the table of equivalents and the hash table@>= +undump(1)(frozen_inaccessible)(hash_used); p:=0; +repeat undump(p+1)(hash_used)(p); undump_hh(hash[p]); undump_hh(eqtb[p]); +until p=hash_used; +for p:=hash_used+1 to hash_end do + begin undump_hh(hash[p]); undump_hh(eqtb[p]); + end; +undump_int(st_count) + +@ We have already printed a lot of statistics, so we set |tracing_stats:=0| +to prevent them appearing again. + +@<Dump a few more things and the closing check word@>= +dump_int(int_ptr); +for k:=1 to int_ptr do + begin dump_int(internal[k]); dump_int(int_name[k]); + end; +dump_int(start_sym); dump_int(interaction); dump_int(mem_ident); +dump_int(bg_loc); dump_int(eg_loc); dump_int(serial_no); dump_int(69073); +internal[tracing_stats]:=0 + +@ @<Undump a few more things and the closing check word@>= +undump(max_given_internal)(max_internal)(int_ptr); +for k:=1 to int_ptr do + begin undump_int(internal[k]); + undump(0)(str_ptr)(int_name[k]); + end; +undump(0)(frozen_inaccessible)(start_sym); +undump(batch_mode)(error_stop_mode)(interaction); +undump(0)(str_ptr)(mem_ident); +undump(1)(hash_end)(bg_loc); +undump(1)(hash_end)(eg_loc); +undump_int(serial_no);@/ +undump_int(x);@+if (x<>69073)or eof(mem_file) then goto off_base + +@ @<Create the |mem_ident|...@>= +selector:=new_string; +print(" (preloaded mem="); print(job_name); print_char(" "); +print_int(round_unscaled(internal[year]) mod 100); print_char("."); +print_int(round_unscaled(internal[month])); print_char("."); +print_int(round_unscaled(internal[day])); print_char(")"); +if interaction=batch_mode then selector:=log_only +else selector:=term_and_log; +str_room(1); mem_ident:=make_string; str_ref[mem_ident]:=max_str_ref;@/ +pack_job_name(mem_extension); +while not w_open_out(mem_file) do + prompt_file_name("mem file name",mem_extension); +print_nl("Beginning to dump on file "); +@.Beginning to dump...@> +s:=w_make_name_string(mem_file); +print(s); flush_string(s); +print_nl(mem_ident) + +@ @<Close the mem file@>= +w_close(mem_file) + +@* \[46] The main program. +This is it: the part of \MP\ that executes all those procedures we have +written. + +Well---almost. We haven't put the parsing subroutines into the +program yet; and we'd better leave space for a few more routines that may +have been forgotten. + +@p @<Declare the basic parsing subroutines@>@; +@<Declare miscellaneous procedures that were declared |forward|@>@; +@<Last-minute procedures@> + +@ We've noted that there are two versions of \MP. One, called \.{INIMP}, +@.INIMP@> +has to be run first; it initializes everything from scratch, without +reading a mem file, and it has the capability of dumping a mem file. +The other one is called `\.{VIRMP}'; it is a ``virgin'' program that needs +@.VIRMP@> +to input a mem file in order to get started. \.{VIRMP} typically has +a bit more memory capacity than \.{INIMP}, because it does not need the +space consumed by the dumping/undumping routines and the numerous calls on +|primitive|, etc. + +The \.{VIRMP} program cannot read a mem file instantaneously, of course; +the best implementations therefore allow for production versions of \MP\ that +not only avoid the loading routine for \PASCAL\ object code, they also have +a mem file pre-loaded. This is impossible to do if we stick to standard +\PASCAL; but there is a simple way to fool many systems into avoiding the +initialization, as follows:\quad(1)~We declare a global integer variable +called |ready_already|. The probability is negligible that this +variable holds any particular value like 314159 when \.{VIRMP} is first +loaded.\quad(2)~After we have read in a mem file and initialized +everything, we set |ready_already:=314159|.\quad(3)~Soon \.{VIRMP} +will print `\.*', waiting for more input; and at this point we +interrupt the program and save its core image in some form that the +operating system can reload speedily.\quad(4)~When that core image is +activated, the program starts again at the beginning; but now +|ready_already=314159| and all the other global variables have +their initial values too. The former chastity has vanished! + +In other words, if we allow ourselves to test the condition +|ready_already=314159|, before |ready_already| has been +assigned a value, we can avoid the lengthy initialization. Dirty tricks +rarely pay off so handsomely. +@^dirty \PASCAL@> +@^system dependencies@> + +@<Glob...@>= +@!ready_already:integer; {a sacrifice of purity for economy} + +@ Now this is really it: \MP\ starts and ends here. + +The initial test involving |ready_already| should be deleted if the +\PASCAL\ runtime system is smart enough to detect such a ``mistake.'' +@^system dependencies@> + +@p begin @!{|start_here|} +history:=fatal_error_stop; {in case we quit during initialization} +t_open_out; {open the terminal for output} +if ready_already=314159 then goto start_of_MP; +@<Check the ``constant'' values...@>@; +if bad>0 then + begin wterm_ln('Ouch---my internal constants have been clobbered!', + '---case ',bad:1); +@.Ouch...clobbered@> + goto final_end; + end; +initialize; {set global variables to their starting values} +@!init if not get_strings_started then goto final_end; +init_tab; {initialize the tables} +init_prim; {call |primitive| for each primitive} +init_str_use:=str_ptr; init_pool_ptr:=pool_ptr;@/ +max_str_ptr:=str_ptr; max_pool_ptr:=pool_ptr; +fix_date_and_time; +tini@/ +ready_already:=314159; +start_of_MP: @<Initialize the output routines@>; +@<Get the first line of input and prepare to start@>; +history:=spotless; {ready to go!} +if start_sym>0 then {insert the `\&{everyjob}' symbol} + begin cur_sym:=start_sym; back_input; + end; +main_control; {come to life} +final_cleanup; {prepare for death} +end_of_MP: close_files_and_terminate; +final_end: ready_already:=0; +end. + +@ Here we do whatever is needed to complete \MP's job gracefully on the +local operating system. The code here might come into play after a fatal +error; it must therefore consist entirely of ``safe'' operations that +cannot produce error messages. For example, it would be a mistake to call +|str_room| or |make_string| at this time, because a call on |overflow| +might lead to an infinite loop. +@^system dependencies@> + +This program doesn't bother to close the input files that may still be open. + +@<Last-minute...@>= +procedure close_files_and_terminate; +var @!k:integer; {all-purpose index} +@!lh:integer; {the length of the \.{TFM} header, in words} +@!lk_offset:0..256; {extra words inserted at beginning of |lig_kern| array} +@!p:pointer; {runs through a list of \.{TFM} dimensions} +begin @<Close all open files in the |rd_file| and |wr_file| arrays@>; +@!stat if internal[tracing_stats]>0 then + @<Output statistics about this job@>;@;@+tats@/ +wake_up_terminal; @<Do all the finishing work on the \.{TFM} file@>; +@<Explain what output files were written@>; +if log_opened then + begin wlog_cr; + a_close(log_file); selector:=selector-2; + if selector=term_only then + begin print_nl("Transcript written on "); +@.Transcript written...@> + print(log_name); print_char("."); + end; + end; +end; + +@ @<Close all open files in the |rd_file| and |wr_file| arrays@>= +for k:=0 to read_files-1 do + if rd_fname[k]<>0 then a_close(rd_file[k]); +for k:=0 to write_files-1 do + if wr_fname[k]<>0 then a_close(wr_file[k]) + +@ We want to produce a \.{TFM} file if and only if |fontmaking| is positive. + +We reclaim all of the variable-size memory at this point, so that +there is no chance of another memory overflow after the memory capacity +has already been exceeded. + +@<Do all the finishing work on the \.{TFM} file@>= +if internal[fontmaking]>0 then + begin @<Make the dynamic memory into one big available node@>; + @<Massage the \.{TFM} widths@>; + fix_design_size; fix_check_sum; + @<Massage the \.{TFM} heights, depths, and italic corrections@>; + internal[fontmaking]:=0; {avoid loop in case of fatal error} + @<Finish the \.{TFM} file@>; + end + +@ @<Make the dynamic memory into one big available node@>= +rover:=lo_mem_stat_max+1; link(rover):=empty_flag; lo_mem_max:=hi_mem_min-1; +if lo_mem_max-rover>max_halfword then lo_mem_max:=max_halfword+rover; +node_size(rover):=lo_mem_max-rover; llink(rover):=rover; rlink(rover):=rover; +link(lo_mem_max):=null; info(lo_mem_max):=null + +@ The present section goes directly to the log file instead of using +|print| commands, because there's no need for these strings to take +up |str_pool| memory when a non-{\bf stat} version of \MP\ is being used. + +@<Output statistics...@>= +if log_opened then + begin wlog_ln(' '); + wlog_ln('Here is how much of MetaPost''s memory',' you used:'); +@.Here is how much...@> + wlog(' ',max_strs_used-init_str_use:1,' string'); + if max_strs_used<>init_str_use+1 then wlog('s'); + wlog_ln(' out of ', max_strings-1-init_str_use:1);@/ + wlog_ln(' ',max_pl_used-init_pool_ptr:1,' string characters out of ', + pool_size-init_pool_ptr:1);@/ + wlog_ln(' ',lo_mem_max-mem_min+mem_end-hi_mem_min+2:1,@| + ' words of memory out of ',mem_end+1-mem_min:1);@/ + wlog_ln(' ',st_count:1,' symbolic tokens out of ', + hash_size:1);@/ + wlog_ln(' ',max_in_stack:1,'i,',@| + int_ptr:1,'n,',@| + max_param_stack:1,'p,',@| + max_buf_stack+1:1,'b stack positions out of ',@| + stack_size:1,'i,', + max_internal:1,'n,', + param_size:1,'p,', + buf_size:1,'b'); + wlog_ln(' ',pact_count:1,' string compactions (moved ', + pact_chars:1,' characters, ', + pact_strs:1,' strings)'); + end + +@ We get to the |final_cleanup| routine when \&{end} or \&{dump} has +been scanned. + +@<Last-minute...@>= +procedure final_cleanup; +label exit; +var c:small_number; {0 for \&{end}, 1 for \&{dump}} +begin c:=cur_mod; +if job_name=0 then open_log_file; +while input_ptr>0 do + if token_state then end_token_list@+else end_file_reading; +while loop_ptr<>null do stop_iteration; +while open_parens>0 do + begin print(" )"); decr(open_parens); + end; +while cond_ptr<>null do + begin print_nl("(end occurred when ");@/ +@.end occurred...@> + print_cmd_mod(fi_or_else,cur_if); + {`\.{if}' or `\.{elseif}' or `\.{else}'} + if if_line<>0 then + begin print(" on line "); print_int(if_line); + end; + print(" was incomplete)"); + if_line:=if_line_field(cond_ptr); + cur_if:=name_type(cond_ptr); cond_ptr:=link(cond_ptr); + end; +if history<>spotless then + if ((history=warning_issued)or(interaction<error_stop_mode)) then + if selector=term_and_log then + begin selector:=term_only; + print_nl("(see the transcript file for additional information)"); +@.see the transcript file...@> + selector:=term_and_log; + end; +if c=1 then + begin @!init store_mem_file; return;@+tini@/ + print_nl("(dump is performed only by INIMP)"); return; +@.dump...only by INIMP@> + end; +exit:end; + +@ @<Last-minute...@>= +@!init procedure init_prim; {initialize all the primitives} +begin +@<Put each...@>; +end; +@# +procedure init_tab; {initialize other tables} +var @!k:integer; {all-purpose index} +begin @<Initialize table entries (done by \.{INIMP} only)@>@; +end; +tini + +@ When we begin the following code, \MP's tables may still contain garbage; +the strings might not even be present. Thus we must proceed cautiously to get +bootstrapped in. + +But when we finish this part of the program, \MP\ is ready to call on the +|main_control| routine to do its work. + +@<Get the first line...@>= +begin @<Initialize the input routines@>; +if (mem_ident=0)or(buffer[loc]="&") then + begin if mem_ident<>0 then initialize; {erase preloaded mem} + if not open_mem_file then goto final_end; + if not load_mem_file then + begin w_close(mem_file); goto final_end; + end; + w_close(mem_file); + while (loc<limit)and(buffer[loc]=" ") do incr(loc); + end; +buffer[limit]:="%";@/ +fix_date_and_time;@/ +sys_random_seed := (internal[time] div unity)+internal[day];@/ +init_randoms(sys_random_seed);@/ +@<Initialize the print |selector|...@>; +if loc<limit then if buffer[loc]<>"\" then start_input; {\&{input} assumed} +end + +@* \[47] Debugging. +Once \MP\ is working, you should be able to diagnose most errors with +the \.{show} commands and other diagnostic features. But for the initial +stages of debugging, and for the revelation of really deep mysteries, you +can compile \MP\ with a few more aids, including the \PASCAL\ runtime +checks and its debugger. An additional routine called |debug_help| +will also come into play when you type `\.D' after an error message; +|debug_help| also occurs just before a fatal error causes \MP\ to succumb. +@^debugging@> +@^system dependencies@> + +The interface to |debug_help| is primitive, but it is good enough when used +with a \PASCAL\ debugger that allows you to set breakpoints and to read +variables and change their values. After getting the prompt `\.{debug \#}', you +type either a negative number (this exits |debug_help|), or zero (this +goes to a location where you can set a breakpoint, thereby entering into +dialog with the \PASCAL\ debugger), or a positive number |m| followed by +an argument |n|. The meaning of |m| and |n| will be clear from the +program below. (If |m=13|, there is an additional argument, |l|.) +@.debug \#@> + +@d breakpoint=888 {place where a breakpoint is desirable} + +@<Last-minute...@>= +@!debug procedure debug_help; {routine to display various things} +label breakpoint,exit; +var @!k,@!l,@!m,@!n:integer; +begin loop begin wake_up_terminal; + print_nl("debug # (-1 to exit):"); update_terminal; +@.debug \#@> + read(term_in,m); + if m<0 then return + else if m=0 then + begin goto breakpoint;@\ {go to every label at least once} + breakpoint: m:=0; @{'BREAKPOINT'@}@\ + end + else begin read(term_in,n); + case m of + @t\4@>@<Numbered cases for |debug_help|@>@; + othercases print("?") + endcases; + end; + end; +exit:end; +gubed + +@ @<Numbered cases...@>= +1: print_word(mem[n]); {display |mem[n]| in all forms} +2: print_int(info(n)); +3: print_int(link(n)); +4: begin print_int(eq_type(n)); print_char(":"); print_int(equiv(n)); + end; +5: print_variable_name(n); +6: print_int(internal[n]); +7: do_show_dependencies; +9: show_token_list(n,null,100000,0); +10: print(n); +11: check_mem(n>0); {check wellformedness; print new busy locations if |n>0|} +12: search_mem(n); {look for pointers to |n|} +13: begin read(term_in,l); print_cmd_mod(n,l); + end; +14: for k:=0 to n do print(buffer[k]); +15: panicking:=not panicking; + + +@ \MP\ used to have one single routine to print to both `write' files +and the PostScript output. Web2c redefines ``Character |k| cannot be +printed'', and that resulted in some bugs where 8-bit characters were +written to the PostScript file (reported by Wlodek Bzyl). + +Also, Hans Hagen requested spaces to be output as "\\040" instead of +a plain space, since that makes it easier to parse the result file +for postprocessing. + +@<Character |k| is not allowed in PostScript output@>= + (k<=" ")or(k>"~") + +@* \[48] System-dependent changes. +This section should be replaced, if necessary, by any special +modification of the program +that are necessary to make \MP\ work at a particular installation. +It is usually best to design your change file so that all changes to +previous sections preserve the section numbering; then everybody's version +will be consistent with the published program. More extensive changes, +which introduce new sections, can be inserted here; then only the index +itself will get a new section number. +@^system dependencies@> + +@* \[49] Index. +Here is where you can find all uses of each identifier in the program, +with underlined entries pointing to where the identifier was defined. +If the identifier is only one letter long, however, you get to see only +the underlined entries. {\sl All references are to section numbers instead of +page numbers.} + +This index also lists error messages and other aspects of the program +that you might want to look up some day. For example, the entry +for ``system dependencies'' lists all sections that should receive +special attention from people who are installing \MP\ in a new +operating environment. A list of various things that can't happen appears +under ``this can't happen''. +Approximately 25 sections are listed under ``inner loop''; these account +for more than 60\pct! of \MP's running time, exclusive of input and output. |