% $Id: mp.w 1668 2011-05-05 09:54:59Z taco $ % % Copyright 2008-2011 Taco Hoekwater. % % This program is free software: you can redistribute it and/or modify % it under the terms of the GNU Lesser General Public License as published by % the Free Software Foundation, either version 3 of the License, or % (at your option) any later version. % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU Lesser General Public License for more details. % % You should have received a copy of the GNU Lesser General Public License % along with this program. If not, see <http://www.gnu.org/licenses/>. % % 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\ps{PostScript} \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\vbv="026A % synonym for `\|' \def\vb{\relax\ifmmode\vbv\else$\vbv$\fi} \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@@> \def\title{MetaPost} \pdfoutput=1 \pageno=3 @* Introduction. This is \MP\ by John Hobby, a graphics-language processor based on D. E. Knuth's \MF. Much of the original Pascal version 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. The main purpose of the following program is to explain the algorithms of \MP\ as clearly as possible. 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 C. 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 default_banner "This is MetaPost, Version 1.504" /* printed when \MP\ starts */ @d true 1 @d false 0 @(mpmp.h@>= #define metapost_version "1.504" @ The external library header for \MP\ is |mplib.h|. It contains a few typedefs and the header defintions for the externally used fuctions. The most important of the typedefs is the definition of the structure |MP_options|, that acts as a small, configurable front-end to the fairly large |MP_instance| structure. @(mplib.h@>= typedef struct MP_instance *MP; @<Exported types@>; typedef struct MP_options { @<Option variables@> } MP_options; @<Exported function headers@> @ The internal header file is much longer: it not only lists the complete |MP_instance|, but also a lot of functions that have to be available to the \ps\ backend, that is defined in a separate \.{WEB} file. The variables from |MP_options| are included inside the |MP_instance| wholesale. @(mpmp.h@>= #include "avl.h" #include <setjmp.h> typedef struct psout_data_struct *psout_data; typedef struct svgout_data_struct *svgout_data; #ifndef HAVE_BOOLEAN typedef int boolean; #endif #ifndef INTEGER_TYPE typedef int integer; #endif @<Declare helpers@>; @<Enumeration types@>; @<Types in the outer block@>; @<Constants in the outer block@>; typedef struct MP_instance { @<Option variables@> @<Global variables@> } MP_instance; @<Internal library declarations@> @ @c #include <w2c/config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <assert.h> #ifdef HAVE_UNISTD_H # include <unistd.h> /* for access */ #endif #include <time.h> /* for struct tm \& co */ #include "mplib.h" #include "mplibps.h" /* external header */ #include "mplibsvg.h" /* external header */ #include "mpmp.h" /* internal header */ #include "mppsout.h" /* internal header */ #include "mpsvgout.h" /* internal header */ #include "mpmath.h" /* internal header */ extern font_number mp_read_font_info (MP mp, char *fname); /* tfmin.w */ @h @<Declarations@>; @<Basic printing procedures@>; @<Error handling procedures@> @ Some debugging support for development. The trick with the variadic macros probably only works in gcc, as this preprocessor feature was not formalized until the c99 standard (and that is too new for us). Lets' hope that at least most compilers understand the non-debug version. @^system dependencies@> @(mpmp.h@>= #define DEBUG 0 #if DEBUG void do_debug_printf(MP mp, const char *prefix, const char *fmt, ...); # define debug_printf(a1,a2,a3) do_debug_printf(mp, "", a1,a2,a3) # define FUNCTION_TRACE1(a1) do_debug_printf(mp, "FTRACE: ", a1) # define FUNCTION_TRACE2(a1,a2) do_debug_printf(mp, "FTRACE: ", a1,a2) # define FUNCTION_TRACE3(a1,a2,a3) do_debug_printf(mp, "FTRACE: ", a1,a2,a3) # define FUNCTION_TRACE4(a1,a2,a3,a4) do_debug_printf(mp, "FTRACE: ", a1,a2,a3,a4) #else # define debug_printf(a1,a2,a3) # define FUNCTION_TRACE1(a1) # define FUNCTION_TRACE2(a1,a2) # define FUNCTION_TRACE3(a1,a2,a3) # define FUNCTION_TRACE4(a1,a2,a3,a4) #endif @ This function occasionally crashes (if something is written after the log file is already closed), but that is not so important while debugging. @c #if DEBUG void do_debug_printf(MP mp, const char *prefix, const char *fmt, ...) { va_list ap; va_start (ap, fmt); if (mp->log_file && !ferror((FILE *)mp->log_file)) { fputs(prefix, mp->log_file); vfprintf(mp->log_file, fmt, ap); } va_end(ap); va_start (ap, fmt); if (mp->term_out && !ferror((FILE *)mp->term_out)) { fputs(prefix, mp->term_out); vfprintf(mp->term_out, fmt, ap); } else { fputs(prefix, stdout); vfprintf(stdout, fmt, ap); } va_end(ap); } #endif @ Here are the functions that set up the \MP\ instance. @<Declarations@>= MP_options *mp_options (void); MP mp_initialize (MP_options * opt); @ @c MP_options *mp_options (void) { MP_options *opt; size_t l = sizeof (MP_options); opt = malloc (l); if (opt != NULL) { memset (opt, 0, l); } return opt; } @ @<Internal library declarations@>= @<Declare subroutines for parsing file names@> @ The whole instance structure is initialized with zeroes, this greatly reduces the number of statements needed in the |Allocate or initialize variables| block. @d set_callback_option(A) do { mp->A = mp_##A; if (opt->A!=NULL) mp->A = opt->A; } while (0) @c static MP mp_do_new (jmp_buf * buf) { MP mp = malloc (sizeof (MP_instance)); if (mp == NULL) { xfree (buf); return NULL; } memset (mp, 0, sizeof (MP_instance)); mp->jump_buf = buf; return mp; } @ @c static void mp_free (MP mp) { int k; /* loop variable */ @<Dealloc variables@>; if (mp->noninteractive) { @<Finish non-interactive use@>; } xfree (mp->jump_buf); @<Free table entries@>; mp_free_math(mp); xfree (mp); } @ @c static void mp_do_initialize (MP mp) { @<Local variables for initialization@>; @<Set initial values of key variables@>; } @ For the retargetable math library, we need to have a pointer, at least. @<Global variables@>= void *math; @ This procedure gets things started properly. @c MP mp_initialize (MP_options * opt) { MP mp; jmp_buf *buf = malloc (sizeof (jmp_buf)); if (buf == NULL || setjmp (*buf) != 0) return NULL; mp = mp_do_new (buf); if (mp == NULL) return NULL; mp->userdata = opt->userdata; mp->noninteractive = opt->noninteractive; set_callback_option (find_file); set_callback_option (open_file); set_callback_option (read_ascii_file); set_callback_option (read_binary_file); set_callback_option (close_file); set_callback_option (eof_file); set_callback_option (flush_file); set_callback_option (write_ascii_file); set_callback_option (write_binary_file); set_callback_option (shipout_backend); if (opt->banner && *(opt->banner)) { mp->banner = xstrdup (opt->banner); } else { mp->banner = xstrdup (default_banner); } if (opt->command_line && *(opt->command_line)) mp->command_line = xstrdup (opt->command_line); if (mp->noninteractive) { @<Prepare function pointers for non-interactive use@>; } /* open the terminal for output */ t_open_out; mp->math = mp_initialize_math(mp); @<Find and load preload file, if required@>; @<Allocate or initialize variables@>; mp_reallocate_paths (mp, 1000); mp_reallocate_fonts (mp, 8); mp->history = mp_fatal_error_stop; /* in case we quit during initialization */ @<Check the ``constant'' values...@>; if (mp->bad > 0) { char ss[256]; mp_snprintf (ss, 256, "Ouch---my internal constants have been clobbered!\n" "---case %i", (int) mp->bad); do_putsf (mp->err_out, (char *) ss); @.Ouch...clobbered@>; return mp; } mp_do_initialize (mp); /* erase preloaded mem */ mp_init_tab (mp); /* initialize the tables */ mp_init_prim (mp); /* call |primitive| for each primitive */ mp_fix_date_and_time (mp); if (!mp->noninteractive) { @<Initialize the output routines@>; @<Get the first line of input and prepare to start@>; @<Initializations after first line is read@>; @<Fix up |mp->internal[mp_job_name]|@>; } else { mp->history = mp_spotless; } return mp; } @ @<Initializations after first line is read@>= mp_open_log_file (mp); mp_set_job_id (mp); mp_init_map_file (mp, mp->troff_mode); mp->history = mp_spotless; /* ready to go! */ if (mp->troff_mode) { internal_value (mp_gtroffmode) = unity; internal_value (mp_prologues) = unity; } if (mp->start_sym != NULL) { /* insert the `\&{everyjob}' symbol */ mp->cur_sym = mp->start_sym; mp_back_input (mp); } @ @<Exported function headers@>= extern MP_options *mp_options (void); extern MP mp_initialize (MP_options * opt); extern int mp_status (MP mp); extern void *mp_userdata (MP mp); @ @c int mp_status (MP mp) { return mp->history; } @ @c void *mp_userdata (MP mp) { return mp->userdata; } @ 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. @ 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 preprocessor test `|#ifdef DEBUG .. #endif|'. @ The following parameters can be changed at compile time to extend or reduce \MP's capacity. @^system dependencies@> @<Constants...@>= #define bistack_size 1500 /* size of stack for bisection algorithms; should probably be left at this value */ @ Like the preceding parameters, the following quantities can be changed to extend or reduce \MP's capacity. @ @<Glob...@>= int pool_size; /* maximum number of characters in strings, including all error messages and help texts, and the names of all identifiers */ int max_in_open; /* maximum number of input files and error insertions that can be going on simultaneously */ int param_size; /* maximum number of simultaneous macro parameters */ @ @<Option variables@>= int error_line; /* width of context lines on terminal error messages */ int half_error_line; /* width of first lines of contexts in terminal error messages; should be between 30 and |error_line-15| */ int halt_on_error; /* do we quit at the first error? */ int max_print_line; /* width of longest text lines output; should be at least 60 */ void *userdata; /* this allows the calling application to setup local */ char *banner; /* the banner that is printed to the screen and log */ int ini_version; @ @<Dealloc variables@>= xfree (mp->banner); @ @d set_lower_limited_value(a,b,c) do { a=c; if (b>c) a=b; } while (0) @<Allocate or ...@>= mp->param_size = 4; mp->max_in_open = 0; mp->pool_size = 10000; set_lower_limited_value (mp->error_line, opt->error_line, 79); set_lower_limited_value (mp->half_error_line, opt->half_error_line, 50); if (mp->half_error_line > mp->error_line - 15) mp->half_error_line = mp->error_line - 15; mp->max_print_line = 100; set_lower_limited_value (mp->max_print_line, opt->max_print_line, 79); mp->halt_on_error = (opt->halt_on_error ? true : false); mp->ini_version = (opt->ini_version ? true : false); @ In case somebody has inadvertently made bad settings of the ``constants,'' \MP\ checks them using a global variable called |bad|. This is the second of many sections of \MP\ where global variables are defined. @<Glob...@>= integer bad; /* is some ``constant'' wrong? */ @ Later on we will say `|if ( int_packets+17*int_increment>bistack_size )mp->bad=19;|', or something similar. In case you are wondering about the non-consequtive values of |bad|: most of the things that used to be WEB constants are now runtime variables with checking at assignment time. @<Check the ``constant'' values for consistency@>= mp->bad = 0; @ Some |goto| labels are used by the following definitions. 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|'. @ Here are some macros for common programming idioms. @d incr(A) (A)=(A)+1 /* increase a variable by unity */ @d decr(A) (A)=(A)-1 /* decrease a variable by unity */ @d negate(A) (A)=-(A) /* change the sign of a variable */ @d double(A) (A)=(A)+(A) @d odd(A) ((A)%2==1) @* 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...@>= typedef unsigned char ASCII_code; /* eight-bit numbers */ @ The present specification of \MP\ has been written under the assumption that the character set contains at least the letters and symbols associated with ASCII codes 040 through 0176; all of these characters are now available on most computer terminals. @<Types...@>= typedef unsigned char text_char; /* the data type of characters in text files */ @ @<Local variables for init...@>= integer i; @ 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. @(mpmp.h@>= #define xchr(A) mp->xchr[(A)] #define xord(A) mp->xord[(A)] @ @<Glob...@>= ASCII_code xord[256]; /* specifies conversion of input characters */ text_char xchr[256]; /* specifies conversion of output characters */ @ The core system assumes all 8-bit is acceptable. If it is not, a change file has to alter the below section. @^system dependencies@> Additionally, 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 initial ...@>= for (i = 0; i <= 0377; i++) { xchr (i) = (text_char) 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<0177|, 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 040 in case there is a coincidence. @<Set initial ...@>= for (i = 0; i <= 255; i++) { xord (xchr (i)) = 0177; } for (i = 0200; i <= 0377; i++) { xord (xchr (i)) = (ASCII_code) i; } for (i = 0; i <= 0176; i++) { xord (xchr (i)) = (ASCII_code) i; } @* 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. @ Finding files happens in a slightly roundabout fashion: the \MP\ instance object contains a field that holds a function pointer that finds a file, and returns its name, or NULL. For this, it receives three parameters: the non-qualified name |fname|, the intended |fopen| operation type |fmode|, and the type of the file |ftype|. The file types that are passed on in |ftype| can be used to differentiate file searches if a library like kpathsea is used, the fopen mode is passed along for the same reason. @<Types...@>= typedef unsigned char eight_bits; /* unsigned one-byte quantity */ @ @<Exported types@>= enum mp_filetype { mp_filetype_terminal = 0, /* the terminal */ mp_filetype_error, /* the terminal */ mp_filetype_program, /* \MP\ language input */ mp_filetype_log, /* the log file */ mp_filetype_postscript, /* the postscript output */ mp_filetype_memfile, /* memory dumps, obsolete */ mp_filetype_metrics, /* TeX font metric files */ mp_filetype_fontmap, /* PostScript font mapping files */ mp_filetype_font, /* PostScript type1 font programs */ mp_filetype_encoding, /* PostScript font encoding files */ mp_filetype_text /* first text file for readfrom and writeto primitives */ }; typedef char *(*mp_file_finder) (MP, const char *, const char *, int); typedef void *(*mp_file_opener) (MP, const char *, const char *, int); typedef char *(*mp_file_reader) (MP, void *, size_t *); typedef void (*mp_binfile_reader) (MP, void *, void **, size_t *); typedef void (*mp_file_closer) (MP, void *); typedef int (*mp_file_eoftest) (MP, void *); typedef void (*mp_file_flush) (MP, void *); typedef void (*mp_file_writer) (MP, void *, const char *); typedef void (*mp_binfile_writer) (MP, void *, void *, size_t); @ @<Option variables@>= mp_file_finder find_file; mp_file_opener open_file; mp_file_reader read_ascii_file; mp_binfile_reader read_binary_file; mp_file_closer close_file; mp_file_eoftest eof_file; mp_file_flush flush_file; mp_file_writer write_ascii_file; mp_binfile_writer write_binary_file; @ The default function for finding files is |mp_find_file|. It is pretty stupid: it will only find files in the current directory. This function may disappear altogether, it is currently only used for the default font map file. @c static char *mp_find_file (MP mp, const char *fname, const char *fmode, int ftype) { (void) mp; if (fmode[0] != 'r' || (!access (fname, R_OK)) || ftype) { return mp_strdup (fname); } return NULL; } @ Because |mp_find_file| is used so early, it has to be in the helpers section. @<Declarations@>= static char *mp_find_file (MP mp, const char *fname, const char *fmode, int ftype); static void *mp_open_file (MP mp, const char *fname, const char *fmode, int ftype); static char *mp_read_ascii_file (MP mp, void *f, size_t * size); static void mp_read_binary_file (MP mp, void *f, void **d, size_t * size); static void mp_close_file (MP mp, void *f); static int mp_eof_file (MP mp, void *f); static void mp_flush_file (MP mp, void *f); static void mp_write_ascii_file (MP mp, void *f, const char *s); static void mp_write_binary_file (MP mp, void *f, void *s, size_t t); @ The function to open files can now be very short. @c void *mp_open_file (MP mp, const char *fname, const char *fmode, int ftype) { char realmode[3]; (void) mp; realmode[0] = *fmode; realmode[1] = 'b'; realmode[2] = 0; if (ftype == mp_filetype_terminal) { return (fmode[0] == 'r' ? stdin : stdout); } else if (ftype == mp_filetype_error) { return stderr; } else if (fname != NULL && (fmode[0] != 'r' || (!access (fname, R_OK)))) { return (void *) fopen (fname, realmode); } return NULL; } @ (Almost) all file names pass through |name_of_file|. @<Glob...@>= char *name_of_file; /* the name of a system file */ @ If this parameter is true, the terminal and log will report the found file names for input files instead of the requested ones. It is off by default because it creates an extra filename lookup. @<Option variables@>= int print_found_names; /* configuration parameter */ @ @<Allocate or initialize ...@>= mp->print_found_names = (opt->print_found_names > 0 ? true : false); @ The |file_line_error_style| parameter makes \MP\ use a more standard compiler error message format instead of the Knuthian exclamation mark. It needs the actual version of the current input file name, that will be saved by |a_open_in| in the |long_name|. TODO: currently these long strings cause memory leaks, because they cannot be safely freed as they may appear in the |input_stack| multiple times. In fact, the current implementation is just a quick hack in response to a bug report for metapost 1.205. @d long_name mp->cur_input.long_name_field /* long name of the current file */ @<Option variables@>= int file_line_error_style; /* configuration parameter */ @ @<Allocate or initialize ...@>= mp->file_line_error_style = (opt->file_line_error_style > 0 ? true : false); @ \MP's file-opening procedures return |false| if no file identified by |name_of_file| could be opened. The |OPEN_FILE| macro takes care of the |print_found_names| parameter. @d OPEN_FILE(A) do { if (mp->print_found_names || mp->file_line_error_style) { char *s = (mp->find_file)(mp,mp->name_of_file,A,ftype); if (s!=NULL) { *f = (mp->open_file)(mp,mp->name_of_file,A, ftype); if (mp->print_found_names) { xfree(mp->name_of_file); mp->name_of_file = xstrdup(s); } if ((*(A) == 'r') && (ftype == mp_filetype_program)) { long_name = xstrdup(s); } xfree(s); } else { *f = NULL; } } else { *f = (mp->open_file)(mp,mp->name_of_file,A, ftype); } } while (0); return (*f ? true : false) @c static boolean mp_a_open_in (MP mp, void **f, int ftype) { /* open a text file for input */ OPEN_FILE ("r"); } @# static boolean mp_a_open_out (MP mp, void **f, int ftype) { /* open a text file for output */ OPEN_FILE ("w"); } @# static boolean mp_b_open_out (MP mp, void **f, int ftype) { /* open a binary file for output */ OPEN_FILE ("w"); } @ @c static char *mp_read_ascii_file (MP mp, void *ff, size_t * size) { int c; size_t len = 0, lim = 128; char *s = NULL; FILE *f = (FILE *) ff; *size = 0; (void) mp; /* for -Wunused */ if (f == NULL) return NULL; c = fgetc (f); if (c == EOF) return NULL; s = malloc (lim); if (s == NULL) return NULL; while (c != EOF && c != '\n' && c != '\r') { if ((len + 1) == lim) { s = realloc (s, (lim + (lim >> 2))); if (s == NULL) return NULL; lim += (lim >> 2); } s[len++] = (char) c; c = fgetc (f); } if (c == '\r') { c = fgetc (f); if (c != EOF && c != '\n') ungetc (c, f); } s[len] = 0; *size = len; return s; } @ @c void mp_write_ascii_file (MP mp, void *f, const char *s) { (void) mp; if (f != NULL) { fputs (s, (FILE *) f); } } @ @c void mp_read_binary_file (MP mp, void *f, void **data, size_t * size) { size_t len = 0; (void) mp; if (f != NULL) len = fread (*data, 1, *size, (FILE *) f); *size = len; } @ @c void mp_write_binary_file (MP mp, void *f, void *s, size_t size) { (void) mp; if (f != NULL) (void) fwrite (s, size, 1, (FILE *) f); } @ @c void mp_close_file (MP mp, void *f) { (void) mp; if (f != NULL) fclose ((FILE *) f); } @ @c int mp_eof_file (MP mp, void *f) { (void) mp; if (f != NULL) return feof ((FILE *) f); else return 1; } @ @c void mp_flush_file (MP mp, void *f) { (void) mp; if (f != NULL) fflush ((FILE *) f); } @ 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...@>= size_t buf_size; /* maximum number of characters simultaneously present in current lines of open files */ ASCII_code *buffer; /* lines of characters being read */ size_t first; /* the first unused position in |buffer| */ size_t last; /* end of the line just input to |buffer| */ size_t max_buf_stack; /* largest index used in |buffer| */ @ @<Allocate or initialize ...@>= mp->buf_size = 200; mp->buffer = xmalloc ((mp->buf_size + 1), sizeof (ASCII_code)); @ @<Dealloc variables@>= xfree (mp->buffer); @ @c static void mp_reallocate_buffer (MP mp, size_t l) { ASCII_code *buffer; if (l > max_halfword) { mp_confusion (mp, "buffer size"); /* can't happen (I hope) */ } buffer = xmalloc ((l + 1), sizeof (ASCII_code)); (void) memcpy (buffer, mp->buffer, (mp->buf_size + 1)); xfree (mp->buffer); mp->buffer = buffer; mp->buf_size = l; } @ 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@> 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|. @c static boolean mp_input_ln (MP mp, void *f) { /* inputs the next line or returns |false| */ char *s; size_t size = 0; mp->last = mp->first; /* cf.\ Matthew 19\thinspace:\thinspace30 */ s = (mp->read_ascii_file) (mp, f, &size); if (s == NULL) return false; if (size > 0) { mp->last = mp->first + size; if (mp->last >= mp->max_buf_stack) { mp->max_buf_stack = mp->last + 1; while (mp->max_buf_stack > mp->buf_size) { mp_reallocate_buffer (mp, (mp->buf_size + (mp->buf_size >> 2))); } } (void) memcpy ((mp->buffer + mp->first), s, size); } free (s); return true; } @ 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...@>= void *term_in; /* the terminal as an input file */ void *term_out; /* the terminal as an output file */ void *err_out; /* the terminal as an output file */ @ Here is how to open the terminal files. In the default configuration, nothing happens except that the command line (if there is one) is copied to the input buffer. The variable |command_line| will be filled by the |main| procedure. @d t_open_out do {/* open the terminal for text output */ mp->term_out = (mp->open_file)(mp,"terminal", "w", mp_filetype_terminal); mp->err_out = (mp->open_file)(mp,"error", "w", mp_filetype_error); } while (0) @d t_open_in do { /* open the terminal for text input */ mp->term_in = (mp->open_file)(mp,"terminal", "r", mp_filetype_terminal); if (mp->command_line!=NULL) { mp->last = strlen(mp->command_line); (void)memcpy((void *)mp->buffer,(void *)mp->command_line,mp->last); xfree(mp->command_line); } else { mp->last = 0; } } while (0) @<Option variables@>= char *command_line; @ 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: @^system dependencies@> @(mpmp.h@>= #define update_terminal (mp->flush_file)(mp,mp->term_out) /* empty the terminal output buffer */ #define clear_terminal /* clear the terminal input buffer */ #define wake_up_terminal (mp->flush_file)(mp,mp->term_out) /* 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. 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. @ 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 mp->cur_input.loc_field /* location of first unread character in |buffer| */ @c boolean mp_init_terminal (MP mp) { /* gets the terminal input started */ t_open_in; if (mp->last != 0) { loc = 0; mp->first = 0; return true; } while (1) { if (!mp->noninteractive) { wake_up_terminal; do_putsf (mp->term_out, "**"); @.**@>; update_terminal; } if (!mp_input_ln (mp, mp->term_in)) { /* this shouldn't happen */ do_putsf (mp->term_out, "\n! End of file on the terminal... why?"); @.End of file on the terminal@>; return false; } loc = (halfword) mp->first; while ((loc < (int) mp->last) && (mp->buffer[loc] == ' ')) incr (loc); if (loc < (int) mp->last) { return true; /* return unless the line was all blank */ } if (!mp->noninteractive) { do_putsf (mp->term_out, "Please type the name of your input file.\n"); } } } @ @<Declarations@>= static boolean mp_init_terminal (MP mp); @* String handling. Symbolic token names and diagnostic messages are variable-length strings of eight-bit characters. Many strings \MP\ uses are simply literals in the compiled source, like the error messages and the names of the internal parameters. Other strings are used or defined from the \MP\ input language, and these have to be interned. \MP\ uses strings more extensively than \MF\ does, but the necessary operations can still be handled with a fairly simple data structure. The avl tree |strings| contains all of the known string structures. Each structure contains an |unsigned char| pointer containing the eight-bit data, a |size_t| that holds the length of that data, and an |int| that indicates how often this string is referenced (this will be explained below). Such strings are referred to by structure pointers called |str_number|. Besides the avl tree, there is a set of three variables called |cur_string|, |cur_length| and |cur_string_size| that are used for strings while they are being built. @<Exported types...@>= typedef struct { unsigned char *str; /* the string value */ size_t len; /* its length */ int refs; /* number of references */ } mp_lstring; typedef mp_lstring *str_number; /* for pointers to string values */ @ @<Glob...@>= avl_tree strings; /* string avl tree */ unsigned char *cur_string; /* current string buffer */ size_t cur_length; /* current index in that buffer */ size_t cur_string_size; /* malloced size of |cur_string| */ @ Here are the functions needed for the avl construction. @<Declarations@>= static int comp_strings_entry (void *p, const void *pa, const void *pb); static void *copy_strings_entry (const void *p); static void *delete_strings_entry (void *p); @ An earlier version of this function used |strncmp|, but that produces wrong results in some cases. @c #define STRCMP_RESULT(a) ((a)<0 ? -1 : ((a)>0 ? 1 : 0)) static int comp_strings_entry (void *p, const void *pa, const void *pb) { const mp_lstring *a = (const mp_lstring *) pa; const mp_lstring *b = (const mp_lstring *) pb; size_t l; unsigned char *s,*t; (void) p; s = a->str; t = b->str; l = (a->len<=b->len ? a->len : b->len); while ( l-->0 ) { if ( *s!=*t) return STRCMP_RESULT(*s-*t); s++; t++; } return STRCMP_RESULT((int)(a->len)-(int)(b->len)); } static void *copy_strings_entry (const void *p) { str_number ff; const mp_lstring *fp; fp = (const mp_lstring *) p; ff = malloc (sizeof (mp_lstring)); if (ff == NULL) return NULL; ff->str = malloc (fp->len + 1); if (ff->str == NULL) { return NULL; } memcpy ((char *) ff->str, (char *) fp->str, fp->len + 1); ff->len = fp->len; ff->refs = 0; return ff; } static void *delete_strings_entry (void *p) { str_number ff = (str_number) p; mp_xfree (ff->str); mp_xfree (ff); return NULL; } @ @<Allocate or initialize ...@>= mp->strings = avl_create (comp_strings_entry, copy_strings_entry, delete_strings_entry, malloc, free, NULL); mp->cur_string = NULL; mp->cur_length = 0; mp->cur_string_size = 0; @ @<Dealloc variables@>= if (mp->strings != NULL) avl_destroy (mp->strings); xfree (mp->cur_string); @ Actually creating strings is done by |make_string|, but in order to do so it needs a way to create a new, empty string structure. @<Declarations@>= static str_number new_strings_entry (MP mp); @ @c static str_number new_strings_entry (MP mp) { str_number ff; ff = mp_xmalloc (mp, 1, sizeof (mp_lstring)); ff->str = NULL; ff->len = 0; ff->refs = 0; return ff; } @ Most printing is done from |char *|s, but sometimes not. Here are functions that convert an internal string into a |char *| for use by the printing routines, and vice versa. @d null_str mp_rts(mp,"") @<Internal ...@>= int mp_xstrcmp (const char *a, const char *b); char *mp_str (MP mp, str_number s); @ @<Declarations@>= static str_number mp_rtsl (MP mp, const char *s, size_t l); static str_number mp_rts (MP mp, const char *s); static str_number mp_make_string (MP mp); @ @c int mp_xstrcmp (const char *a, const char *b) { if (a == NULL && b == NULL) return 0; if (a == NULL) return -1; if (b == NULL) return 1; return strcmp (a, b); } @ @c char *mp_str (MP mp, str_number ss) { (void) mp; return (char *) ss->str; } str_number mp_rtsl (MP mp, const char *s, size_t l) { str_number str; mp_lstring tmp; tmp.str = xmalloc (l + 1, 1); memcpy (tmp.str, s, (l + 1)); tmp.len = l; str = (str_number) avl_find (&tmp, mp->strings); if (str == NULL) { /* not yet known */ str = new_strings_entry (mp); str->str = xmalloc (l + 1, 1); memcpy (str->str, s, (l + 1)); str->len = tmp.len; assert (avl_ins (str, mp->strings, avl_false) > 0); xfree (str->str); xfree (str); str = (str_number) avl_find (&tmp, mp->strings); } str->refs++; free (tmp.str); return str; } str_number mp_rts (MP mp, const char *s) { return mp_rtsl (mp, s, strlen (s)); } @ The next four variables for keeping track of string pool usage. @<Glob...@>= integer pool_in_use; /* total number of string bytes actually in use */ integer max_pl_used; /* maximum |pool_in_use| so far */ integer strs_in_use; /* total number of strings actually in use */ integer max_strs_used; /* maximum |strs_in_use| so far */ @ Several of the elementary string operations are performed using \.{WEB} macros instead of functions, 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 length(A) ((A)->len) /* the number of characters in string \# */ @ Strings are created by appending character codes to |cur_string|. The |append_char| macro, defined here, does not check to see if the buffer overflows; this test is supposed to be made before |append_char| is used. To test if there is room to append |l| more characters to |cur_string|, we shall write |str_room(l)|, which tries to make sure there is enough room in the |cur_string|. @d EXTRA_STRING 500 @d append_char(A) do { if (mp->cur_string==NULL) reset_cur_string(mp); else str_room(1); *(mp->cur_string+mp->cur_length)=(unsigned char)(A); mp->cur_length++; } while (0) @d str_room(wsize) do { size_t nsize; if ((mp->cur_length+(size_t)wsize) > mp->cur_string_size) { nsize = mp->cur_string_size + mp->cur_string_size / 5 + EXTRA_STRING; if (nsize < (size_t)(wsize)) { nsize = (size_t)wsize + EXTRA_STRING; } mp->cur_string = (unsigned char *) xrealloc(mp->cur_string, (unsigned)nsize, sizeof(unsigned char)); memset (mp->cur_string+mp->cur_length,0,(nsize-mp->cur_length)); mp->cur_string_size = nsize; } } while (0) @ At the very start of the metapost run and each time after |make_string| has stored a new string in the avl tree, the |cur_string| variable has to be prepared so that it will be ready to start creating a new string. The initial size is fairly arbitrary, but setting it a little higher than expected helps prevent |reallocs| @<Declarations@>= static void reset_cur_string (MP mp); @ @c static void reset_cur_string (MP mp) { xfree (mp->cur_string); mp->cur_length = 0; mp->cur_string_size = 63; mp->cur_string = (unsigned char *) xmalloc (64, sizeof (unsigned char)); memset (mp->cur_string, 0, 64); } @ \MP's string expressions are implemented in a brute-force way: Every new string or substring that is needed is simply stored into the string pool. Space is eventually reclaimed using the aid of a simple system system of reference counts. @^reference counts@> The number of references to string number |s| will be |s->refs|. The special value |s->refs=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. @d MAX_STR_REF 127 /* ``infinite'' number of references */ @d add_str_ref(A) { if ( (A)->refs < MAX_STR_REF ) incr((A)->refs); } @ Here's what we do when a string reference disappears: @d delete_str_ref(A) { if ( (A)->refs < MAX_STR_REF ) { if ( (A)->refs > 1 ) decr((A)->refs); else mp_flush_string(mp, (A)); } } @<Declarations@>= static void mp_flush_string (MP mp, str_number s); @ @c void mp_flush_string (MP mp, str_number s) { if (s->refs == 0) { decr (mp->strs_in_use); mp->pool_in_use = mp->pool_in_use - (integer) length (s); (void) avl_del (s, mp->strings, NULL); } } @ Some C literals that are used as values cannot be simply added, their reference count has to be set such that they can not be flushed. @c str_number mp_intern (MP mp, const char *s) { str_number r; r = mp_rts (mp, s); r->refs = MAX_STR_REF; return r; } @ @<Declarations@>= static str_number mp_intern (MP mp, const char *s); @ Once a sequence of characters has been appended to |cur_string|, it officially becomes a string when the function |make_string| is called. This function returns a pointer to the new string as its value. @<Declarations@>= static str_number mp_make_string (MP mp); @ @c str_number mp_make_string (MP mp) { /* current string enters the pool */ str_number str; mp_lstring tmp; tmp.str = mp->cur_string; tmp.len = mp->cur_length; str = (str_number) avl_find (&tmp, mp->strings); if (str == NULL) { /* not yet known */ str = xmalloc (1, sizeof (mp_lstring)); str->str = mp->cur_string; str->len = tmp.len; assert (avl_ins (str, mp->strings, avl_false) > 0); str = (str_number) avl_find (&tmp, mp->strings); mp->pool_in_use = mp->pool_in_use + (integer) length (str); if (mp->pool_in_use > mp->max_pl_used) mp->max_pl_used = mp->pool_in_use; incr (mp->strs_in_use); if (mp->strs_in_use > mp->max_strs_used) mp->max_strs_used = mp->strs_in_use; str->refs = 1; } reset_cur_string (mp); return str; } @ Here is a routine that 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. @c static integer mp_str_vs_str (MP mp, str_number s, str_number t) { (void) mp; return comp_strings_entry (NULL, (const void *) s, (const void *) t); } @ 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[032]=@t\.{'^^Z'}@>|, would like string 032 to be printed as the single character 032 instead of the three characters 0136, 0136, 0132 (\.{\^\^Z}). On the other hand, even people with an extended character set will want to represent string 015 by \.{\^\^M}, since 015 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. 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<0200|, then character |k+0100| or |k-0100| must be printable; moreover, ASCII codes |[060..071, 0141..0146]| must be printable. @^character set dependencies@> @^system dependencies@> @<Character |k| cannot be printed@>= (k < ' ') || (k == 127) @* 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 |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 |>=write_file| 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|, or |new_string|. We need not check for unprintable characters when |selector<pseudo|. Three additional global variables, |tally|, |term_offset| and |file_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|, and |file_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 0 /* printing is deflected to the string pool */ @d pseudo 2 /* special |selector| setting for |show_context| */ @d no_print 3 /* |selector| setting that makes data disappear */ @d term_only 4 /* printing is destined for the terminal only */ @d log_only 5 /* printing is destined for the transcript file only */ @d term_and_log 6 /* normal |selector| setting */ @d write_file 7 /* first write file selector */ @<Glob...@>= void *log_file; /* transcript of \MP\ session */ void *output_file; /* the generic font output goes here */ unsigned int selector; /* where to print a message */ integer tally; /* the number of characters recently printed */ unsigned int term_offset; /* the number of characters on the current terminal line */ unsigned int file_offset; /* the number of characters on the current file line */ ASCII_code *trick_buf; /* circular buffer for pseudoprinting */ integer trick_count; /* threshold for pseudoprinting, explained later */ integer first_count; /* another variable for pseudoprinting */ @ @<Allocate or initialize ...@>= mp->trick_buf = xmalloc ((mp->error_line + 1), sizeof (ASCII_code)); @ @<Dealloc variables@>= xfree (mp->trick_buf); @ @<Initialize the output routines@>= mp->selector = term_only; mp->tally = 0; mp->term_offset = 0; mp->file_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@> @(mpmp.h@>= #define do_putsf(f,b) (mp->write_ascii_file)(mp,f,b) #define wterm(A) do_putsf(mp->term_out,(A)) #define wterm_chr(A) { unsigned char ss[2]; ss[0]=(A); ss[1]='\0'; wterm((char *)ss);} #define wterm_cr do_putsf(mp->term_out,"\n") #define wterm_ln(A) { wterm_cr; do_putsf(mp->term_out,(A)); } #define wlog(A) do_putsf(mp->log_file,(A)) #define wlog_chr(A) { unsigned char ss[2]; ss[0]=(A); ss[1]='\0'; wlog((char *)ss);} #define wlog_cr do_putsf(mp->log_file, "\n") #define wlog_ln(A) { wlog_cr; do_putsf(mp->log_file,(A)); } @ 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. @d mp_print_text(A) mp_print_str(mp,text((A))) @<Internal library ...@>= void mp_print (MP mp, const char *s); void mp_print_ln (MP mp); void mp_print_char (MP mp, ASCII_code k); void mp_print_str (MP mp, str_number s); void mp_print_nl (MP mp, const char *s); void mp_print_two (MP mp, scaled x, scaled y); @ @<Declarations@>= static void mp_print_visible_char (MP mp, ASCII_code s); @ @<Basic print...@>= void mp_print_ln (MP mp) { /* prints an end-of-line */ switch (mp->selector) { case term_and_log: wterm_cr; wlog_cr; mp->term_offset = 0; mp->file_offset = 0; break; case log_only: wlog_cr; mp->file_offset = 0; break; case term_only: wterm_cr; mp->term_offset = 0; break; case no_print: case pseudo: case new_string: break; default: do_putsf (mp->wr_file[(mp->selector - write_file)], "\n"); } } /* 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. @<Basic printing...@>= static void mp_print_visible_char (MP mp, ASCII_code s) { /* prints a single character */ switch (mp->selector) { case term_and_log: wterm_chr (xchr (s)); wlog_chr (xchr (s)); incr (mp->term_offset); incr (mp->file_offset); if (mp->term_offset == (unsigned) mp->max_print_line) { wterm_cr; mp->term_offset = 0; }; if (mp->file_offset == (unsigned) mp->max_print_line) { wlog_cr; mp->file_offset = 0; }; break; case log_only: wlog_chr (xchr (s)); incr (mp->file_offset); if (mp->file_offset == (unsigned) mp->max_print_line) mp_print_ln (mp); break; case term_only: wterm_chr (xchr (s)); incr (mp->term_offset); if (mp->term_offset == (unsigned) mp->max_print_line) mp_print_ln (mp); break; case no_print: break; case pseudo: if (mp->tally < mp->trick_count) mp->trick_buf[mp->tally % mp->error_line] = s; break; case new_string: append_char (s); break; default: { text_char ss[2]; ss[0] = xchr (s); ss[1] = 0; do_putsf (mp->wr_file[(mp->selector - write_file)], (char *) ss); } } incr (mp->tally); } @ 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|. The user might want to write unprintable characters. @<Basic printing...@>= void mp_print_char (MP mp, ASCII_code k) { /* prints a single character */ if (mp->selector < pseudo || mp->selector >= write_file) { mp_print_visible_char (mp, k); } else if (@<Character |k| cannot be printed@>) { mp_print (mp, "^^"); if (k < 0100) { mp_print_visible_char (mp, (ASCII_code) (k + 0100)); } else if (k < 0200) { mp_print_visible_char (mp, (ASCII_code) (k - 0100)); } else { int l; /* small index or counter */ l = (k / 16); mp_print_visible_char (mp, xord (l < 10 ? l + '0' : l - 10 + 'a')); l = (k % 16); mp_print_visible_char (mp, xord (l < 10 ? l + '0' : l - 10 + 'a')); } } else { mp_print_visible_char (mp, k); } } @ 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...@>= static void mp_do_print (MP mp, const char *ss, size_t len) { /* prints string |s| */ size_t j = 0; if (mp->selector == new_string) { str_room ((len * 4)); } while (j < len) { /* this was |xord((int)ss[j])| but that doesnt work */ mp_print_char (mp, (ASCII_code) ss[j]); j++; } } @ @<Basic print...@>= void mp_print (MP mp, const char *ss) { if (ss == NULL) return; mp_do_print (mp, ss, strlen (ss)); } void mp_print_str (MP mp, str_number s) { assert (s != NULL); mp_do_print (mp, (const char *) s->str, s->len); } @ Here is the very first thing that \MP\ prints: a headline that identifies the version number and base name. The |term_offset| variable is temporarily incorrect, but the discrepancy is not serious since we assume that the banner and mem identifier together will occupy at most |max_print_line| character positions. @<Initialize the output...@>= wterm (mp->banner); if (mp->mem_ident != NULL) mp_print (mp, mp->mem_ident); mp_print_ln (mp); 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...@>= void mp_print_nl (MP mp, const char *s) { /* prints string |s| at beginning of line */ switch (mp->selector) { case term_and_log: if ((mp->term_offset > 0) || (mp->file_offset > 0)) mp_print_ln (mp); break; case log_only: if (mp->file_offset > 0) mp_print_ln (mp); break; case term_only: if (mp->term_offset > 0) mp_print_ln (mp); break; case no_print: case pseudo: case new_string: break; } /* there are no other cases */ mp_print (mp, s); } @ The following procedure, which prints out the decimal representation of a given integer |n|, assumes that all integers fit nicely into a |int|. @^system dependencies@> @<Basic print...@>= void mp_print_int (MP mp, integer n) { /* prints an integer in decimal form */ char s[12]; mp_snprintf (s, 12, "%d", (int) n); mp_print (mp, s); } @ @<Internal library ...@>= void mp_print_int (MP mp, integer n); @ \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|. @c static void mp_print_dd (MP mp, integer n) { /* prints two least significant digits */ n = abs (n) % 100; mp_print_char (mp, xord ('0' + (n / 10))); mp_print_char (mp, xord ('0' + (n % 10))); } @ @<Declarations@>= static void mp_print_dd (MP mp, integer n); @ 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<mp_scroll_mode|. @d prompt_input(A) do { if (!mp->noninteractive) { wake_up_terminal; mp_print(mp, (A)); } mp_term_input(mp); } while (0) /* prints a string and gets a line of input */ @c void mp_term_input (MP mp) { /* gets a line from the terminal */ size_t k; /* index into |buffer| */ if (mp->noninteractive) { if (!mp_input_ln (mp, mp->term_in)) longjmp (*(mp->jump_buf), 1); /* chunk finished */ mp->buffer[mp->last] = xord ('%'); } else { update_terminal; /* Now the user sees the prompt for sure */ if (!mp_input_ln (mp, mp->term_in)) { mp_fatal_error (mp, "End of file on the terminal!"); @.End of file on the terminal@> } mp->term_offset = 0; /* the user's line ended with \<\rm return> */ decr (mp->selector); /* prepare to echo the input */ if (mp->last != mp->first) { for (k = mp->first; k < mp->last; k++) { mp_print_char (mp, mp->buffer[k]); } } mp_print_ln (mp); mp->buffer[mp->last] = xord ('%'); incr (mp->selector); /* restore previous status */ } } @* 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: @<Exported types@>= enum mp_interaction_mode { mp_unspecified_mode = 0, /* extra value for command-line switch */ mp_batch_mode, /* omits all stops and omits terminal output */ mp_nonstop_mode, /* omits all stops */ mp_scroll_mode, /* omits error stops */ mp_error_stop_mode /* stops at every opportunity to interact */ }; @ @<Option variables@>= int interaction; /* current level of interaction */ int noninteractive; /* do we have a terminal? */ @ Set it here so it can be overwritten by the commandline @<Allocate or initialize ...@>= mp->interaction = opt->interaction; if (mp->interaction == mp_unspecified_mode || mp->interaction > mp_error_stop_mode) mp->interaction = mp_error_stop_mode; if (mp->interaction < mp_unspecified_mode) mp->interaction = mp_batch_mode; @ @d print_err(A) mp_print_err(mp,(A)) @<Internal ...@>= void mp_print_err (MP mp, const char *A); @ @c void mp_print_err (MP mp, const char *A) { if (mp->interaction == mp_error_stop_mode) wake_up_terminal; if (mp->file_line_error_style && file_state && !terminal_input) { mp_print_nl (mp, ""); if (long_name != NULL) { mp_print (mp, long_name); } else { mp_print (mp, mp_str (mp, name)); } mp_print (mp, ":"); mp_print_int (mp, line); mp_print (mp, ": "); } else { mp_print_nl (mp, "! "); } mp_print (mp, A); @.!\relax@> } @ \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=mp_batch_mode| and |log_file| not yet open); \hang|term_only| (when |interaction>mp_batch_mode| and |log_file| not yet open); \hang|log_only| (when |interaction=mp_batch_mode| and |log_file| is open); \hang|term_and_log| (when |interaction>mp_batch_mode| and |log_file| is open). @<Initialize the print |selector| based on |interaction|@>= if (mp->interaction == mp_batch_mode) mp->selector = no_print; else mp->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. @<Exported types@>= enum mp_history_state { mp_spotless = 0, /* |history| value when nothing has been amiss yet */ mp_warning_issued, /* |history| value when |begin_diagnostic| has been called */ mp_error_message_issued, /* |history| value when |error| has been called */ mp_fatal_error_stop, /* |history| value when termination was premature */ mp_system_error_stop /* |history| value when termination was due to disaster */ }; @ @<Glob...@>= boolean deletions_allowed; /* is it safe for |error| to call |get_next|? */ int history; /* has the source input been clean so far? */ int error_count; /* 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. @<Allocate or ...@>= mp->deletions_allowed = true; /* |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. @<Declarations@>= static void mp_get_next (MP mp); static void mp_term_input (MP mp); static void mp_show_context (MP mp); static void mp_begin_file_reading (MP mp); static void mp_open_log_file (MP mp); static void mp_clear_for_error_prompt (MP mp); @ @<Internal ...@>= void mp_normalize_selector (MP mp); @ 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(A) mp->help_line[0]=A; } @d hlp2(A,B) mp->help_line[1]=A; hlp1(B) @d hlp3(A,B,C) mp->help_line[2]=A; hlp2(B,C) @d hlp4(A,B,C,D) mp->help_line[3]=A; hlp3(B,C,D) @d hlp5(A,B,C,D,E) mp->help_line[4]=A; hlp4(B,C,D,E) @d hlp6(A,B,C,D,E,F) mp->help_line[5]=A; hlp5(B,C,D,E,F) @d help0 mp->help_ptr=0 /* sometimes there might be no help */ @d help1 { mp->help_ptr=1; hlp1 /* use this with one help line */ @d help2 { mp->help_ptr=2; hlp2 /* use this with two help lines */ @d help3 { mp->help_ptr=3; hlp3 /* use this with three help lines */ @d help4 { mp->help_ptr=4; hlp4 /* use this with four help lines */ @d help5 { mp->help_ptr=5; hlp5 /* use this with five help lines */ @d help6 { mp->help_ptr=6; hlp6 /* use this with six help lines */ @<Glob...@>= const char *help_line[6]; /* helps for the next |error| */ unsigned int help_ptr; /* the number of help lines present */ boolean use_err_help; /* should the |err_help| string be shown? */ str_number err_help; /* a string set up by \&{errhelp} */ @ @<Allocate or ...@>= mp->use_err_help = false; @ 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. The program uses a |jump_buf| to handle this, this is initialized at three spots: the start of |mp_new|, the start of |mp_initialize|, and the start of |mp_run|. Those are the only library enty points. @^system dependencies@> @<Glob...@>= jmp_buf *jump_buf; @ If the array of internals is still |NULL| when |jump_out| is called, a crash occured during initialization, and it is not safe to run the normal cleanup routine. @<Error hand...@>= static void mp_jump_out (MP mp) { if (mp->internal != NULL && mp->history < mp_system_error_stop) mp_close_files_and_terminate (mp); longjmp (*(mp->jump_buf), 1); } @ Here now is the general |error| routine. @<Error hand...@>= void mp_error (MP mp) { /* completes the job of error reporting */ ASCII_code c; /* what the user types */ integer s1, s2; /* used to save global variables when deleting tokens */ mp_sym s3; /* likewise */ if (mp->history < mp_error_message_issued) mp->history = mp_error_message_issued; mp_print_char (mp, xord ('.')); mp_show_context (mp); if (mp->halt_on_error) { mp->history = mp_fatal_error_stop; mp_jump_out (mp); } if ((!mp->noninteractive) && (mp->interaction == mp_error_stop_mode)) { @<Get user's advice and |return|@>; } incr (mp->error_count); if (mp->error_count == 100) { mp_print_nl (mp, "(That makes 100 errors; please try again.)"); @.That makes 100 errors...@>; mp->history = mp_fatal_error_stop; mp_jump_out (mp); } @<Put help message on the transcript file@>; } void mp_warn (MP mp, const char *msg) { unsigned saved_selector = mp->selector; mp_normalize_selector (mp); mp_print_nl (mp, "Warning: "); mp_print (mp, msg); mp_print_ln (mp); mp->selector = saved_selector; } @ @<Exported function ...@>= extern void mp_error (MP mp); extern void mp_warn (MP mp, const char *msg); @ @<Get user's advice...@>= while (true) { CONTINUE: mp_clear_for_error_prompt (mp); prompt_input ("? "); @.?\relax@>; if (mp->last == mp->first) return; c = mp->buffer[mp->first]; if (c >= 'a') c = (ASCII_code) (c + 'A' - 'a'); /* convert to uppercase */ @<Interpret code |c| and |return| if done@>; } @ 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@> @<Exported types@>= typedef void (*mp_editor_cmd) (MP, char *, int); @ @<Option variables@>= mp_editor_cmd run_editor; @ @<Allocate or initialize ...@>= set_callback_option (run_editor); @ @<Declarations@>= static void mp_run_editor (MP mp, char *fname, int fline); @ @c void mp_run_editor (MP mp, char *fname, int fline) { char *s = xmalloc (256, 1); mp_snprintf (s, 256, "You want to edit file %s at line %d\n", fname, fline); wterm_ln (s); @.You want to edit file x@> } @ There is a secret `\.D' option available when the debugging routines haven't been commented~out. @^debugging@> @<Interpret code |c| and |return| if done@>= switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (mp->deletions_allowed) { @<Delete tokens and |continue|@>; } break; case 'E': if (mp->file_ptr > 0) { mp->interaction = mp_scroll_mode; mp_close_files_and_terminate (mp); (mp->run_editor) (mp, mp_str (mp, mp->input_stack[mp->file_ptr].name_field), mp_true_line (mp)); mp_jump_out (mp); } break; case 'H': @<Print the help information and |continue|@>; /* |break;| */ case 'I': @<Introduce new material from the terminal and |return|@>; /* |break;| */ case 'Q': case 'R': case 'S': @<Change the interaction level and |return|@>; /* |break;| */ case 'X': mp->interaction = mp_scroll_mode; mp_jump_out (mp); break; default: break; } @<Print the menu of available options@> @ @<Print the menu...@>= { mp_print (mp, "Type <return> to proceed, S to scroll future error messages,"); @.Type <return> to proceed...@>; mp_print_nl (mp, "R to run without stopping, Q to run quietly,"); mp_print_nl (mp, "I to insert something, "); if (mp->file_ptr > 0) mp_print (mp, "E to edit your file,"); if (mp->deletions_allowed) mp_print_nl (mp, "1 or ... or 9 to ignore the next 1 to 9 tokens of input,"); mp_print_nl (mp, "H for help, X to quit."); } @ Here the author of \MP\ apologizes for making use of the numerical relation between |"Q"|, |"R"|, |"S"|, and the desired interaction settings |mp_batch_mode|, |mp_nonstop_mode|, |mp_scroll_mode|. @^Knuth, Donald Ervin@> @<Change the interaction...@>= { mp->error_count = 0; mp->interaction = mp_batch_mode + c - 'Q'; mp_print (mp, "OK, entering "); switch (c) { case 'Q': mp_print (mp, "batchmode"); decr (mp->selector); break; case 'R': mp_print (mp, "nonstopmode"); break; case 'S': mp_print (mp, "scrollmode"); break; } /* there are no other cases */ mp_print (mp, "..."); mp_print_ln (mp); update_terminal; return; } @ 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...@>= { mp_begin_file_reading (mp); /* enter a new syntactic level for terminal input */ if (mp->last > mp->first + 1) { loc = (halfword) (mp->first + 1); mp->buffer[mp->first] = xord (' '); } else { prompt_input ("insert>"); loc = (halfword) mp->first; @.insert>@> } mp->first = mp->last + 1; mp->cur_input.limit_field = (halfword) mp->last; return; } @ We allow deletion of up to 99 tokens at a time. @<Delete tokens...@>= { s1 = mp->cur_cmd; s2 = mp->cur_mod; s3 = mp->cur_sym; mp->OK_to_interrupt = false; if ((mp->last > mp->first + 1) && (mp->buffer[mp->first + 1] >= '0') && (mp->buffer[mp->first + 1] <= '9')) c = xord (c * 10 + mp->buffer[mp->first + 1] - '0' * 11); else c = (ASCII_code) (c - '0'); while (c > 0) { mp_get_next (mp); /* one-level recursive call of |error| is possible */ @<Decrease the string reference count, if the current token is a string@>; c--; }; mp->cur_cmd = s1; mp->cur_mod = s2; mp->cur_sym = s3; mp->OK_to_interrupt = true; help2 ("I have just deleted some text, as you asked.", "You can now delete more, or insert, or whatever."); mp_show_context (mp); goto CONTINUE; } @ @<Print the help info...@>= { if (mp->use_err_help) { @<Print the string |err_help|, possibly on several lines@>; mp->use_err_help = false; } else { if (mp->help_ptr == 0) { help2 ("Sorry, I don't know how to help in this situation.", "Maybe you should try asking a human?"); } do { decr (mp->help_ptr); mp_print (mp, mp->help_line[mp->help_ptr]); mp_print_ln (mp); } while (mp->help_ptr != 0); }; 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; } @ @<Print the string |err_help|, possibly on several lines@>= { size_t j = 0; while (j < length (mp->err_help)) { if (*(mp->err_help->str + j) != '%') mp_print (mp, (const char *) (mp->err_help->str + j)); else if (j + 1 == length (mp->err_help)) mp_print_ln (mp); else if (*(mp->err_help->str + j) != '%') mp_print_ln (mp); else { j++; mp_print_char (mp, xord ('%')); }; j++; } } @ @<Put help message on the transcript file@>= if (mp->interaction > mp_batch_mode) decr (mp->selector); /* avoid terminal output */ if (mp->use_err_help) { mp_print_nl (mp, ""); @<Print the string |err_help|, possibly on several lines@>; } else { while (mp->help_ptr > 0) { decr (mp->help_ptr); mp_print_nl (mp, mp->help_line[mp->help_ptr]); }; mp_print_ln (mp); if (mp->interaction > mp_batch_mode) incr (mp->selector); /* re-enable terminal output */ mp_print_ln (mp); } @ 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. @c void mp_normalize_selector (MP mp) { if (mp->log_opened) mp->selector = term_and_log; else mp->selector = term_only; if (mp->job_name == NULL) mp_open_log_file (mp); if (mp->interaction == mp_batch_mode) decr (mp->selector); } @ The following procedure prints \MP's last words before dying. @d succumb { if ( mp->interaction==mp_error_stop_mode ) mp->interaction=mp_scroll_mode; /* no more interaction */ if ( mp->log_opened ) mp_error(mp); mp->history=mp_fatal_error_stop; mp_jump_out(mp); /* irrecoverable error */ } @<Error hand...@>= void mp_fatal_error (MP mp, const char *s) { /* prints |s|, and that's it */ mp_normalize_selector (mp); print_err ("Emergency stop"); help1 (s); succumb; @.Emergency stop@> } @ @<Exported function ...@>= extern void mp_fatal_error (MP mp, const char *s); @ Here is the most dreaded error message. @<Error hand...@>= void mp_overflow (MP mp, const char *s, integer n) { /* stop due to finiteness */ char msg[256]; mp_normalize_selector (mp); mp_snprintf (msg, 256, "MetaPost capacity exceeded, sorry [%s=%d]", s, (int) n); @.MetaPost capacity exceeded ...@>; print_err (msg); help2 ("If you really absolutely need more capacity,", "you can ask a wizard to enlarge me."); succumb; } @ @<Internal library declarations@>= void mp_overflow (MP mp, const char *s, integer n); @ 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@> @<Internal library ...@>= void mp_confusion (MP mp, const char *s); @ Consistency check violated; |s| tells where. @<Error hand...@>= void mp_confusion (MP mp, const char *s) { char msg[256]; mp_normalize_selector (mp); if (mp->history < mp_error_message_issued) { mp_snprintf (msg, 256, "This can't happen (%s)", s); @.This can't happen@>; print_err (msg); help1 ("I'm broken. Please show this to someone who can fix can fix"); } else { 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."); } succumb; } @ Users occasionally want to interrupt \MP\ while it's running. If the 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 C debugger. @^system dependencies@> @^debugging@> @d check_interrupt { if ( mp->interrupt!=0 ) mp_pause_for_instructions(mp); } @<Global...@>= integer interrupt; /* should \MP\ pause for instructions? */ boolean OK_to_interrupt; /* should interrupts be observed? */ integer run_state; /* are we processing input ? */ boolean finished; /* set true by |close_files_and_terminate| */ boolean reading_preload; @ @<Allocate or ...@>= mp->OK_to_interrupt = true; mp->finished = false; @ 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. @c static void mp_pause_for_instructions (MP mp) { if (mp->OK_to_interrupt) { mp->interaction = mp_error_stop_mode; if ((mp->selector == log_only) || (mp->selector == no_print)) incr (mp->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'."); mp->deletions_allowed = false; mp_error (mp); mp->deletions_allowed = true; mp->interrupt = 0; } } @ 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. @c static void mp_missing_err (MP mp, const char *s) { char msg[256]; mp_snprintf (msg, 256, "Missing `%s' has been inserted", s); @.Missing...inserted@>; print_err (msg); } @* 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 C does not rigidly define the |/| operation in the case of negative dividends; for example, the result of |(-2*n-1) / 2| is |-(n+1)| on some computers and |-n| on others (is this true ?). There are two principal types of arithmetic: ``translation-preserving,'' in which the identity |(a+q*b)/b=(a/b)+q| is valid; and ``negation-preserving,'' in which |(-a)/b=-(a/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 ((math_data *)mp->math)->max_scaled_ @ 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. @^overflow in arithmetic@> @<Glob...@>= boolean arith_error; /* has arithmetic overflow occurred recently? */ @ @<Allocate or ...@>= mp->arith_error = false; @ At crucial points the program will say |check_arith|, to test if an arithmetic error has been detected. @d check_arith do { if ( mp->arith_error ) mp_clear_arith(mp); } while (0) @c static void mp_clear_arith (MP mp) { 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."); mp_error (mp); mp->arith_error = false; } @ The definitions of these are set up by the math initialization. @d unity ((math_data *)mp->math)->unity_ @d two ((math_data *)mp->math)->two_ @d three ((math_data *)mp->math)->three_ @d half_unit ((math_data *)mp->math)->half_unit_ @d three_quarter_unit ((math_data *)mp->math)->three_quarter_unit_ @ In fact, the two sorts of scaling discussed above aren't quite sufficient; \MP\ has yet another, used internally to keep track of angles. @<Exported types...@>= #if 1 typedef int scaled; /* this type is used for scaled integers */ #else typedef struct scaled { int val; } scaled; #endif typedef int fraction; /* this type is used for scaled fractions */ typedef int angle; /* this type is used for scaled angles */ @ We often want to print two scaled quantities in parentheses, separated by a comma. @<Basic printing...@>= void mp_print_two (MP mp, scaled x, scaled y) { /* prints `|(x,y)|' */ mp_print_char (mp, xord ('(')); mp_print_scaled (mp, x); mp_print_char (mp, xord (',')); mp_print_scaled (mp, y); mp_print_char (mp, xord (')')); } @ @d fraction_one ((math_data *)mp->math)->fraction_one_ @d fraction_half ((math_data *)mp->math)->fraction_half_ @d fraction_two ((math_data *)mp->math)->fraction_two_ @d fraction_three ((math_data *)mp->math)->fraction_three_ @d fraction_four ((math_data *)mp->math)->fraction_four_ @d ninety_deg ((math_data *)mp->math)->ninety_deg_ @d one_eighty_deg ((math_data *)mp->math)->one_eighty_deg_ @d three_sixty_deg ((math_data *)mp->math)->three_sixty_deg_ @ @<Local variables for initialization@>= integer k; /* all-purpose loop index */ @ 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 |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 initialization code below will initialize this variable to |(internal[mp_time] div unity)+internal[mp_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...@>= fraction randoms[55]; /* the last 55 random values generated */ int j_random; /* the number of unused |randoms| */ @ @<Option variables@>= int random_seed; /* the default random seed */ @ @<Allocate or initialize ...@>= mp->random_seed = opt->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 ( mp->j_random==0 ) mp_new_randoms(mp); else decr(mp->j_random); } @c static void mp_new_randoms (MP mp) { int k; /* index into |randoms| */ fraction x; /* accumulator */ for (k = 0; k <= 23; k++) { x = mp->randoms[k] - mp->randoms[k + 31]; if (x < 0) x = x + fraction_one; mp->randoms[k] = x; } for (k = 24; k <= 54; k++) { x = mp->randoms[k] - mp->randoms[k - 24]; if (x < 0) x = x + fraction_one; mp->randoms[k] = x; } mp->j_random = 54; } @ @<Declarations@>= static void mp_init_randoms (MP mp, int seed); @ To initialize the |randoms| table, we call the following routine. @c void mp_init_randoms (MP mp, int seed) { fraction j, jj, k; /* more or less random integers */ int i; /* index into |randoms| */ j = abs (seed); while (j >= fraction_one) j = halfp (j); k = 1; for (i = 0; i <= 54; i++) { jj = k; k = j - k; j = jj; if (k < 0) k = k + fraction_one; mp->randoms[(i * 21) % 55] = j; } mp_new_randoms (mp); mp_new_randoms (mp); mp_new_randoms (mp); /* ``warm up'' the array */ } @ 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. @c static scaled mp_unif_rand (MP mp, scaled x) { scaled y; /* trial value */ next_random; y = mp_take_fraction (mp, abs (x), mp->randoms[mp->j_random]); if (y == abs (x)) return 0; else if (x > 0) return y; else return (-y); } @ 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\/}). @c static scaled mp_norm_rand (MP mp) { integer x, u, l; /* what the book would call $2^{16}X$, $2^{28}U$, and $-2^{24}\ln U$ */ do { do { next_random; x = mp_take_fraction (mp, 112429, mp->randoms[mp->j_random] - fraction_half); /* $2^{16}\sqrt{8/e}\approx 112428.82793$ */ next_random; u = mp->randoms[mp->j_random]; } while (abs (x) >= u); x = mp_make_fraction (mp, x, u); l = 139548960 - mp_m_log (mp, u); /* $2^{24}\cdot12\ln2\approx139548959.6165$ */ } while (mp_ab_vs_cd (mp, 1024, l, x, x) < 0); return x; } @* Packed data. @d max_quarterword 0x3FFF /* largest allowable value in a |quarterword| */ @d max_halfword 0xFFFFFFF /* largest allowable value in a |halfword| */ @ The macros |qi| and |qo| are used for input to and output from quarterwords. These are legacy macros. @^system dependencies@> @d qo(A) (A) /* to read eight bits from a quarterword */ @d qi(A) (quarterword)(A) /* to store eight bits in a quarterword */ @ The reader should study the following definitions closely: @^system dependencies@> @<Types...@>= typedef struct mp_value_node_data *mp_value_node; typedef struct mp_node_data *mp_node; typedef struct mp_symbol_entry *mp_sym; typedef short quarterword; /* 1/4 of a word */ typedef int halfword; /* 1/2 of a word */ typedef struct { halfword val; integer scale; str_number str; mp_sym sym; mp_node node; mp_knot p; } mp_value_data; typedef struct { mp_variable_type type; mp_value_data data; } mp_value; typedef struct { quarterword b0, b1, b2, b3; } four_quarters; typedef union { integer sc; four_quarters qqqq; } font_data; @ @d xfree(A) do { mp_xfree(A); A=NULL; } while (0) @d xrealloc(P,A,B) mp_xrealloc(mp,P,(size_t)A,B) @d xmalloc(A,B) mp_xmalloc(mp,(size_t)A,B) @d xstrdup(A) mp_xstrdup(mp,A) @d XREALLOC(a,b,c) a = xrealloc(a,(b+1),sizeof(c)); @<Declare helpers@>= extern char *mp_strdup (const char *p); extern char *mp_strldup (const char *p, size_t l); extern void mp_xfree (void *x); extern void *mp_xrealloc (MP mp, void *p, size_t nmem, size_t size); extern void *mp_xmalloc (MP mp, size_t nmem, size_t size); extern char *mp_xstrdup (MP mp, const char *s); extern char *mp_xstrldup (MP mp, const char *s, size_t l); @ Some care has to be taken while copying strings @c char *mp_strldup (const char *p, size_t l) { char *r, *s; if (p == NULL) return NULL; r = malloc ((size_t) (l * sizeof (char) + 1)); if (r == NULL) return NULL; s = memcpy (r, p, (size_t) (l)); *(s + l) = '\0'; return s; } char *mp_strdup (const char *p) { if (p == NULL) return NULL; return mp_strldup (p, strlen (p)); } @ The |max_size_test| guards against overflow, on the assumption that |size_t| is at least 31bits wide. @d max_size_test 0x7FFFFFFF @c void mp_xfree (void *x) { if (x != NULL) free (x); } void *mp_xrealloc (MP mp, void *p, size_t nmem, size_t size) { void *w; if ((max_size_test / size) < nmem) { do_putsf (mp->err_out, "Memory size overflow!\n"); mp->history = mp_fatal_error_stop; mp_jump_out (mp); } w = realloc (p, (nmem * size)); if (w == NULL) { do_putsf (mp->err_out, "Out of memory!\n"); mp->history = mp_system_error_stop; mp_jump_out (mp); } return w; } void *mp_xmalloc (MP mp, size_t nmem, size_t size) { void *w; if ((max_size_test / size) < nmem) { do_putsf (mp->err_out, "Memory size overflow!\n"); mp->history = mp_fatal_error_stop; mp_jump_out (mp); } w = malloc (nmem * size); if (w == NULL) { do_putsf (mp->err_out, "Out of memory!\n"); mp->history = mp_system_error_stop; mp_jump_out (mp); } return w; } char *mp_xstrldup (MP mp, const char *s, size_t l) { char *w; if (s == NULL) return NULL; w = mp_strldup (s, l); if (w == NULL) { do_putsf (mp->err_out, "Out of memory!\n"); mp->history = mp_system_error_stop; mp_jump_out (mp); } return w; } char *mp_xstrdup (MP mp, const char *s) { if (s == NULL) return NULL; return mp_xstrldup (mp, s, strlen (s)); } @ @<Internal library declarations@>= #ifdef HAVE_SNPRINTF # define mp_snprintf (void)snprintf #else static void mp_snprintf (char *str, int size, const char *fmt, ...); #endif @ This internal version is rather stupid, but good enough for its purpose. @c #ifndef HAVE_SNPRINTF static char *mp_itoa (int i) { char res[32]; unsigned idx = 30; unsigned v = (unsigned) abs (i); memset (res, 0, 32 * sizeof (char)); while (v >= 10) { char d = (char) (v % 10); v = v / 10; res[idx--] = (char) (d + '0'); } res[idx--] = (char) (v + '0'); if (i < 0) { res[idx--] = '-'; } return mp_strdup ((res + idx + 1)); } static char *mp_utoa (unsigned v) { char res[32]; unsigned idx = 30; memset (res, 0, 32 * sizeof (char)); while (v >= 10) { char d = (char) (v % 10); v = v / 10; res[idx--] = (char) (d + '0'); } res[idx--] = (char) (v + '0'); return mp_strdup ((res + idx + 1)); } static void mp_snprintf (char *str, int size, const char *format, ...) { const char *fmt; char *res; int fw, pad; va_list ap; va_start (ap, format); res = str; for (fmt = format; *fmt != '\0'; fmt++) { if (*fmt == '%') { fw = 0; pad = 0; RESTART: fmt++; switch (*fmt) { case '0': pad = 1; goto RESTART; break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': assert (fw == 0); fw = *fmt - '0'; goto RESTART; break; case 's': { char *s = va_arg (ap, char *); while (*s) { *res = *s++; if (size-- > 0) res++; } } break; case 'c': { int s = va_arg (ap, int); *res = (char) s; if (size-- > 0) res++; } break; case 'i': case 'd': { char *sstart, *s = mp_itoa (va_arg (ap, int)); sstart = s; if (fw) { int ffw = fw - (int) strlen (s); while (ffw-- > 0) { *res = (char) (pad ? '0' : ' '); if (size-- > 0) res++; } } if (s != NULL) { while (*s) { *res = *s++; if (size-- > 0) res++; } mp_xfree (sstart); } } break; case 'u': { char *sstart, *s = mp_utoa (va_arg (ap, unsigned)); sstart = s; if (fw) { int ffw = fw - (int) strlen (s); while (ffw-- > 0) { *res = (char) (pad ? '0' : ' '); if (size-- > 0) res++; } } if (s != NULL) { while (*s) { *res = *s++; if (size-- > 0) res++; } mp_xfree (sstart); } } break; case '%': *res = '%'; if (size-- > 0) res++; break; default: *res = '%'; if (size-- > 0) res++; *res = *fmt; if (size-- > 0) res++; break; } } else { *res = *fmt; if (size-- > 0) res++; } } *res = '\0'; va_end (ap); } #endif @* 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. @d MP_VOID (mp_node)(1) /* |NULL+1|, a |NULL| pointer different from |NULL| */ @d mp_link(A) (A)->link /* the |link| field of a node */ @d set_mp_link(A,B) do { mp_node d = (B); /* |printf("set link of %p to %p on line %d\n", (A), d, __LINE__);| */ mp_link((A)) = d; } while (0) @d mp_type(A) (A)->type /* identifies what kind of value this is */ @d mp_name_type(A) (A)->name_type /* a clue to the name of this value */ @ @(mpmp.h@>= #define NODE_BODY \ mp_variable_type type; \ quarterword name_type; \ halfword info; \ struct mp_node_data *link typedef struct mp_node_data { NODE_BODY; } mp_node_data; typedef struct mp_symbolic_node_data { NODE_BODY; mp_sym sym; } mp_symbolic_node_data; typedef struct mp_symbolic_node_data *mp_symbolic_node; @ Users who wish to study the memory requirements of particular applications can can use the special features that keep track of current and maximum memory usage. \MP\ will report these statistics when |mp_tracing_stats| is positive. @d add_var_used(a) do { mp->var_used+=(a); if (mp->var_used>mp->var_used_max) mp->var_used_max=mp->var_used; } while (0) @<Glob...@>= size_t var_used; /* how much memory is in use */ size_t var_used_max; /* how much memory was in use max */ @ These redirect to function to aid in debugging. @d mp_sym_info(A) get_mp_sym_info(mp,(A)) @d set_mp_sym_info(A,B) do_set_mp_sym_info(mp,(A),(B)) @c static void do_set_mp_sym_info (MP mp, mp_node p, halfword v) { (void) mp; assert (p->type == mp_symbol_node); p->info = v; } static halfword get_mp_sym_info (MP mp, mp_node p) { (void) mp; assert (p->type == mp_symbol_node); return p->info; } @ Similarly, so do these redirect to functions. @d mp_sym_sym(A) get_mp_sym_sym(mp,(A)) @d set_mp_sym_sym(A,B) do_set_mp_sym_sym(mp,(A),(mp_sym)(B)) @c static void do_set_mp_sym_sym (MP mp, mp_node p, mp_sym v) { mp_symbolic_node pp = (mp_symbolic_node) p; (void) mp; assert (pp->type == mp_symbol_node); pp->sym = v; } static mp_sym get_mp_sym_sym (MP mp, mp_node p) { mp_symbolic_node pp = (mp_symbolic_node) p; (void) mp; assert (pp->type == mp_symbol_node); return pp->sym; } @ @<Declarations@>= static void do_set_mp_sym_info (MP mp, mp_node A, halfword B); static halfword get_mp_sym_info (MP mp, mp_node p); static void do_set_mp_sym_sym (MP mp, mp_node A, mp_sym B); static mp_sym get_mp_sym_sym (MP mp, mp_node p); @ Symbolic nodes also have |name_type|, which is a short enumeration @<Types...@>= enum { mp_normal_sym = 0, mp_internal_sym, /* for values of internals */ mp_macro_sym, /* for macro names */ mp_expr_sym, /* for macro parameters if type |expr| */ mp_suffix_sym, /* for macro parameters if type |suffix| */ mp_text_sym, /* for macro parameters if type |text| */ } mp_sym_name_types; @ The function |get_symbolic_node| returns a pointer to a new symbolic node whose |link| field is null. @^inner loop@> @d symbolic_node_size sizeof(mp_symbolic_node_data) @c static mp_node mp_get_symbolic_node (MP mp) { mp_symbolic_node p = xmalloc (1, symbolic_node_size); add_var_used (symbolic_node_size); memset (p, 0, symbolic_node_size); p->type = mp_symbol_node; p->name_type = mp_normal_sym; FUNCTION_TRACE2 ("%p = mp_get_symbolic_node()\n", p); return (mp_node) p; } @ Conversely, when some 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. A symbolic node is recycled by calling |free_symbolic_node|. @d mp_free_symbolic_node(mp, A) mp_free_node(mp, (A), symbolic_node_size) @c void mp_free_node (MP mp, mp_node p, size_t siz) { /* node liberation */ FUNCTION_TRACE3 ("mp_free_node(%p,%d)\n", p, siz); mp->var_used -= siz; xfree (p); /* do more later */ } @ @<Internal library declarations@>= void mp_free_node (MP mp, mp_node p, size_t siz); @ Same redirection trick as above @d mp_info(A) get_mp_info(mp,(A)) @d set_mp_info(A,B) do_set_mp_info(mp,(A),(B)) @c static void do_set_mp_info (MP mp, mp_node p, halfword v) { (void) mp; p->info = v; } halfword get_mp_info (MP mp, mp_node p) { (void) mp; return p->info; } @ @<Declarations@>= static halfword get_mp_info (MP mp, mp_node p); static void do_set_mp_info (MP mp, mp_node p, halfword v); @* Memory layout. Some nodes are created statically, since static allocation is more efficient than dynamic allocation when we can get away with it. @<Glob...@>= mp_node null_dash; mp_value_node dep_head; mp_node inf_val; mp_node zero_val; mp_node temp_val; mp_node end_attr; mp_node bad_vardef; mp_node temp_head; mp_node hold_head; mp_node spec_head; @ The following code gets the memory off to a good start. @<Initialize table entries@>= mp->spec_head = mp_get_symbolic_node (mp); mp->last_pending = mp->spec_head; mp->temp_head = mp_get_symbolic_node (mp); mp->hold_head = mp_get_symbolic_node (mp); @ @<Free table entries@>= mp_free_symbolic_node (mp, mp->spec_head); mp_free_symbolic_node (mp, mp->temp_head); mp_free_symbolic_node (mp, mp->hold_head); @ The procedure |flush_node_list(p)| frees an entire linked list of nodes that starts at a given position, until coming to a |NULL| pointer. @^inner loop@> @c static void mp_flush_node_list (MP mp, mp_node p) { mp_node q; /* the node being recycled */ FUNCTION_TRACE2 ("mp_flush_node_list(%p)\n", p); while (p != NULL) { q = p; p = p->link; if (q->type != mp_symbol_node) mp_free_node (mp, q, token_node_size); else mp_free_symbolic_node (mp, q); } } @* 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 mp_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}) or font map info (\&{fontmapfile}, \&{fontmapline}) */ @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 (mp->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 */ @d undefined_cs (max_command_code+2) /* protection code added to command code */ @<Types...@>= typedef int 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 |>mp_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 unknown_types mp_unknown_boolean: case mp_unknown_string: case mp_unknown_pen: case mp_unknown_picture: case mp_unknown_path @<Enumeration types@>= typedef enum { mp_vacuous = 1, /* no expression was present */ mp_boolean_type, /* \&{boolean} with a known value */ mp_unknown_boolean, mp_string_type, /* \&{string} with a known value */ mp_unknown_string, mp_pen_type, /* \&{pen} with a known value */ mp_unknown_pen, mp_path_type, /* \&{path} with a known value */ mp_unknown_path, mp_picture_type, /* \&{picture} with a known value */ mp_unknown_picture, mp_transform_type, /* \&{transform} variable or capsule */ mp_color_type, /* \&{color} variable or capsule */ mp_cmykcolor_type, /* \&{cmykcolor} variable or capsule */ mp_pair_type, /* \&{pair} variable or capsule */ mp_numeric_type, /* variable that has been declared \&{numeric} but not used */ mp_known, /* \&{numeric} with a known value */ mp_dependent, /* a linear combination with |fraction| coefficients */ mp_proto_dependent, /* a linear combination with |scaled| coefficients */ mp_independent, /* \&{numeric} with unknown value */ mp_token_list, /* variable name or suffix argument or text argument */ mp_structured, /* variable with subscripts and attributes */ mp_unsuffixed_macro, /* variable defined with \&{vardef} but no \.{\AT!\#} */ mp_suffixed_macro, /* variable defined with \&{vardef} and \.{\AT!\#} */ /* here are some generic node types */ mp_symbol_node, mp_token_node_type, mp_value_node_type, mp_attr_node_type, mp_subscr_node_type, mp_pair_node_type, mp_transform_node_type, mp_color_node_type, mp_cmykcolor_node_type, /* it is important that the next 7 items remain in this order, for export */ mp_fill_node_type, mp_stroked_node_type, mp_text_node_type, mp_start_clip_node_type, mp_start_bounds_node_type, mp_stop_clip_node_type, mp_stop_bounds_node_type, mp_dash_node_type, mp_dep_node_type, mp_if_node_type, mp_edge_header_node_type, } mp_variable_type; @ @<Declarations@>= static void mp_print_type (MP mp, quarterword t); @ @<Basic printing procedures@>= static const char *mp_type_string (quarterword t) { const char *s = NULL; switch (t) { case undefined: s = "undefined"; break; case mp_vacuous: s = "vacuous"; break; case mp_boolean_type: s = "boolean"; break; case mp_unknown_boolean: s = "unknown boolean"; break; case mp_string_type: s = "string"; break; case mp_unknown_string: s = "unknown string"; break; case mp_pen_type: s = "pen"; break; case mp_unknown_pen: s = "unknown pen"; break; case mp_path_type: s = "path"; break; case mp_unknown_path: s = "unknown path"; break; case mp_picture_type: s = "picture"; break; case mp_unknown_picture: s = "unknown picture"; break; case mp_transform_type: s = "transform"; break; case mp_color_type: s = "color"; break; case mp_cmykcolor_type: s = "cmykcolor"; break; case mp_pair_type: s = "pair"; break; case mp_known: s = "known numeric"; break; case mp_dependent: s = "dependent"; break; case mp_proto_dependent: s = "proto-dependent"; break; case mp_numeric_type: s = "numeric"; break; case mp_independent: s = "independent"; break; case mp_token_list: s = "token list"; break; case mp_structured: s = "mp_structured"; break; case mp_unsuffixed_macro: s = "unsuffixed macro"; break; case mp_suffixed_macro: s = "suffixed macro"; break; case mp_symbol_node: s = "symbol node"; break; case mp_token_node_type: s = "token node"; break; case mp_value_node_type: s = "value node"; break; case mp_attr_node_type: s = "attribute node"; break; case mp_subscr_node_type: s = "subscript node"; break; case mp_fill_node_type: s = "fill node"; break; case mp_stroked_node_type: s = "stroked node"; break; case mp_text_node_type: s = "text node"; break; case mp_start_clip_node_type: s = "start clip node"; break; case mp_start_bounds_node_type: s = "start bounds node"; break; case mp_stop_clip_node_type: s = "stop clip node"; break; case mp_stop_bounds_node_type: s = "stop bounds node"; break; case mp_dash_node_type: s = "dash node"; break; case mp_dep_node_type: s = "dependency node"; break; case mp_if_node_type: s = "if node"; break; case mp_edge_header_node_type: s = "edge header node"; break; default: assert (0); break; } return s; } void mp_print_type (MP mp, quarterword t) { if (t >= 0 && t <= mp_edge_header_node_type) mp_print (mp, mp_type_string (t)); else mp_print (mp, "unknown"); } @ Values inside \MP\ are stored in non-symbolic 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. @<Types...@>= enum mp_name_types { mp_root = 0, /* |name_type| at the top level of a variable */ mp_saved_root, /* same, when the variable has been saved */ mp_structured_root, /* |name_type| where a |mp_structured| branch occurs */ mp_subscr, /* |name_type| in a subscript node */ mp_attr, /* |name_type| in an attribute node */ mp_x_part_sector, /* |name_type| in the \&{xpart} of a node */ mp_y_part_sector, /* |name_type| in the \&{ypart} of a node */ mp_xx_part_sector, /* |name_type| in the \&{xxpart} of a node */ mp_xy_part_sector, /* |name_type| in the \&{xypart} of a node */ mp_yx_part_sector, /* |name_type| in the \&{yxpart} of a node */ mp_yy_part_sector, /* |name_type| in the \&{yypart} of a node */ mp_red_part_sector, /* |name_type| in the \&{redpart} of a node */ mp_green_part_sector, /* |name_type| in the \&{greenpart} of a node */ mp_blue_part_sector, /* |name_type| in the \&{bluepart} of a node */ mp_cyan_part_sector, /* |name_type| in the \&{redpart} of a node */ mp_magenta_part_sector, /* |name_type| in the \&{greenpart} of a node */ mp_yellow_part_sector, /* |name_type| in the \&{bluepart} of a node */ mp_black_part_sector, /* |name_type| in the \&{greenpart} of a node */ mp_grey_part_sector, /* |name_type| in the \&{bluepart} of a node */ mp_capsule, /* |name_type| in stashed-away subexpressions */ mp_token /* |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., |mp_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..mp_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 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 color_model_part 54 /* operation code for \.{colormodel} */ @d x_part 55 /* operation code for \.{xpart} */ @d y_part 56 /* operation code for \.{ypart} */ @d xx_part 57 /* operation code for \.{xxpart} */ @d xy_part 58 /* operation code for \.{xypart} */ @d yx_part 59 /* operation code for \.{yxpart} */ @d yy_part 60 /* operation code for \.{yypart} */ @d red_part 61 /* operation code for \.{redpart} */ @d green_part 62 /* operation code for \.{greenpart} */ @d blue_part 63 /* operation code for \.{bluepart} */ @d cyan_part 64 /* operation code for \.{cyanpart} */ @d magenta_part 65 /* operation code for \.{magentapart} */ @d yellow_part 66 /* operation code for \.{yellowpart} */ @d black_part 67 /* operation code for \.{blackpart} */ @d grey_part 68 /* operation code for \.{greypart} */ @d font_part 69 /* operation code for \.{fontpart} */ @d text_part 70 /* operation code for \.{textpart} */ @d path_part 71 /* operation code for \.{pathpart} */ @d pen_part 72 /* operation code for \.{penpart} */ @d dash_part 73 /* operation code for \.{dashpart} */ @d sqrt_op 74 /* operation code for \.{sqrt} */ @d mp_m_exp_op 75 /* operation code for \.{mexp} */ @d mp_m_log_op 76 /* operation code for \.{mlog} */ @d sin_d_op 77 /* operation code for \.{sind} */ @d cos_d_op 78 /* operation code for \.{cosd} */ @d floor_op 79 /* operation code for \.{floor} */ @d uniform_deviate 80 /* operation code for \.{uniformdeviate} */ @d char_exists_op 81 /* operation code for \.{charexists} */ @d font_size 82 /* operation code for \.{fontsize} */ @d ll_corner_op 83 /* operation code for \.{llcorner} */ @d lr_corner_op 84 /* operation code for \.{lrcorner} */ @d ul_corner_op 85 /* operation code for \.{ulcorner} */ @d ur_corner_op 86 /* operation code for \.{urcorner} */ @d arc_length 87 /* operation code for \.{arclength} */ @d angle_op 88 /* operation code for \.{angle} */ @d cycle_op 89 /* operation code for \.{cycle} */ @d filled_op 90 /* operation code for \.{filled} */ @d stroked_op 91 /* operation code for \.{stroked} */ @d textual_op 92 /* operation code for \.{textual} */ @d clipped_op 93 /* operation code for \.{clipped} */ @d bounded_op 94 /* operation code for \.{bounded} */ @d plus 95 /* operation code for \.+ */ @d minus 96 /* operation code for \.- */ @d times 97 /* operation code for \.* */ @d over 98 /* operation code for \./ */ @d pythag_add 99 /* operation code for \.{++} */ @d pythag_sub 100 /* operation code for \.{+-+} */ @d or_op 101 /* operation code for \.{or} */ @d and_op 102 /* operation code for \.{and} */ @d less_than 103 /* operation code for \.< */ @d less_or_equal 104 /* operation code for \.{<=} */ @d greater_than 105 /* operation code for \.> */ @d greater_or_equal 106 /* operation code for \.{>=} */ @d equal_to 107 /* operation code for \.= */ @d unequal_to 108 /* operation code for \.{<>} */ @d concatenate 109 /* operation code for \.\& */ @d rotated_by 110 /* operation code for \.{rotated} */ @d slanted_by 111 /* operation code for \.{slanted} */ @d scaled_by 112 /* operation code for \.{scaled} */ @d shifted_by 113 /* operation code for \.{shifted} */ @d transformed_by 114 /* operation code for \.{transformed} */ @d x_scaled 115 /* operation code for \.{xscaled} */ @d y_scaled 116 /* operation code for \.{yscaled} */ @d z_scaled 117 /* operation code for \.{zscaled} */ @d in_font 118 /* operation code for \.{infont} */ @d intersect 119 /* operation code for \.{intersectiontimes} */ @d double_dot 120 /* operation code for improper \.{..} */ @d substring_of 121 /* operation code for \.{substring} */ @d min_of substring_of @d subpath_of 122 /* operation code for \.{subpath} */ @d direction_time_of 123 /* operation code for \.{directiontime} */ @d point_of 124 /* operation code for \.{point} */ @d precontrol_of 125 /* operation code for \.{precontrol} */ @d postcontrol_of 126 /* operation code for \.{postcontrol} */ @d pen_offset_of 127 /* operation code for \.{penoffset} */ @d arc_time_of 128 /* operation code for \.{arctime} */ @d mp_version 129 /* operation code for \.{mpversion} */ @d envelope_of 130 /* operation code for \.{envelope} */ @d glyph_infont 131 /* operation code for \.{glyph} */ @c static void mp_print_op (MP mp, quarterword c) { if (c <= mp_numeric_type) { mp_print_type (mp, c); } else { switch (c) { case true_code: mp_print (mp, "true"); break; case false_code: mp_print (mp, "false"); break; case null_picture_code: mp_print (mp, "nullpicture"); break; case null_pen_code: mp_print (mp, "nullpen"); break; case read_string_op: mp_print (mp, "readstring"); break; case pen_circle: mp_print (mp, "pencircle"); break; case normal_deviate: mp_print (mp, "normaldeviate"); break; case read_from_op: mp_print (mp, "readfrom"); break; case close_from_op: mp_print (mp, "closefrom"); break; case odd_op: mp_print (mp, "odd"); break; case known_op: mp_print (mp, "known"); break; case unknown_op: mp_print (mp, "unknown"); break; case not_op: mp_print (mp, "not"); break; case decimal: mp_print (mp, "decimal"); break; case reverse: mp_print (mp, "reverse"); break; case make_path_op: mp_print (mp, "makepath"); break; case make_pen_op: mp_print (mp, "makepen"); break; case oct_op: mp_print (mp, "oct"); break; case hex_op: mp_print (mp, "hex"); break; case ASCII_op: mp_print (mp, "ASCII"); break; case char_op: mp_print (mp, "char"); break; case length_op: mp_print (mp, "length"); break; case turning_op: mp_print (mp, "turningnumber"); break; case x_part: mp_print (mp, "xpart"); break; case y_part: mp_print (mp, "ypart"); break; case xx_part: mp_print (mp, "xxpart"); break; case xy_part: mp_print (mp, "xypart"); break; case yx_part: mp_print (mp, "yxpart"); break; case yy_part: mp_print (mp, "yypart"); break; case red_part: mp_print (mp, "redpart"); break; case green_part: mp_print (mp, "greenpart"); break; case blue_part: mp_print (mp, "bluepart"); break; case cyan_part: mp_print (mp, "cyanpart"); break; case magenta_part: mp_print (mp, "magentapart"); break; case yellow_part: mp_print (mp, "yellowpart"); break; case black_part: mp_print (mp, "blackpart"); break; case grey_part: mp_print (mp, "greypart"); break; case color_model_part: mp_print (mp, "colormodel"); break; case font_part: mp_print (mp, "fontpart"); break; case text_part: mp_print (mp, "textpart"); break; case path_part: mp_print (mp, "pathpart"); break; case pen_part: mp_print (mp, "penpart"); break; case dash_part: mp_print (mp, "dashpart"); break; case sqrt_op: mp_print (mp, "sqrt"); break; case mp_m_exp_op: mp_print (mp, "mexp"); break; case mp_m_log_op: mp_print (mp, "mlog"); break; case sin_d_op: mp_print (mp, "sind"); break; case cos_d_op: mp_print (mp, "cosd"); break; case floor_op: mp_print (mp, "floor"); break; case uniform_deviate: mp_print (mp, "uniformdeviate"); break; case char_exists_op: mp_print (mp, "charexists"); break; case font_size: mp_print (mp, "fontsize"); break; case ll_corner_op: mp_print (mp, "llcorner"); break; case lr_corner_op: mp_print (mp, "lrcorner"); break; case ul_corner_op: mp_print (mp, "ulcorner"); break; case ur_corner_op: mp_print (mp, "urcorner"); break; case arc_length: mp_print (mp, "arclength"); break; case angle_op: mp_print (mp, "angle"); break; case cycle_op: mp_print (mp, "cycle"); break; case filled_op: mp_print (mp, "filled"); break; case stroked_op: mp_print (mp, "stroked"); break; case textual_op: mp_print (mp, "textual"); break; case clipped_op: mp_print (mp, "clipped"); break; case bounded_op: mp_print (mp, "bounded"); break; case plus: mp_print_char (mp, xord ('+')); break; case minus: mp_print_char (mp, xord ('-')); break; case times: mp_print_char (mp, xord ('*')); break; case over: mp_print_char (mp, xord ('/')); break; case pythag_add: mp_print (mp, "++"); break; case pythag_sub: mp_print (mp, "+-+"); break; case or_op: mp_print (mp, "or"); break; case and_op: mp_print (mp, "and"); break; case less_than: mp_print_char (mp, xord ('<')); break; case less_or_equal: mp_print (mp, "<="); break; case greater_than: mp_print_char (mp, xord ('>')); break; case greater_or_equal: mp_print (mp, ">="); break; case equal_to: mp_print_char (mp, xord ('=')); break; case unequal_to: mp_print (mp, "<>"); break; case concatenate: mp_print (mp, "&"); break; case rotated_by: mp_print (mp, "rotated"); break; case slanted_by: mp_print (mp, "slanted"); break; case scaled_by: mp_print (mp, "scaled"); break; case shifted_by: mp_print (mp, "shifted"); break; case transformed_by: mp_print (mp, "transformed"); break; case x_scaled: mp_print (mp, "xscaled"); break; case y_scaled: mp_print (mp, "yscaled"); break; case z_scaled: mp_print (mp, "zscaled"); break; case in_font: mp_print (mp, "infont"); break; case intersect: mp_print (mp, "intersectiontimes"); break; case substring_of: mp_print (mp, "substring"); break; case subpath_of: mp_print (mp, "subpath"); break; case direction_time_of: mp_print (mp, "directiontime"); break; case point_of: mp_print (mp, "point"); break; case precontrol_of: mp_print (mp, "precontrol"); break; case postcontrol_of: mp_print (mp, "postcontrol"); break; case pen_offset_of: mp_print (mp, "penoffset"); break; case arc_time_of: mp_print (mp, "arctime"); break; case mp_version: mp_print (mp, "mpversion"); break; case envelope_of: mp_print (mp, "envelope"); break; case glyph_infont: mp_print (mp, "glyph"); break; default: mp_print (mp, ".."); break; } } } @ \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. @<Types...@>= enum mp_given_internal { mp_output_template = 1, /* a string set up by \&{outputtemplate} */ mp_output_format, /* the output format set up by \&{outputformat} */ mp_job_name, /* the perceived jobname, as set up from the options stucture, the name of the input file, or by \&{jobname} */ mp_tracing_titles, /* show titles online when they appear */ mp_tracing_equations, /* show each variable when it becomes known */ mp_tracing_capsules, /* show capsules too */ mp_tracing_choices, /* show the control points chosen for paths */ mp_tracing_specs, /* show path subdivision prior to filling with polygonal a pen */ mp_tracing_commands, /* show commands and operations before they are performed */ mp_tracing_restores, /* show when a variable or internal is restored */ mp_tracing_macros, /* show macros before they are expanded */ mp_tracing_output, /* show digitized edges as they are output */ mp_tracing_stats, /* show memory usage at end of job */ mp_tracing_lost_chars, /* show characters that aren't \&{infont} */ mp_tracing_online, /* show long diagnostics on terminal and in the log file */ mp_year, /* the current year (e.g., 1984) */ mp_month, /* the current month (e.g., 3 $\equiv$ March) */ mp_day, /* the current day of the month */ mp_time, /* the number of minutes past midnight when this job started */ mp_hour, /* the number of hours past midnight when this job started */ mp_minute, /* the number of minutes in that hour when this job started */ mp_char_code, /* the number of the next character to be output */ mp_char_ext, /* the extension code of the next character to be output */ mp_char_wd, /* the width of the next character to be output */ mp_char_ht, /* the height of the next character to be output */ mp_char_dp, /* the depth of the next character to be output */ mp_char_ic, /* the italic correction of the next character to be output */ mp_design_size, /* the unit of measure used for |mp_char_wd..mp_char_ic|, in points */ mp_pausing, /* positive to display lines on the terminal before they are read */ mp_showstopping, /* positive to stop after each \&{show} command */ mp_fontmaking, /* positive if font metric output is to be produced */ mp_linejoin, /* as in \ps: 0 for mitered, 1 for round, 2 for beveled */ mp_linecap, /* as in \ps: 0 for butt, 1 for round, 2 for square */ mp_miterlimit, /* controls miter length as in \ps */ mp_warning_check, /* controls error message when variable value is large */ mp_boundary_char, /* the right boundary character for ligatures */ mp_prologues, /* positive to output conforming PostScript using built-in fonts */ mp_true_corners, /* positive to make \&{llcorner} etc. ignore \&{setbounds} */ mp_default_color_model, /* the default color model for unspecified items */ mp_restore_clip_color, mp_procset, /* wether or not create PostScript command shortcuts */ mp_gtroffmode /* whether the user specified |-troff| on the command line */ }; typedef struct { mp_value v; char *intname; } mp_internal; @ @(mpmp.h@>= #define internal_value(A) mp->internal[(A)].v.data.val #define internal_string(A) mp->internal[(A)].v.data.str #define internal_name(A) mp->internal[(A)].intname #define internal_type(A) mp->internal[(A)].v.type @ @d max_given_internal mp_gtroffmode @<Glob...@>= mp_internal *internal; /* the values of internal quantities */ int int_ptr; /* the maximum internal quantity defined so far */ int max_internal; /* current maximum number of internal quantities */ @ @<Option variables@>= int troff_mode; @ @<Allocate or initialize ...@>= mp->max_internal = 2 * max_given_internal; mp->internal = xmalloc ((mp->max_internal + 1), sizeof (mp_internal)); memset (mp->internal, 0, (size_t) (mp->max_internal + 1) * sizeof (mp_internal)); { int i; for (i = 1; i <= max_given_internal; i++) internal_type (i) = mp_known; } internal_type (mp_output_format) = mp_string_type; internal_type (mp_output_template) = mp_string_type; internal_type (mp_job_name) = mp_string_type; mp->troff_mode = (opt->troff_mode > 0 ? true : false); @ @<Exported function ...@>= int mp_troff_mode (MP mp); @ @c int mp_troff_mode (MP mp) { return mp->troff_mode; } @ @<Set initial ...@>= mp->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@>= mp_primitive (mp, "tracingtitles", internal_quantity, mp_tracing_titles); @:tracingtitles_}{\&{tracingtitles} primitive@>; mp_primitive (mp, "tracingequations", internal_quantity, mp_tracing_equations); @:mp_tracing_equations_}{\&{tracingequations} primitive@>; mp_primitive (mp, "tracingcapsules", internal_quantity, mp_tracing_capsules); @:mp_tracing_capsules_}{\&{tracingcapsules} primitive@>; mp_primitive (mp, "tracingchoices", internal_quantity, mp_tracing_choices); @:mp_tracing_choices_}{\&{tracingchoices} primitive@>; mp_primitive (mp, "tracingspecs", internal_quantity, mp_tracing_specs); @:mp_tracing_specs_}{\&{tracingspecs} primitive@>; mp_primitive (mp, "tracingcommands", internal_quantity, mp_tracing_commands); @:mp_tracing_commands_}{\&{tracingcommands} primitive@>; mp_primitive (mp, "tracingrestores", internal_quantity, mp_tracing_restores); @:mp_tracing_restores_}{\&{tracingrestores} primitive@>; mp_primitive (mp, "tracingmacros", internal_quantity, mp_tracing_macros); @:mp_tracing_macros_}{\&{tracingmacros} primitive@>; mp_primitive (mp, "tracingoutput", internal_quantity, mp_tracing_output); @:mp_tracing_output_}{\&{tracingoutput} primitive@>; mp_primitive (mp, "tracingstats", internal_quantity, mp_tracing_stats); @:mp_tracing_stats_}{\&{tracingstats} primitive@>; mp_primitive (mp, "tracinglostchars", internal_quantity, mp_tracing_lost_chars); @:mp_tracing_lost_chars_}{\&{tracinglostchars} primitive@>; mp_primitive (mp, "tracingonline", internal_quantity, mp_tracing_online); @:mp_tracing_online_}{\&{tracingonline} primitive@>; mp_primitive (mp, "year", internal_quantity, mp_year); @:mp_year_}{\&{year} primitive@>; mp_primitive (mp, "month", internal_quantity, mp_month); @:mp_month_}{\&{month} primitive@>; mp_primitive (mp, "day", internal_quantity, mp_day); @:mp_day_}{\&{day} primitive@>; mp_primitive (mp, "time", internal_quantity, mp_time); @:time_}{\&{time} primitive@>; mp_primitive (mp, "hour", internal_quantity, mp_hour); @:hour_}{\&{hour} primitive@>; mp_primitive (mp, "minute", internal_quantity, mp_minute); @:minute_}{\&{minute} primitive@>; mp_primitive (mp, "charcode", internal_quantity, mp_char_code); @:mp_char_code_}{\&{charcode} primitive@>; mp_primitive (mp, "charext", internal_quantity, mp_char_ext); @:mp_char_ext_}{\&{charext} primitive@>; mp_primitive (mp, "charwd", internal_quantity, mp_char_wd); @:mp_char_wd_}{\&{charwd} primitive@>; mp_primitive (mp, "charht", internal_quantity, mp_char_ht); @:mp_char_ht_}{\&{charht} primitive@>; mp_primitive (mp, "chardp", internal_quantity, mp_char_dp); @:mp_char_dp_}{\&{chardp} primitive@>; mp_primitive (mp, "charic", internal_quantity, mp_char_ic); @:mp_char_ic_}{\&{charic} primitive@>; mp_primitive (mp, "designsize", internal_quantity, mp_design_size); @:mp_design_size_}{\&{designsize} primitive@>; mp_primitive (mp, "pausing", internal_quantity, mp_pausing); @:mp_pausing_}{\&{pausing} primitive@>; mp_primitive (mp, "showstopping", internal_quantity, mp_showstopping); @:mp_showstopping_}{\&{showstopping} primitive@>; mp_primitive (mp, "fontmaking", internal_quantity, mp_fontmaking); @:mp_fontmaking_}{\&{fontmaking} primitive@>; mp_primitive (mp, "linejoin", internal_quantity, mp_linejoin); @:mp_linejoin_}{\&{linejoin} primitive@>; mp_primitive (mp, "linecap", internal_quantity, mp_linecap); @:mp_linecap_}{\&{linecap} primitive@>; mp_primitive (mp, "miterlimit", internal_quantity, mp_miterlimit); @:mp_miterlimit_}{\&{miterlimit} primitive@>; mp_primitive (mp, "warningcheck", internal_quantity, mp_warning_check); @:mp_warning_check_}{\&{warningcheck} primitive@>; mp_primitive (mp, "boundarychar", internal_quantity, mp_boundary_char); @:mp_boundary_char_}{\&{boundarychar} primitive@>; mp_primitive (mp, "prologues", internal_quantity, mp_prologues); @:mp_prologues_}{\&{prologues} primitive@>; mp_primitive (mp, "truecorners", internal_quantity, mp_true_corners); @:mp_true_corners_}{\&{truecorners} primitive@>; mp_primitive (mp, "mpprocset", internal_quantity, mp_procset); @:mp_procset_}{\&{mpprocset} primitive@>; mp_primitive (mp, "troffmode", internal_quantity, mp_gtroffmode); @:troffmode_}{\&{troffmode} primitive@>; mp_primitive (mp, "defaultcolormodel", internal_quantity, mp_default_color_model); @:mp_default_color_model_}{\&{defaultcolormodel} primitive@>; mp_primitive (mp, "restoreclipcolor", internal_quantity, mp_restore_clip_color); @:mp_restore_clip_color_}{\&{restoreclipcolor} primitive@>; mp_primitive (mp, "outputtemplate", internal_quantity, mp_output_template); @:mp_output_template_}{\&{outputtemplate} primitive@>; mp_primitive (mp, "outputformat", internal_quantity, mp_output_format); @:mp_output_format_}{\&{outputformat} primitive@>; mp_primitive (mp, "jobname", internal_quantity, mp_job_name); @:mp_job_name_}{\&{jobname} primitive@> @ Colors can be specified in four color models. In the special case of |no_model|, MetaPost does not output any color operator to the postscript output. Note: these values are passed directly on to |with_option|. This only works because the other possible values passed to |with_option| are 8 and 10 respectively (from |with_pen| and |with_picture|). There is a first state, that is only used for |gs_colormodel|. It flags the fact that there has not been any kind of color specification by the user so far in the game. @(mplib.h@>= enum mp_color_model { mp_no_model = 1, mp_grey_model = 3, mp_rgb_model = 5, mp_cmyk_model = 7, mp_uninitialized_model = 9 }; @ @<Initialize table entries@>= internal_value (mp_default_color_model) = (mp_rgb_model * unity); internal_value (mp_restore_clip_color) = unity; internal_string (mp_output_template) = mp_intern (mp, "%j.%c"); internal_string (mp_output_format) = mp_intern (mp, "eps"); #if 0 internal_value (mp_tracing_titles) = 3 * unity; internal_value (mp_tracing_equations) = 3 * unity; internal_value (mp_tracing_capsules) = 3 * unity; internal_value (mp_tracing_choices) = 3 * unity; internal_value (mp_tracing_specs) = 3 * unity; internal_value (mp_tracing_commands) = 3 * unity; internal_value (mp_tracing_restores) = 3 * unity; internal_value (mp_tracing_macros) = 3 * unity; internal_value (mp_tracing_output) = 3 * unity; internal_value (mp_tracing_stats) = 3 * unity; internal_value (mp_tracing_lost_chars) = 3 * unity; internal_value (mp_tracing_online) = 3 * unity; #endif @ Well, we do have to list the names one more time, for use in symbolic printouts. @<Initialize table...@>= internal_name (mp_tracing_titles) = xstrdup ("tracingtitles"); internal_name (mp_tracing_equations) = xstrdup ("tracingequations"); internal_name (mp_tracing_capsules) = xstrdup ("tracingcapsules"); internal_name (mp_tracing_choices) = xstrdup ("tracingchoices"); internal_name (mp_tracing_specs) = xstrdup ("tracingspecs"); internal_name (mp_tracing_commands) = xstrdup ("tracingcommands"); internal_name (mp_tracing_restores) = xstrdup ("tracingrestores"); internal_name (mp_tracing_macros) = xstrdup ("tracingmacros"); internal_name (mp_tracing_output) = xstrdup ("tracingoutput"); internal_name (mp_tracing_stats) = xstrdup ("tracingstats"); internal_name (mp_tracing_lost_chars) = xstrdup ("tracinglostchars"); internal_name (mp_tracing_online) = xstrdup ("tracingonline"); internal_name (mp_year) = xstrdup ("year"); internal_name (mp_month) = xstrdup ("month"); internal_name (mp_day) = xstrdup ("day"); internal_name (mp_time) = xstrdup ("time"); internal_name (mp_hour) = xstrdup ("hour"); internal_name (mp_minute) = xstrdup ("minute"); internal_name (mp_char_code) = xstrdup ("charcode"); internal_name (mp_char_ext) = xstrdup ("charext"); internal_name (mp_char_wd) = xstrdup ("charwd"); internal_name (mp_char_ht) = xstrdup ("charht"); internal_name (mp_char_dp) = xstrdup ("chardp"); internal_name (mp_char_ic) = xstrdup ("charic"); internal_name (mp_design_size) = xstrdup ("designsize"); internal_name (mp_pausing) = xstrdup ("pausing"); internal_name (mp_showstopping) = xstrdup ("showstopping"); internal_name (mp_fontmaking) = xstrdup ("fontmaking"); internal_name (mp_linejoin) = xstrdup ("linejoin"); internal_name (mp_linecap) = xstrdup ("linecap"); internal_name (mp_miterlimit) = xstrdup ("miterlimit"); internal_name (mp_warning_check) = xstrdup ("warningcheck"); internal_name (mp_boundary_char) = xstrdup ("boundarychar"); internal_name (mp_prologues) = xstrdup ("prologues"); internal_name (mp_true_corners) = xstrdup ("truecorners"); internal_name (mp_default_color_model) = xstrdup ("defaultcolormodel"); internal_name (mp_procset) = xstrdup ("mpprocset"); internal_name (mp_gtroffmode) = xstrdup ("troffmode"); internal_name (mp_restore_clip_color) = xstrdup ("restoreclipcolor"); internal_name (mp_output_template) = xstrdup ("outputtemplate"); internal_name (mp_output_format) = xstrdup ("outputformat"); internal_name (mp_job_name) = xstrdup ("jobname"); @ 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@> Note that the values are |scaled| integers. Hence \MP\ can no longer be used after the year 32767. @c static void mp_fix_date_and_time (MP mp) { time_t aclock = time ((time_t *) 0); struct tm *tmptr = localtime (&aclock); internal_value (mp_time) = (tmptr->tm_hour * 60 + tmptr->tm_min) * unity; /* minutes since midnight */ internal_value (mp_hour) = (tmptr->tm_hour) * unity; /* hours since midnight */ internal_value (mp_minute) = (tmptr->tm_min) * unity; /* minutes since the hour */ internal_value (mp_day) = (tmptr->tm_mday) * unity; /* fourth day of the month */ internal_value (mp_month) = (tmptr->tm_mon + 1) * unity; /* seventh month of the year */ internal_value (mp_year) = (tmptr->tm_year + 1900) * unity; /* Anno Domini */ } @ @<Declarations@>= static void mp_fix_date_and_time (MP mp); @ \MP\ is occasionally supposed to print diagnostic information that goes only into the transcript file, unless |mp_tracing_online| is positive. Now that we have defined |mp_tracing_online| we can define two routines that adjust the destination of print commands: @<Declarations@>= static void mp_begin_diagnostic (MP mp); static void mp_end_diagnostic (MP mp, boolean blank_line); static void mp_print_diagnostic (MP mp, const char *s, const char *t, boolean nuline); @ @<Basic printing...@>= void mp_begin_diagnostic (MP mp) { /* prepare to do some tracing */ mp->old_setting = mp->selector; if ((internal_value (mp_tracing_online) <= 0) && (mp->selector == term_and_log)) { decr (mp->selector); if (mp->history == mp_spotless) mp->history = mp_warning_issued; } } @# void mp_end_diagnostic (MP mp, boolean blank_line) { /* restore proper conditions after tracing */ mp_print_nl (mp, ""); if (blank_line) mp_print_ln (mp); mp->selector = mp->old_setting; } @ @<Glob...@>= unsigned int old_setting; @ 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...@>= void mp_print_diagnostic (MP mp, const char *s, const char *t, boolean nuline) { mp_begin_diagnostic (mp); if (nuline) mp_print_nl (mp, s); else mp_print (mp, s); mp_print (mp, " at line "); mp_print_int (mp, mp_true_line (mp)); mp_print (mp, t); mp_print_char (mp, xord (':')); } @ 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: case 6: case 7: case 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...@>= int char_class[256]; /* 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 initial ...@>= for (k = '0'; k <= '9'; k++) mp->char_class[k] = digit_class; mp->char_class['.'] = period_class; mp->char_class[' '] = space_class; mp->char_class['%'] = percent_class; mp->char_class['"'] = string_class; mp->char_class[','] = 5; mp->char_class[';'] = 6; mp->char_class['('] = 7; mp->char_class[')'] = right_paren_class; for (k = 'A'; k <= 'Z'; k++) mp->char_class[k] = letter_class; for (k = 'a'; k <= 'z'; k++) mp->char_class[k] = letter_class; mp->char_class['_'] = letter_class; mp->char_class['<'] = 10; mp->char_class['='] = 10; mp->char_class['>'] = 10; mp->char_class[':'] = 10; mp->char_class['|'] = 10; mp->char_class['`'] = 11; mp->char_class['\''] = 11; mp->char_class['+'] = 12; mp->char_class['-'] = 12; mp->char_class['/'] = 13; mp->char_class['*'] = 13; mp->char_class['\\'] = 13; mp->char_class['!'] = 14; mp->char_class['?'] = 14; mp->char_class['#'] = 15; mp->char_class['&'] = 15; mp->char_class['@@'] = 15; mp->char_class['$'] = 15; mp->char_class['^'] = 16; mp->char_class['~'] = 16; mp->char_class['['] = left_bracket_class; mp->char_class[']'] = right_bracket_class; mp->char_class['{'] = 19; mp->char_class['}'] = 19; for (k = 0; k < ' '; k++) mp->char_class[k] = invalid_class; mp->char_class['\t'] = space_class; mp->char_class['\f'] = space_class; for (k = 127; k <= 255; k++) mp->char_class[k] = invalid_class; @* The hash table. Symbolic tokens are stored in and retrieved from an AVL tree. This is not as fast as an actual hash table, but it is easily extensible. A symbolic token contains a pointer to the |str_number| that contains the string representation of the symbol, a |halfword| that holds the current command value of the token, and an |mp_value| for the associated equivalent. @d text(A) (A)->text /* string number for symbolic token name */ @d eq_type(A) (A)->type /* the current ``meaning'' of a symbolic token */ @d equiv(A) (A)->v.data.val /* parametric part of a token's meaning */ @d equiv_node(A) (A)->v.data.node /* parametric part of a token's meaning */ @d equiv_sym(A) (A)->v.data.sym /* parametric part of a token's meaning */ @ @<Types...@>= typedef struct mp_symbol_entry { halfword type; mp_value v; str_number text; } mp_symbol_entry; @ @<Glob...@>= integer st_count; /* total number of known identifiers */ avl_tree symbols; /* avl tree of symbolic tokens */ avl_tree frozen_symbols; /* avl tree of frozen symbolic tokens */ mp_sym frozen_bad_vardef; mp_sym frozen_colon; mp_sym frozen_end_def; mp_sym frozen_end_for; mp_sym frozen_end_group; mp_sym frozen_etex; mp_sym frozen_fi; mp_sym frozen_inaccessible; mp_sym frozen_left_bracket; mp_sym frozen_mpx_break; mp_sym frozen_repeat_loop; mp_sym frozen_right_delimiter; mp_sym frozen_semicolon; mp_sym frozen_slash; mp_sym frozen_undefined; mp_sym frozen_dump; @ Here are the functions needed for the avl construction. @<Declarations@>= static int comp_symbols_entry (void *p, const void *pa, const void *pb); static void *copy_symbols_entry (const void *p); static void *delete_symbols_entry (void *p); @ The avl comparison function is a straightword version of |strcmp|, except that checks for the string lengths first. @c static int comp_symbols_entry (void *p, const void *pa, const void *pb) { const mp_symbol_entry *a = (const mp_symbol_entry *) pa; const mp_symbol_entry *b = (const mp_symbol_entry *) pb; (void) p; if (a->text->len != b->text->len) { return (a->text->len > b->text->len ? 1 : -1); } return strncmp ((const char *) a->text->str, (const char *) b->text->str, a->text->len); } @ Copying a symbol happens when an item is inserted into an AVL tree. The |text| needs to be deep copied, every thing else can be reassigned. @c static void *copy_symbols_entry (const void *p) { mp_sym ff; const mp_symbol_entry *fp; fp = (const mp_symbol_entry *) p; ff = malloc (sizeof (mp_symbol_entry)); if (ff == NULL) return NULL; ff->text = copy_strings_entry (fp->text); if (ff->text == NULL) return NULL; ff->v = fp->v; ff->type = fp->type; return ff; } @ In the current implementation, symbols are not freed until the end of the run. @c static void *delete_symbols_entry (void *p) { mp_sym ff = (mp_sym) p; delete_strings_entry (ff->text); mp_xfree (ff); return NULL; } @ @<Allocate or initialize ...@>= mp->symbols = avl_create (comp_symbols_entry, copy_symbols_entry, delete_symbols_entry, malloc, free, NULL); mp->frozen_symbols = avl_create (comp_symbols_entry, copy_symbols_entry, delete_symbols_entry, malloc, free, NULL); @ @<Dealloc variables@>= if (mp->symbols != NULL) avl_destroy (mp->symbols); if (mp->frozen_symbols != NULL) avl_destroy (mp->frozen_symbols); @ Actually creating symbols is done by |id_lookup|, but in order to do so it needs a way to create a new, empty symbol structure. @<Declarations@>= static mp_sym new_symbols_entry (MP mp, unsigned char *nam, size_t len); @ @c static mp_sym new_symbols_entry (MP mp, unsigned char *nam, size_t len) { mp_sym ff; ff = mp_xmalloc (mp, 1, sizeof (mp_symbol_entry)); memset (ff, 0, sizeof (mp_symbol_entry)); ff->text = new_strings_entry (mp); ff->text->str = nam; ff->text->len = len; ff->type = tag_token; ff->v.type = mp_known; FUNCTION_TRACE4 ("%p = new_symbols_entry(\"%s\",%d)\n", ff, nam, len); return ff; } @ Certain symbols are ``frozen'' and not redefinable, since they are used in error recovery. @<Initialize table entries@>= mp->st_count = 0; mp->frozen_bad_vardef = mp_frozen_primitive (mp, "a bad variable", tag_token, 0); mp->frozen_right_delimiter = mp_frozen_primitive (mp, ")", right_delimiter, 0); mp->frozen_inaccessible = mp_frozen_primitive (mp, " INACCESSIBLE", tag_token, 0); mp->frozen_undefined = mp_frozen_primitive (mp, " UNDEFINED", tag_token, 0); @ Here is the subroutine that searches the avl tree 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 if |insert_new| is |true|, and the corresponding symbol will be returned. There are two variations on the lookup function: one for the normal symbol table, and one for the table of error recovery symbols. @c static mp_sym mp_do_id_lookup (MP mp, avl_tree symbols, const char *j, size_t l, boolean insert_new) { /* search an avl tree */ mp_sym s, str; unsigned char *nam = (unsigned char *) mp_xstrldup (mp, j, l); s = new_symbols_entry (mp, nam, l); str = (mp_sym) avl_find (s, symbols); if (str == NULL && insert_new) { mp->st_count++; assert (avl_ins (s, symbols, avl_false) > 0); str = (mp_sym) avl_find (s, symbols); } delete_symbols_entry (s); return str; } static mp_sym mp_id_lookup (MP mp, char *j, size_t l, boolean insert_new) { /* search the normal symbol table */ return mp_do_id_lookup (mp, mp->symbols, j, l, insert_new); } static mp_sym mp_frozen_id_lookup (MP mp, const char *j, size_t l, boolean insert_new) { /* search the error recovery symbol table */ return mp_do_id_lookup (mp, mp->frozen_symbols, j, l, insert_new); } @ 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. @c static void mp_primitive (MP mp, const char *ss, halfword c, halfword o) { char *s = mp_xstrdup (mp, ss); mp->cur_sym = mp_id_lookup (mp, s, strlen (s), true); mp_xfree (s); eq_type (mp->cur_sym) = c; equiv (mp->cur_sym) = o; } @ Some other symbolic tokens only exist for error recovery. @c static mp_sym mp_frozen_primitive (MP mp, const char *ss, halfword c, halfword o) { mp_sym str = mp_frozen_id_lookup (mp, ss, strlen (ss), true); str->type = c; str->v.data.val = o; return str; } @ This routine returns |true| if the argument is an un-redefinable symbol because it is one of the error recovery tokens (as explained elsewhere, |frozen_inaccessible| actuall is redefinable). @c static boolean mp_is_frozen (MP mp, mp_sym sym) { mp_sym temp = mp_frozen_id_lookup (mp, (char *) sym->text->str, sym->text->len, false); if (temp==mp->frozen_inaccessible) return false; return (temp == sym); } @ 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@>= mp_primitive (mp, "..", path_join, 0); @:.._}{\.{..} primitive@>; mp_primitive (mp, "[", left_bracket, 0); mp->frozen_left_bracket = mp_frozen_primitive (mp, "[", left_bracket, 0); @:[ }{\.{[} primitive@>; mp_primitive (mp, "]", right_bracket, 0); @:] }{\.{]} primitive@>; mp_primitive (mp, "}", right_brace, 0); @:]]}{\.{\char`\}} primitive@>; mp_primitive (mp, "{", left_brace, 0); @:][}{\.{\char`\{} primitive@>; mp_primitive (mp, ":", colon, 0); mp->frozen_colon = mp_frozen_primitive (mp, ":", colon, 0); @:: }{\.{:} primitive@>; mp_primitive (mp, "::", double_colon, 0); @::: }{\.{::} primitive@>; mp_primitive (mp, "||:", bchar_label, 0); @:::: }{\.{\char'174\char'174:} primitive@>; mp_primitive (mp, ":=", assignment, 0); @::=_}{\.{:=} primitive@>; mp_primitive (mp, ",", comma, 0); @:, }{\., primitive@>; mp_primitive (mp, ";", semicolon, 0); mp->frozen_semicolon = mp_frozen_primitive (mp, ";", semicolon, 0); @:; }{\.; primitive@>; mp_primitive (mp, "\\", relax, 0); @:]]\\}{\.{\char`\\} primitive@>; mp_primitive (mp, "addto", add_to_command, 0); @:add_to_}{\&{addto} primitive@>; mp_primitive (mp, "atleast", at_least, 0); @:at_least_}{\&{atleast} primitive@>; mp_primitive (mp, "begingroup", begin_group, 0); mp->bg_loc = mp->cur_sym; @:begin_group_}{\&{begingroup} primitive@>; mp_primitive (mp, "controls", controls, 0); @:controls_}{\&{controls} primitive@>; mp_primitive (mp, "curl", curl_command, 0); @:curl_}{\&{curl} primitive@>; mp_primitive (mp, "delimiters", delimiters, 0); @:delimiters_}{\&{delimiters} primitive@>; mp_primitive (mp, "endgroup", end_group, 0); mp->eg_loc = mp->cur_sym; mp->frozen_end_group = mp_frozen_primitive (mp, "endgroup", end_group, 0); @:endgroup_}{\&{endgroup} primitive@>; mp_primitive (mp, "everyjob", every_job_command, 0); @:every_job_}{\&{everyjob} primitive@>; mp_primitive (mp, "exitif", exit_test, 0); @:exit_if_}{\&{exitif} primitive@>; mp_primitive (mp, "expandafter", expand_after, 0); @:expand_after_}{\&{expandafter} primitive@>; mp_primitive (mp, "interim", interim_command, 0); @:interim_}{\&{interim} primitive@>; mp_primitive (mp, "let", let_command, 0); @:let_}{\&{let} primitive@>; mp_primitive (mp, "newinternal", new_internal, 0); @:new_internal_}{\&{newinternal} primitive@>; mp_primitive (mp, "of", of_token, 0); @:of_}{\&{of} primitive@>; mp_primitive (mp, "randomseed", mp_random_seed, 0); @:mp_random_seed_}{\&{randomseed} primitive@>; mp_primitive (mp, "save", save_command, 0); @:save_}{\&{save} primitive@>; mp_primitive (mp, "scantokens", scan_tokens, 0); @:scan_tokens_}{\&{scantokens} primitive@>; mp_primitive (mp, "shipout", ship_out_command, 0); @:ship_out_}{\&{shipout} primitive@>; mp_primitive (mp, "skipto", skip_to, 0); @:skip_to_}{\&{skipto} primitive@>; mp_primitive (mp, "special", special_command, 0); @:special}{\&{special} primitive@>; mp_primitive (mp, "fontmapfile", special_command, 1); @:fontmapfile}{\&{fontmapfile} primitive@>; mp_primitive (mp, "fontmapline", special_command, 2); @:fontmapline}{\&{fontmapline} primitive@>; mp_primitive (mp, "step", step_token, 0); @:step_}{\&{step} primitive@>; mp_primitive (mp, "str", str_op, 0); @:str_}{\&{str} primitive@>; mp_primitive (mp, "tension", tension, 0); @:tension_}{\&{tension} primitive@>; mp_primitive (mp, "to", to_token, 0); @:to_}{\&{to} primitive@>; mp_primitive (mp, "until", until_token, 0); @:until_}{\&{until} primitive@>; mp_primitive (mp, "within", within_token, 0); @:within_}{\&{within} primitive@>; mp_primitive (mp, "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@>= case add_to_command: mp_print (mp, "addto"); break; case assignment: mp_print (mp, ":="); break; case at_least: mp_print (mp, "atleast"); break; case bchar_label: mp_print (mp, "||:"); break; case begin_group: mp_print (mp, "begingroup"); break; case colon: mp_print (mp, ":"); break; case comma: mp_print (mp, ","); break; case controls: mp_print (mp, "controls"); break; case curl_command: mp_print (mp, "curl"); break; case delimiters: mp_print (mp, "delimiters"); break; case double_colon: mp_print (mp, "::"); break; case end_group: mp_print (mp, "endgroup"); break; case every_job_command: mp_print (mp, "everyjob"); break; case exit_test: mp_print (mp, "exitif"); break; case expand_after: mp_print (mp, "expandafter"); break; case interim_command: mp_print (mp, "interim"); break; case left_brace: mp_print (mp, "{"); break; case left_bracket: mp_print (mp, "["); break; case let_command: mp_print (mp, "let"); break; case new_internal: mp_print (mp, "newinternal"); break; case of_token: mp_print (mp, "of"); break; case path_join: mp_print (mp, ".."); break; case mp_random_seed: mp_print (mp, "randomseed"); break; case relax: mp_print_char (mp, xord ('\\')); break; case right_brace: mp_print_char (mp, xord ('}')); break; case right_bracket: mp_print_char (mp, xord (']')); break; case save_command: mp_print (mp, "save"); break; case scan_tokens: mp_print (mp, "scantokens"); break; case semicolon: mp_print_char (mp, xord (';')); break; case ship_out_command: mp_print (mp, "shipout"); break; case skip_to: mp_print (mp, "skipto"); break; case special_command: if (m == 2) mp_print (mp, "fontmapline"); else if (m == 1) mp_print (mp, "fontmapfile"); else mp_print (mp, "special"); break; case step_token: mp_print (mp, "step"); break; case str_op: mp_print (mp, "str"); break; case tension: mp_print (mp, "tension"); break; case to_token: mp_print (mp, "to"); break; case until_token: mp_print (mp, "until"); break; case within_token: mp_print (mp, "within"); break; case write_command: mp_print (mp, "write"); break; @ 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'. @* Token lists. A \MP\ token is either symbolic or numeric or a string, or it denotes a macro parameter or capsule or an internal; so there are six corresponding ways to encode it internally: @^token@> (1)~A symbolic token for symbol |p| is represented by the pointer |p|, in the |sym_sym| field of a symbolic node in~|mem|. The |type| field is |symbol_node|; and it has a |name_type| to differentiate various subtypes of symbolic tokens, which is usually |normal_sym|, but |macro_sym| for macro names. (2)~A numeric token whose |scaled| value is~|v| is represented in a non-symbolic node of~|mem|; the |type| field is |known|, the |name_type| field is |token|, and the |value| field holds~|v|. (3)~A string token is also represented in a non-symbolic node; the |type| field is |mp_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, with |type| different from |symbol_node| (in ways to be explained later). (5)~Macro parameters appear in |sym_info| fields of symbolic nodes. The |type| field is |symbol_node|; the $k$th parameter is represented by |k| in |sym_info|; and |expr_sym| in |name_type|, if it is of type \&{expr}, or |suffix_sym| if it is of type \&{suffix}, or by |text_sym| if it is of type \&{text}. (6)~The $k$th internal is also represented by |k| in |sym_info|; the |type| field is |symbol_node| as for the other symbolic tokens; and |internal_sym| is its |name_type|; Actual values of the parameters and internals are kept in a separate stack, as we will see later. 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 token_node_size sizeof(mp_token_node_data) /* the number of words in a large token node */ @d value_sym(A) ((mp_token_node)(A))->data.sym /* the sym stored in a large token node */ @d value(A) ((mp_token_node)(A))->data.val /* the value stored in a large token node */ @d set_value(A,B) do { /* store the value in a large token node */ knot_value(A)=NULL; str_value(A)=NULL; value_node(A)=NULL; value(A)=(B); } while (0) @d value_node(A) ((mp_token_node)(A))->data.node /* the value stored in a large token node */ @d set_value_node(A,B) do { /* store the value in a large token node */ knot_value(A)=NULL; str_value(A)=NULL; value_node(A)=(B); value(A)=0; } while (0) @d str_value(A) ((mp_token_node)(A))->data.str /* the value stored in a large token node */ @d set_str_value(A,B) do { /* store the value in a large token node */ knot_value(A)=NULL; str_value(A)=(B); value_node(A)=NULL; value(A)=0; } while (0) @d knot_value(A) ((mp_token_node)(A))->data.p /* the value stored in a large token node */ @d set_knot_value(A,B) do { /* store the value in a large token node */ knot_value(A)=(B); str_value(A)=NULL; value_node(A)=NULL; value(A)=0; } while (0) @(mpmp.h@>= typedef struct mp_token_node_data { NODE_BODY; mp_value_data data; } mp_token_node_data; typedef struct mp_token_node_data *mp_token_node; @ @c static mp_node mp_get_token_node (MP mp) { mp_token_node p = (mp_token_node) xmalloc (1, token_node_size); add_var_used (token_node_size); memset (p, 0, token_node_size); p->type = mp_token_node_type; FUNCTION_TRACE2 ("%p = mp_get_token_node()\n", p); return (mp_node) p; } @ A numeric token is created by the following trivial routine. @c static mp_node mp_new_num_tok (MP mp, scaled v) { mp_node p; /* the new node */ p = mp_get_token_node (mp); set_value (p, v); p->type = mp_known; p->name_type = mp_token; return p; } @ 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. @c static void mp_flush_token_list (MP mp, mp_node p) { mp_node q; /* the node being recycled */ FUNCTION_TRACE2 ("mp_flush_token_list(%p)\n", p); while (p != NULL) { q = p; p = mp_link (p); if (mp_type (q) == mp_symbol_node) { mp_free_symbolic_node (mp, q); } else { switch (mp_type (q)) { case mp_vacuous: case mp_boolean_type: case mp_known: break; case mp_string_type: delete_str_ref (str_value (q)); break; case unknown_types: case mp_pen_type: case mp_path_type: case mp_picture_type: case mp_pair_type: case mp_color_type: case mp_cmykcolor_type: case mp_transform_type: case mp_dependent: case mp_proto_dependent: case mp_independent: mp_recycle_value (mp, q); break; default: mp_confusion (mp, "token"); @:this can't happen token}{\quad token@>; } mp_free_node (mp, q, token_node_size); } } } @ 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. 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}'. @<Declarations@>= static void mp_show_token_list (MP mp, mp_node p, mp_node q, integer l, integer null_tally); @ @c void mp_show_token_list (MP mp, mp_node p, mp_node q, integer l, integer null_tally) { quarterword class, c; /* the |char_class| of previous and new tokens */ integer r, v; /* temporary registers */ class = percent_class; mp->tally = null_tally; while ((p != NULL) && (mp->tally < l)) { if (p == q) @<Do magic computation@>; @<Display token |p| and set |c| to its class; but |return| if there are problems@>; class = c; p = mp_link (p); } if (p != NULL) mp_print (mp, " ETC."); @.ETC@>; return; } @ @<Display token |p| and set |c| to its class...@>= c = letter_class; /* the default */ if (mp_type (p) != mp_symbol_node) { @<Display non-symbolic token@>; } else { if (mp_name_type (p) == mp_expr_sym || mp_name_type (p) == mp_suffix_sym || mp_name_type (p) == mp_text_sym) { r = mp_sym_info (p); if (mp_name_type (p) == mp_expr_sym) { mp_print (mp, "(EXPR"); @.EXPR@> } else if (mp_name_type (p) == mp_suffix_sym) { mp_print (mp, "(SUFFIX"); @.SUFFIX@> } else { mp_print (mp, "(TEXT"); @.TEXT@> } mp_print_int (mp, r); mp_print_char (mp, xord (')')); c = right_paren_class; } else { mp_sym sr = mp_sym_sym (p); if (sr == 0) { @<Display a collective subscript@> } else { str_number rr = text (sr); if (rr == NULL) { mp_print (mp, " NONEXISTENT"); @.NONEXISTENT@> } else { @<Print string |r| as a symbolic token and set |c| to its class@>; } } } } @ @<Display non-symbolic token@>= if (mp_name_type (p) == mp_token) { if (mp_type (p) == mp_known) { @<Display a numeric token@>; } else if (mp_type (p) != mp_string_type) { mp_print (mp, " BAD"); @.BAD@> } else { mp_print_char (mp, xord ('"')); mp_print_str (mp, str_value (p)); mp_print_char (mp, xord ('"')); c = string_class; } } else if ((mp_name_type (p) != mp_capsule) || (mp_type (p) < mp_vacuous) || (mp_type (p) > mp_independent)) { mp_print (mp, " BAD"); } else { mp_print_capsule (mp, p); c = right_paren_class; } @ @<Display a numeric token@>= if (class == digit_class) mp_print_char (mp, xord (' ')); v = value (p); if (v < 0) { if (class == left_bracket_class) mp_print_char (mp, xord (' ')); mp_print_char (mp, xord ('[')); mp_print_scaled (mp, v); mp_print_char (mp, xord (']')); c = right_bracket_class; } else { mp_print_scaled (mp, v); c = digit_class; } @ Strictly speaking, a genuine token will never have |mp_info(p)=0|. But we will see later (in the |print_variable_name| routine) that it is convenient to let |mp_info(p)=0| stand for `\.{[]}'. @<Display a collective subscript@>= { if (class == left_bracket_class) mp_print_char (mp, xord (' ')); mp_print (mp, "[]"); c = right_bracket_class; } @ @<Print string |r| as a symbolic token...@>= { c = (quarterword) mp->char_class[(rr->str[0])]; if (c == class) { switch (c) { case letter_class: mp_print_char (mp, xord ('.')); break; case isolated_classes: break; default: mp_print_char (mp, xord (' ')); break; } } mp_print_str (mp, rr); } @ @<Declarations@>= static void mp_print_capsule (MP mp, mp_node p); @ @<Declare miscellaneous procedures that were declared |forward|@>= void mp_print_capsule (MP mp, mp_node p) { mp_print_char (mp, xord ('(')); mp_print_exp (mp, p, 0); mp_print_char (mp, xord (')')); } @ Macro definitions are kept in \MP's memory in the form of token lists that have a few extra symbolic 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 |sym_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 consist of zero or more parameter tokens followed by a code for the type of macro. @d ref_count(A) mp_sym_info(A) /* reference count preceding a macro definition or picture header */ @d add_mac_ref(A) set_mp_sym_info((A),ref_count((A))+1) /* make a new reference to a macro list */ @d decr_mac_ref(A) set_mp_sym_info((A),ref_count((A))-1) /* remove a 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 */ @d expr_param 8 @d suffix_param 9 @d text_param 10 @c static void mp_delete_mac_ref (MP mp, mp_node p) { /* |p| points to the reference count of a macro list that is losing one reference */ if (ref_count (p) == 0) mp_flush_token_list (mp, p); else decr_mac_ref (p); } @ The following subroutine displays a macro, given a pointer to its reference count. @c static void mp_show_macro (MP mp, mp_node p, mp_node q, integer l) { mp_node r; /* temporary storage */ p = mp_link (p); /* bypass the reference count */ while (mp_name_type (p) != mp_macro_sym) { r = mp_link (p); mp_link (p) = NULL; mp_show_token_list (mp, p, NULL, l, 0); mp_link (p) = r; p = r; if (l > 0) l = l - mp->tally; else return; } /* control printing of `\.{ETC.}' */ @.ETC@>; mp->tally = 0; switch (mp_sym_info (p)) { case general_macro: mp_print (mp, "->"); break; @.->@> case primary_macro: case secondary_macro: case tertiary_macro: mp_print_char (mp, xord ('<')); mp_print_cmd_mod (mp, param_type, mp_sym_info (p)); mp_print (mp, ">->"); break; case expr_macro: mp_print (mp, "<expr>->"); break; case of_macro: mp_print (mp, "<expr>of<primary>->"); break; case suffix_macro: mp_print (mp, "<suffix>->"); break; case text_macro: mp_print (mp, "<text>->"); break; } /* there are no other cases */ mp_show_token_list (mp, mp_link (p), q, l - mp->tally, 0); } @* 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} \.{x[]a.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 non-symbolic node called a value node, or as a non-symbolic 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 |mp_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. TH Note: DEK and JDH had a nice theoretical split between |value|, |attr| and |subscr| nodes, as documented above and further below. However, all three types had a bad habit of transmuting into each other in practice while pointers to them still lived on elsewhere, so using three different C structures is simply not workable. All three are now represented as a single C structure called |mp_value_node|. There is a union in this structure in the interest of space saving: |subscript_| and |hashloc_| are mutually exclusive. Actually, so are |attr_head_| + |subscr_head_| on one side and and |value_| on the other, but because of all the access macros that are used in the code base to get at values, those cannot be folded into a union (yet); this would have required creating a similar union in |mp_token_node| where it would only serve to confuse things. Finally, |parent_| only applies in |attr| nodes (the ones that have |hashloc_|), but creating an extra substructure inside the union just for that does not save space and the extra complication in the structure is not worth the minimal extra code clarification. @d attr_head(A) ((mp_value_node)(A))->attr_head_ /* pointer to attribute info */ @d set_attr_head(A,B) do { mp_node d = (B); /* |printf("set attrhead of %p to %p on %d\n",A,d,__LINE__);| */ attr_head((A)) = d; } while (0) @d subscr_head(A) ((mp_value_node)(A))->subscr_head_ /* pointer to subscript info */ @d set_subscr_head(A,B) do { mp_node d = (B); /* |printf("set subcrhead of %p to %p on %d\n",A,d,__LINE__);| */ subscr_head((A)) = d; } while (0) @(mpmp.h@>= typedef struct mp_value_node_data { NODE_BODY; mp_value_data data; union { scaled subscript_; mp_sym hashloc_; } v; mp_node parent_; mp_node attr_head_; mp_node subscr_head_; } mp_value_node_data; @ @<Declarations@>= static mp_node mp_get_value_node (MP mp); @ It would have been nicer to make |mp_get_value_node| return |mp_value_node| variables, but with |eqtb| as it stands that became messy: lots of typecasts. So, it returns a simple |mp_node| for now. @d value_node_size sizeof(struct mp_value_node_data) /* the number of words in a value node */ @d mp_free_value_node(a,b) mp_free_node(a,b,value_node_size) @c static mp_node mp_get_value_node (MP mp) { mp_node p = xmalloc (1, value_node_size); add_var_used (value_node_size); memset (p, 0, value_node_size); mp_type (p) = mp_value_node_type; FUNCTION_TRACE2 ("%p = mp_get_value_node()\n", p); return p; } @ 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 |hashloc| 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 |mp_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 |hashloc(mp_link(p))>hashloc(p)|. The final attribute node links to the constant |end_attr|, whose |hashloc| field is greater than any legal hash address. The |attr_head| in the parent points to a node whose |name_type| is |mp_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 has the fields of 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 |hashloc| 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 non-symbolic value node with |mp_name_type(p)=root| and |mp_link(p)=h(x)|. We have |type(p)=mp_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 |mp_name_type(q)=mp_structured_root| and |mp_link(q)=q1|, where |q1| points to an attribute node representing `\.{x[]}'. Thus |mp_name_type(q1)=attr|, |hashloc(q1)=collective_subscript=0|, |parent(q1)=p|, |type(q1)=mp_structured|, |attr_head(q1)=qq|, and |subscr_head(q1)=qq1|; |qq| is a three-word ``attribute-as-value'' node with |type(qq)=numeric_type| (assuming that \.{x5} is numeric, because |qq| represents `\.{x[]}' with no further attributes), |mp_name_type(qq)=structured_root|, |hashloc(qq)=0|, |parent(qq)=p|, and |mp_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 |mp_name_type(qq1)=attr|, |hashloc(qq1)=collective_subscript|, |parent(qq1)=q1|, and |mp_link(qq1)=qq2|. Since |qq2| represents `\.{x[]b}', |type(qq2)=mp_unknown_boolean|; also |hashloc(qq2)=h(b)|, |parent(qq2)=q1|, |mp_name_type(qq2)=attr|, |mp_link(qq2)=end_attr|. (Maybe colored lines will help untangle your picture.) Node |r| is a subscript node with |type| and |value| representing `\.{x5}'; |mp_name_type(r)=subscr|, |subscript(r)=5.0|, and |mp_link(r)=r1| is another subscript node. To complete the picture, see if you can guess what |mp_link(r1)| is; give up? It's~|q1|. Furthermore |subscript(r1)=20.0|, |mp_name_type(r1)=subscr|, |type(r1)=mp_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 hashloc(A) ((mp_value_node)(A))->v.hashloc_ /* hash address of this attribute */ @d set_hashloc(A,B) do { /* |printf ("set attrloc of %p to %d on %d\n", (A), d, __LINE__);| */ ((mp_value_node)(A))->v.hashloc_ = (mp_sym)(B); } while (0) @d parent(A) (A)->parent_ /* pointer to |mp_structured| variable */ @ @d mp_free_attr_node(a,b) mp_free_node(a,b,value_node_size) @c static mp_value_node mp_get_attr_node (MP mp) { mp_value_node p = (mp_value_node) mp_get_value_node (mp); mp_type (p) = mp_attr_node_type; return p; } @ Setting the |hashloc| field of |end_attr| to a value greater than any legal hash address is done by assigning $-1$ typecasted to |mp_sym|, hopefully resulting in all bits being set. On systems that support negative pointer values or where typecasting $-1$ does not result in all bits in a pointer being set, something else needs to be done. @^system dependencies@> @<Initialize table...@>= mp->end_attr = (mp_node) mp_get_attr_node (mp); set_hashloc (mp->end_attr, -1); parent ((mp_value_node) mp->end_attr) = NULL; @ @<Free table...@>= mp_free_attr_node (mp, mp->end_attr); @ @d collective_subscript 0 /* code for the attribute `\.{[]}' */ @d subscript(A) ((mp_value_node)(A))->v.subscript_ /* subscript of this variable */ @c static mp_value_node mp_get_subscr_node (MP mp) { mp_value_node p = (mp_value_node) mp_get_value_node (mp); mp_type (p) = mp_subscr_node_type; return p; } @ 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=mp_x_part_sector| and the second has |name_type=mp_y_part_sector|; the |link| in the first points back to the node whose |value| points to this four-word node. @d x_part_loc(A) ((mp_pair_node)(A))->x_part_ /* where the \&{xpart} is found in a pair node */ @d y_part_loc(A) ((mp_pair_node)(A))->y_part_ /* where the \&{ypart} is found in a pair node */ @(mpmp.h@>= typedef struct mp_pair_node_data { NODE_BODY; mp_node x_part_; mp_node y_part_; } mp_pair_node_data; typedef struct mp_pair_node_data *mp_pair_node; @ @d pair_node_size sizeof(struct mp_pair_node_data) /* the number of words in a subscript node */ @c static mp_node mp_get_pair_node (MP mp) { mp_pair_node p = (mp_pair_node) xmalloc (1, pair_node_size); add_var_used (pair_node_size); memset (p, 0, pair_node_size); mp_type (p) = mp_pair_node_type; FUNCTION_TRACE2("get_pair_node(): %p\n", p); return (mp_node) p; } @ If |type(p)=mp_pair_type| or if |value(p)=NULL|, the procedure call |init_pair_node(p)| will allocate a pair node for~|p|. The individual parts of such nodes are initially of type |mp_independent|. @c static void mp_init_pair_node (MP mp, mp_node p) { mp_node q; /* the new node */ mp_type (p) = mp_pair_type; q = mp_get_pair_node (mp); y_part_loc (q) = mp_get_value_node (mp); new_indep (y_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (y_part_loc (q)) = (quarterword) (mp_y_part_sector); mp_link (y_part_loc (q)) = p; x_part_loc (q) = mp_get_value_node (mp); new_indep (x_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (x_part_loc (q)) = (quarterword) (mp_x_part_sector); mp_link (x_part_loc (q)) = p; set_value_node (p, q); } @ 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|, |mp_xx_part_sector|, |mp_xy_part_sector|, |mp_yx_part_sector|, and |mp_yy_part_sector|. @d tx_part_loc(A) ((mp_transform_node)(A))->tx_part_ /* where the \&{xpart} is found in a transform node */ @d ty_part_loc(A) ((mp_transform_node)(A))->ty_part_ /* where the \&{ypart} is found in a transform node */ @d xx_part_loc(A) ((mp_transform_node)(A))->xx_part_ /* where the \&{xxpart} is found in a transform node */ @d xy_part_loc(A) ((mp_transform_node)(A))->xy_part_ /* where the \&{xypart} is found in a transform node */ @d yx_part_loc(A) ((mp_transform_node)(A))->yx_part_ /* where the \&{yxpart} is found in a transform node */ @d yy_part_loc(A) ((mp_transform_node)(A))->yy_part_ /* where the \&{yypart} is found in a transform node */ @(mpmp.h@>= typedef struct mp_transform_node_data { NODE_BODY; mp_node tx_part_; mp_node ty_part_; mp_node xx_part_; mp_node yx_part_; mp_node xy_part_; mp_node yy_part_; } mp_transform_node_data; typedef struct mp_transform_node_data *mp_transform_node; @ @d transform_node_size sizeof(struct mp_transform_node_data) /* the number of words in a subscript node */ @c static mp_node mp_get_transform_node (MP mp) { mp_transform_node p = (mp_transform_node) xmalloc (1, transform_node_size); add_var_used (transform_node_size); memset (p, 0, transform_node_size); mp_type (p) = mp_transform_node_type; return (mp_node) p; } @ @c static void mp_init_transform_node (MP mp, mp_node p) { mp_node q; /* the new node */ mp_type (p) = mp_transform_type; q = mp_get_transform_node (mp); /* big node */ yy_part_loc (q) = mp_get_value_node (mp); new_indep (yy_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (yy_part_loc (q)) = (quarterword) (mp_yy_part_sector); mp_link (yy_part_loc (q)) = p; yx_part_loc (q) = mp_get_value_node (mp); new_indep (yx_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (yx_part_loc (q)) = (quarterword) (mp_yx_part_sector); mp_link (yx_part_loc (q)) = p; xy_part_loc (q) = mp_get_value_node (mp); new_indep (xy_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (xy_part_loc (q)) = (quarterword) (mp_xy_part_sector); mp_link (xy_part_loc (q)) = p; xx_part_loc (q) = mp_get_value_node (mp); new_indep (xx_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (xx_part_loc (q)) = (quarterword) (mp_xx_part_sector); mp_link (xx_part_loc (q)) = p; ty_part_loc (q) = mp_get_value_node (mp); new_indep (ty_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (ty_part_loc (q)) = (quarterword) (mp_y_part_sector); mp_link (ty_part_loc (q)) = p; tx_part_loc (q) = mp_get_value_node (mp); new_indep (tx_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (tx_part_loc (q)) = (quarterword) (mp_x_part_sector); mp_link (tx_part_loc (q)) = p; set_value_node (p, q); } @ Variables of type \&{color} have 3~values in 6~words identified by |mp_red_part_sector|, |mp_green_part_sector|, and |mp_blue_part_sector|. @d red_part_loc(A) ((mp_color_node)(A))->red_part_ /* where the \&{redpart} is found in a color node */ @d green_part_loc(A) ((mp_color_node)(A))->green_part_ /* where the \&{greenpart} is found in a color node */ @d blue_part_loc(A) ((mp_color_node)(A))->blue_part_ /* where the \&{bluepart} is found in a color node */ @d grey_part_loc(A) red_part_loc(A) /* where the \&{greypart} is found in a color node */ @(mpmp.h@>= typedef struct mp_color_node_data { NODE_BODY; mp_node red_part_; mp_node green_part_; mp_node blue_part_; } mp_color_node_data; typedef struct mp_color_node_data *mp_color_node; @ @d color_node_size sizeof(struct mp_color_node_data) /* the number of words in a subscript node */ @c static mp_node mp_get_color_node (MP mp) { mp_color_node p = (mp_color_node) xmalloc (1, color_node_size); add_var_used (color_node_size); memset (p, 0, color_node_size); mp_type (p) = mp_color_node_type; p->link = NULL; return (mp_node) p; } @ @c static void mp_init_color_node (MP mp, mp_node p) { mp_node q; /* the new node */ mp_type (p) = mp_color_type; q = mp_get_color_node (mp); /* big node */ blue_part_loc (q) = mp_get_value_node (mp); new_indep (blue_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (blue_part_loc (q)) = (quarterword) (mp_blue_part_sector); mp_link (blue_part_loc (q)) = p; green_part_loc (q) = mp_get_value_node (mp); new_indep (green_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (y_part_loc (q)) = (quarterword) (mp_green_part_sector); mp_link (green_part_loc (q)) = p; red_part_loc (q) = mp_get_value_node (mp); new_indep (red_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (red_part_loc (q)) = (quarterword) (mp_red_part_sector); mp_link (red_part_loc (q)) = p; set_value_node (p, q); } @ Finally, variables of type |cmykcolor|. @d cyan_part_loc(A) ((mp_cmykcolor_node)(A))->cyan_part_ /* where the \&{cyanpart} is found in a color node */ @d magenta_part_loc(A) ((mp_cmykcolor_node)(A))->magenta_part_ /* where the \&{magentapart} is found in a color node */ @d yellow_part_loc(A) ((mp_cmykcolor_node)(A))->yellow_part_ /* where the \&{yellowpart} is found in a color node */ @d black_part_loc(A) ((mp_cmykcolor_node)(A))->black_part_ /* where the \&{blackpart} is found in a color node */ @(mpmp.h@>= typedef struct mp_cmykcolor_node_data { NODE_BODY; mp_node cyan_part_; mp_node magenta_part_; mp_node yellow_part_; mp_node black_part_; } mp_cmykcolor_node_data; typedef struct mp_cmykcolor_node_data *mp_cmykcolor_node; @ @d cmykcolor_node_size sizeof(struct mp_cmykcolor_node_data) /* the number of words in a subscript node */ @c static mp_node mp_get_cmykcolor_node (MP mp) { mp_cmykcolor_node p = (mp_cmykcolor_node) xmalloc (1, cmykcolor_node_size); add_var_used (cmykcolor_node_size); memset (p, 0, cmykcolor_node_size); mp_type (p) = mp_cmykcolor_node_type; p->link = NULL; return (mp_node) p; } @ @c static void mp_init_cmykcolor_node (MP mp, mp_node p) { mp_node q; /* the new node */ mp_type (p) = mp_cmykcolor_type; q = mp_get_cmykcolor_node (mp); /* big node */ black_part_loc (q) = mp_get_value_node (mp); new_indep (black_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (black_part_loc (q)) = (quarterword) (mp_black_part_sector); mp_link (black_part_loc (q)) = p; yellow_part_loc (q) = mp_get_value_node (mp); new_indep (yellow_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (yellow_part_loc (q)) = (quarterword) (mp_yellow_part_sector); mp_link (yellow_part_loc (q)) = p; magenta_part_loc (q) = mp_get_value_node (mp); new_indep (magenta_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (magenta_part_loc (q)) = (quarterword) (mp_magenta_part_sector); mp_link (magenta_part_loc (q)) = p; cyan_part_loc (q) = mp_get_value_node (mp); new_indep (cyan_part_loc (q)); /* sets |type(q)| and |value(q)| */ mp_name_type (cyan_part_loc (q)) = (quarterword) (mp_cyan_part_sector); mp_link (cyan_part_loc (q)) = p; set_value_node (p, q); } @ 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}. @ The |id_transform| function creates a capsule for the identity transformation. @c static mp_node mp_id_transform (MP mp) { mp_node p, q; /* list manipulation registers */ p = mp_get_value_node (mp); mp_name_type (p) = mp_capsule; set_value (p, 0); /* todo: this was |null| */ mp_init_transform_node (mp, p); q = value_node (p); mp_type (tx_part_loc (q)) = mp_known; set_value (tx_part_loc (q), 0); mp_type (ty_part_loc (q)) = mp_known; set_value (ty_part_loc (q), 0); mp_type (xy_part_loc (q)) = mp_known; set_value (xy_part_loc (q), 0); mp_type (yx_part_loc (q)) = mp_known; set_value (yx_part_loc (q), 0); mp_type (xx_part_loc (q)) = mp_known; set_value (xx_part_loc (q), unity); mp_type (yy_part_loc (q)) = mp_known; set_value (yy_part_loc (q), unity); return p; } @ 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. @c static void mp_new_root (MP mp, mp_sym x) { mp_node p; /* the new node */ p = mp_get_value_node (mp); mp_type (p) = undefined; mp_name_type (p) = mp_root; value_sym (p) = x; equiv_node (x) = p; } @ 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 value. @<Declarations@>= static void mp_print_variable_name (MP mp, mp_node p); @ @c void mp_print_variable_name (MP mp, mp_node p) { mp_node q; /* a token list that will name the variable's suffix */ mp_node r; /* temporary for token list creation */ while (mp_name_type (p) >= mp_x_part_sector) { @<Preface the output with a part specifier; |return| in the case of a capsule@>; } q = NULL; while (mp_name_type (p) > mp_saved_root) { @<Ascend one level, pushing a token onto list |q| and replacing |p| by its parent@>; } /* now |link(p)| is the hash address of |p|, and |name_type(p)| is either |root| or |saved_root|. Have to prepend a token to |q| for |show_token_list|. */ r = mp_get_symbolic_node (mp); set_mp_sym_sym (r, value_sym (p)); mp_link (r) = q; if (mp_name_type (p) == mp_saved_root) mp_print (mp, "(SAVED)"); @.SAVED@>; mp_show_token_list (mp, r, NULL, max_integer, mp->tally); mp_flush_token_list (mp, r); } @ @<Ascend one level, pushing a token onto list |q|...@>= { if (mp_name_type (p) == mp_subscr) { r = mp_new_num_tok (mp, subscript (p)); do { p = mp_link (p); } while (mp_name_type (p) != mp_attr); } else if (mp_name_type (p) == mp_structured_root) { p = mp_link (p); goto FOUND; } else { if (mp_name_type (p) != mp_attr) mp_confusion (mp, "var"); @:this can't happen var}{\quad var@>; r = mp_get_symbolic_node (mp); set_mp_sym_sym (r, hashloc (p)); /* the hash address */ } set_mp_link (r, q); q = r; FOUND: p = parent ((mp_value_node) p); } @ @<Preface the output with a part specifier...@>= { switch (mp_name_type (p)) { case mp_x_part_sector: mp_print (mp, "x"); break; case mp_y_part_sector: mp_print (mp, "y"); break; case mp_xx_part_sector: mp_print (mp, "xx"); break; case mp_xy_part_sector: mp_print (mp, "xy"); break; case mp_yx_part_sector: mp_print (mp, "yx"); break; case mp_yy_part_sector: mp_print (mp, "yy"); break; case mp_red_part_sector: mp_print (mp, "red"); break; case mp_green_part_sector: mp_print (mp, "green"); break; case mp_blue_part_sector: mp_print (mp, "blue"); break; case mp_cyan_part_sector: mp_print (mp, "cyan"); break; case mp_magenta_part_sector: mp_print (mp, "magenta"); break; case mp_yellow_part_sector: mp_print (mp, "yellow"); break; case mp_black_part_sector: mp_print (mp, "black"); break; case mp_grey_part_sector: mp_print (mp, "grey"); break; case mp_capsule: { char pval[19]; /* allow 64bit pointers, + "0x" */ mp_print (mp, "%CAPSULE"); sprintf (pval, "%p", p); mp_print (mp, pval); return; } break; @.CAPSULE@> } /* there are no other cases */ mp_print (mp, "part "); p = mp_link (p); } @ The |interesting| function returns |true| if a given variable is not in a capsule, or if the user wants to trace capsules. @c static boolean mp_interesting (MP mp, mp_node p) { quarterword t; /* a |name_type| */ if (internal_value (mp_tracing_capsules) > 0) { return true; } else { t = mp_name_type (p); if (t >= mp_x_part_sector && t != mp_capsule) { switch (t) { case mp_x_part_sector: t = mp_name_type (mp_link (x_part_loc (p))); break; case mp_y_part_sector: t = mp_name_type (mp_link (y_part_loc (p))); break; case mp_xx_part_sector: t = mp_name_type (mp_link (xx_part_loc (p))); break; case mp_xy_part_sector: t = mp_name_type (mp_link (xy_part_loc (p))); break; case mp_yx_part_sector: t = mp_name_type (mp_link (yx_part_loc (p))); break; case mp_yy_part_sector: t = mp_name_type (mp_link (yy_part_loc (p))); break; case mp_red_part_sector: t = mp_name_type (mp_link (red_part_loc (p))); break; case mp_green_part_sector: t = mp_name_type (mp_link (green_part_loc (p))); break; case mp_blue_part_sector: t = mp_name_type (mp_link (blue_part_loc (p))); break; case mp_cyan_part_sector: t = mp_name_type (mp_link (cyan_part_loc (p))); break; case mp_magenta_part_sector: t = mp_name_type (mp_link (magenta_part_loc (p))); break; case mp_yellow_part_sector: t = mp_name_type (mp_link (yellow_part_loc (p))); break; case mp_black_part_sector: t = mp_name_type (mp_link (black_part_loc (p))); break; case mp_grey_part_sector: t = mp_name_type (mp_link (grey_part_loc (p))); break; } } } return (t != mp_capsule); } @ Now here is a subroutine that converts an unstructured type into an equivalent structured type, by inserting a |mp_structured| node that is capable of growing. This operation is done only when |mp_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. @c static mp_node mp_new_structure (MP mp, mp_node p) { mp_node q, r = NULL; /* list manipulation registers */ mp_sym qq = NULL; switch (mp_name_type (p)) { case mp_root: { qq = value_sym (p); r = mp_get_value_node (mp); equiv_node (qq) = r; } break; case mp_subscr: @<Link a new subscript node |r| in place of node |p|@>; break; case mp_attr: @<Link a new attribute node |r| in place of node |p|@>; break; default: mp_confusion (mp, "struct"); @:this can't happen struct}{\quad struct@>; break; } set_mp_link (r, mp_link (p)); value_sym (r) = value_sym (p); mp_type (r) = mp_structured; mp_name_type (r) = mp_name_type (p); set_attr_head (r, p); mp_name_type (p) = mp_structured_root; { mp_value_node qqr = mp_get_attr_node (mp); set_mp_link (p, (mp_node) qqr); set_subscr_head (r, (mp_node) qqr); parent (qqr) = r; mp_type (qqr) = undefined; mp_name_type (qqr) = mp_attr; set_mp_link (qqr, mp->end_attr); set_hashloc (qqr, collective_subscript); } return r; } @ @<Link a new subscript node |r| in place of node |p|@>= { mp_node q_new; q = p; do { q = mp_link (q); } while (mp_name_type (q) != mp_attr); q = parent ((mp_value_node) q); r = mp->temp_head; set_mp_link (r, subscr_head (q)); do { q_new = r; r = mp_link (r); } while (r != p); r = (mp_node) mp_get_subscr_node (mp); if (q_new == mp->temp_head) { subscr_head (q) = r; } else { set_mp_link (q_new, r); } subscript (r) = subscript (p); } @ 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|@>= { mp_value_node rr; q = parent ((mp_value_node) p); r = attr_head (q); do { q = r; r = mp_link (r); } while (r != p); rr = mp_get_attr_node (mp); r = (mp_node) rr; set_mp_link (q, (mp_node) rr); set_hashloc (rr, hashloc (p)); parent (rr) = parent ((mp_value_node) p); if (hashloc (p) == collective_subscript) { q = mp->temp_head; set_mp_link (q, subscr_head (parent ((mp_value_node) p))); while (mp_link (q) != p) q = mp_link (q); if (q == mp->temp_head) subscr_head (parent ((mp_value_node) p)) = (mp_node) rr; else set_mp_link (q, (mp_node) rr); } } @ The |find_variable| routine is given a pointer~|t| to a nonempty token list of suffixes; it returns a pointer to the corresponding non-symbolic 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)<mp_structured|. @d abort_find { return NULL; } @c static mp_node mp_find_variable (MP mp, mp_node t) { mp_node p, q, r, s; /* nodes in the ``value'' line */ mp_sym p_sym; mp_node pp, qq, rr, ss; /* nodes in the ``collective'' line */ integer n; /* subscript or attribute */ @^inner loop@>; p_sym = mp_sym_sym (t); t = mp_link (t); if ((eq_type (p_sym) % outer_tag) != tag_token) abort_find; if (equiv_node (p_sym) == NULL) mp_new_root (mp, p_sym); p = equiv_node (p_sym); pp = p; while (t != NULL) { @<Make sure that both nodes |p| and |pp| are of |mp_structured| type@>; if (mp_type (t) != mp_symbol_node) { @<Descend one level for the subscript |value(t)|@> } else { @<Descend one level for the attribute |mp_sym_info(t)|@>; } t = mp_link (t); } if (mp_type (pp) >= mp_structured) { if (mp_type (pp) == mp_structured) pp = attr_head (pp); else abort_find; } if (mp_type (p) == mp_structured) p = attr_head (p); if (mp_type (p) == undefined) { if (mp_type (pp) == undefined) { mp_type (pp) = mp_numeric_type; set_value (pp, 0); /* todo: this was |null| */ } mp_type (p) = mp_type (pp); set_value (p, 0); /* todo: this was |null| */ } return p; } @ 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 (mp_type (pp) != mp_structured) { if (mp_type (pp) > mp_structured) abort_find; ss = mp_new_structure (mp, pp); if (p == pp) p = ss; pp = ss; } /* now |type(pp)=mp_structured| */ if (mp_type (p) != mp_structured) { /* it cannot be |>mp_structured| */ p = mp_new_structure (mp, p); /* now |type(p)=mp_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)|@>= { halfword save_subscript; /* temporary storage */ n = value (t); pp = mp_link (attr_head (pp)); /* now |hashloc(pp)=collective_subscript| */ q = mp_link (attr_head (p)); save_subscript = subscript (q); subscript (q) = EL_GORDO; s = mp->temp_head; set_mp_link (s, subscr_head (p)); do { r = s; s = mp_link (s); } while (n > subscript (s)); if (n == subscript (s)) { p = s; } else { mp_value_node pp = mp_get_subscr_node (mp); if (r == mp->temp_head) set_subscr_head (p, (mp_node) pp); else set_mp_link (r, (mp_node) pp); set_mp_link (pp, s); subscript (pp) = n; mp_name_type (pp) = mp_subscr; mp_type (pp) = undefined; p = (mp_node) pp; } subscript (q) = save_subscript; } @ @<Descend one level for the attribute |mp_sym_info(t)|@>= { mp_sym nn = mp_sym_sym (t); ss = attr_head (pp); do { rr = ss; ss = mp_link (ss); } while (nn > hashloc (ss)); if (nn < hashloc (ss)) { qq = (mp_node) mp_get_attr_node (mp); set_mp_link (rr, qq); set_mp_link (qq, ss); set_hashloc (qq, nn); mp_name_type (qq) = mp_attr; mp_type (qq) = undefined; parent ((mp_value_node) qq) = pp; ss = qq; } if (p == pp) { p = ss; pp = ss; } else { pp = ss; s = attr_head (p); do { r = s; s = mp_link (s); } while (nn > hashloc (s)); if (nn == hashloc (s)) { p = s; } else { q = (mp_node) mp_get_attr_node (mp); set_mp_link (r, q); set_mp_link (q, s); set_hashloc (q, nn); mp_name_type (q) = mp_attr; mp_type (q) = undefined; parent ((mp_value_node) q) = p; p = q; } } } @ 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 symbolic nodes that represent suffixes, with |info=collective_subscript| for subscripts. @<Declarations@>= static void mp_flush_cur_exp (MP mp, mp_value v); @ @c static void mp_flush_variable (MP mp, mp_node p, mp_node t, boolean discard_suffixes) { mp_node q, r; /* list manipulation */ mp_sym n; /* attribute to match */ while (t != NULL) { if (mp_type (p) != mp_structured) return; n = mp_sym_sym (t); t = mp_link (t); if (n == collective_subscript) { r = mp->temp_head; mp_link (r) = subscr_head (p); q = mp_link (r); while (mp_name_type (q) == mp_subscr) { mp_flush_variable (mp, q, t, discard_suffixes); if (t == NULL) { if (mp_type (q) == mp_structured) { r = q; } else { set_mp_link (r, mp_link (q)); mp_free_node (mp, q, value_node_size); } } else { r = q; } q = mp_link (r); } /* fix |subscr_head| if it was already present */ if (q==mp_link(mp->temp_head)) set_subscr_head (p, q); } p = attr_head (p); do { r = p; p = mp_link (p); } while (hashloc (p) < n); if (hashloc (p) != n) return; } if (discard_suffixes) { mp_flush_below_variable (mp, p); } else { if (mp_type (p) == mp_structured) p = attr_head (p); mp_recycle_value (mp, p); } } @ The next procedure is simpler; it wipes out everything but |p| itself, which becomes undefined. @<Declarations@>= static void mp_flush_below_variable (MP mp, mp_node p); @ @c void mp_flush_below_variable (MP mp, mp_node p) { mp_node q, r; /* list manipulation registers */ FUNCTION_TRACE2 ("mp_flush_below_variable(%p)\n", p); if (mp_type (p) != mp_structured) { mp_recycle_value (mp, p); /* this sets |type(p)=undefined| */ } else { q = subscr_head (p); while (mp_name_type (q) == mp_subscr) { mp_flush_below_variable (mp, q); r = q; q = mp_link (q); mp_free_node (mp, r, value_node_size); } r = attr_head (p); q = mp_link (r); mp_recycle_value (mp, r); mp_free_node (mp, r, value_node_size); do { mp_flush_below_variable (mp, q); r = q; q = mp_link (q); mp_free_node (mp, r, value_node_size); } while (q != mp->end_attr); mp_type (p) = undefined; } } @ 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. @c static quarterword mp_und_type (MP mp, mp_node p) { (void) mp; switch (mp_type (p)) { case mp_vacuous: return undefined; case mp_boolean_type: case mp_unknown_boolean: return mp_unknown_boolean; case mp_string_type: case mp_unknown_string: return mp_unknown_string; case mp_pen_type: case mp_unknown_pen: return mp_unknown_pen; case mp_path_type: case mp_unknown_path: return mp_unknown_path; case mp_picture_type: case mp_unknown_picture: return mp_unknown_picture; case mp_transform_type: case mp_color_type: case mp_cmykcolor_type: case mp_pair_type: case mp_numeric_type: return mp_type (p); case mp_known: case mp_dependent: case mp_proto_dependent: case mp_independent: return mp_numeric_type; default: /* there are no other valid cases, but please the compiler */ return 0; } return 0; } @ 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. @c static void mp_clear_symbol (MP mp, mp_sym p, boolean saving) { mp_node q; /* |equiv(p)| */ FUNCTION_TRACE3 ("mp_clear_symbol(%p,%d)\n", p, saving); q = equiv_node (p); switch (eq_type (p) % outer_tag) { case defined_macro: case secondary_primary_macro: case tertiary_secondary_macro: case expression_tertiary_macro: if (!saving) mp_delete_mac_ref (mp, q); break; case tag_token: if (q != NULL) { if (saving) { mp_name_type (q) = mp_saved_root; } else { mp_flush_below_variable (mp, q); mp_free_node (mp, q, value_node_size); } } break; default: break; } equiv (p) = mp->frozen_undefined->v.data.val; equiv_node (p) = NULL; eq_type (p) = mp->frozen_undefined->type; } @* 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 |p->info=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 |p->type=mp_normal_sym| and |p->info=q|, means that |p->equiv| holds the former contents of |eqtb[q]|. Such save stack entries are generated by \&{save} commands. \smallskip\hang |p->type=mp_internal_sym| and |p->info=q|, where |q>0|, means that |p->value| is a |mp_internal| 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. @<Types...@>= typedef struct mp_save_data { halfword info; mp_sym sym; quarterword type; mp_internal value; halfword equiv; halfword eq_type; mp_node equiv_n; struct mp_save_data *link; } mp_save_data; @ @<Glob...@>= mp_save_data *save_ptr; /* the most recently saved item */ @ @<Set init...@>= mp->save_ptr = NULL; @ Saving a boundary item @c static void mp_save_boundary (MP mp) { mp_save_data *p; /* temporary register */ FUNCTION_TRACE1 ("mp_save_boundary ()\n"); p = xmalloc (1, sizeof (mp_save_data)); p->info = 0; p->link = mp->save_ptr; mp->save_ptr = p; } @ 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. @c static void mp_save_variable (MP mp, mp_sym q) { mp_save_data *p; /* temporary register */ FUNCTION_TRACE2 ("mp_save_variable (%p)\n", q); if (mp->save_ptr != NULL) { p = xmalloc (1, sizeof (mp_save_data)); p->info = 1; p->sym = q; p->type = mp_normal_sym; p->link = mp->save_ptr; p->equiv = equiv (q); p->eq_type = eq_type (q); p->equiv_n = equiv_node (q); mp->save_ptr = p; } mp_clear_symbol (mp, q, (mp->save_ptr != NULL)); } @ Similarly, |save_internal| is given the location |q| of an internal quantity like |mp_tracing_pens|. It creates a save stack entry of the third kind. @c static void mp_save_internal (MP mp, halfword q) { mp_save_data *p; /* new item for the save stack */ FUNCTION_TRACE2 ("mp_save_internal (%p)\n", q); if (mp->save_ptr != NULL) { p = xmalloc (1, sizeof (mp_save_data)); p->info = q; p->type = mp_internal_sym; p->link = mp->save_ptr; p->value = mp->internal[q]; mp->save_ptr = p; } } @ 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. @c static void mp_unsave (MP mp) { mp_save_data *p; /* saved item */ FUNCTION_TRACE1 ("mp_unsave ()\n"); while (mp->save_ptr->info != 0) { halfword q = mp->save_ptr->info; if (mp->save_ptr->type == mp_internal_sym) { if (internal_value (mp_tracing_restores) > 0) { mp_begin_diagnostic (mp); mp_print_nl (mp, "{restoring "); mp_print (mp, internal_name (q)); mp_print_char (mp, xord ('=')); if (internal_type (q) == mp_known) { mp_print_scaled (mp, mp->save_ptr->value.v.data.val); } else if (internal_type (q) == mp_string_type) { char *s = mp_str (mp, mp->save_ptr->value.v.data.str); mp_print (mp, s); } else { mp_confusion (mp, "internal_restore"); } mp_print_char (mp, xord ('}')); mp_end_diagnostic (mp, false); } mp->internal[q] = mp->save_ptr->value; } else { mp_sym q = mp->save_ptr->sym; if (internal_value (mp_tracing_restores) > 0) { mp_begin_diagnostic (mp); mp_print_nl (mp, "{restoring "); mp_print_text (q); mp_print_char (mp, xord ('}')); mp_end_diagnostic (mp, false); } mp_clear_symbol (mp, q, false); equiv (q) = mp->save_ptr->equiv; eq_type (q) = mp->save_ptr->eq_type; equiv_node (q) = mp->save_ptr->equiv_n; if (eq_type (q) % outer_tag == tag_token) { mp_node pp = equiv_node (q); if (pp != NULL) mp_name_type (pp) = mp_root; } } p = mp->save_ptr->link; xfree (mp->save_ptr); mp->save_ptr = p; } p = mp->save_ptr->link; xfree (mp->save_ptr); mp->save_ptr = p; } @* 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 |mp_left_type| and |mp_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 |mp_left_type| of knot~0 and the |mp_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 mp_x_coord(A) (A)->x_coord /* the |x| coordinate of this knot */ @d mp_y_coord(A) (A)->y_coord /* the |y| coordinate of this knot */ @d mp_left_x(A) (A)->left_x /* the |x| coordinate of previous control point */ @d mp_left_y(A) (A)->left_y /* the |y| coordinate of previous control point */ @d mp_right_x(A) (A)->right_x /* the |x| coordinate of next control point */ @d mp_right_y(A) (A)->right_y /* the |y| coordinate of next control point */ @d mp_next_knot(A) (A)->next /* the next knot in this list */ @d mp_left_type(A) (A)->data.types.left_type /* characterizes the path entering this knot */ @d mp_right_type(A) (A)->data.types.right_type /* characterizes the path leaving this knot */ @d mp_prev_knot(A) (A)->data.prev /* the previous knot in this list (only for pens) */ @d mp_knot_info(A) (A)->data.info /* temporary info, used during splitting */ @<Exported types...@>= typedef struct mp_knot_data *mp_knot; typedef struct mp_knot_data { scaled x_coord; scaled y_coord; scaled left_x; scaled left_y; scaled right_x; scaled right_y; mp_knot next; union { struct { unsigned short left_type; unsigned short right_type; } types; mp_knot prev; signed int info; } data; unsigned char originator; } mp_knot_data; @ @(mplib.h@>= enum mp_knot_type { mp_endpoint = 0, /* |mp_left_type| at path beginning and |mp_right_type| at path end */ mp_explicit, /* |mp_left_type| or |mp_right_type| when control points are known */ mp_given, /* |mp_left_type| or |mp_right_type| when a direction is given */ mp_curl, /* |mp_left_type| or |mp_right_type| when a curl is desired */ mp_open, /* |mp_left_type| or |mp_right_type| when \MP\ should choose the direction */ mp_end_cycle }; @ 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 |mp_right_type=mp_open|, the curve should leave the knot in the same direction it entered; \MP\ will figure out a suitable direction. \yskip \textindent{$\bullet$} If |mp_right_type=mp_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 |mp_right_type=mp_given|, the curve should leave the knot in a nonzero direction stored as an |angle| in |right_given|. \yskip \textindent{$\bullet$} If |mp_right_type=mp_explicit|, the B\'ezier control point for leaving this knot has already been computed; it is in the |mp_right_x| and |mp_right_y| fields. \yskip\noindent The rules for |mp_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 |mp_left_type|&\\{left} info&|mp_x_coord,mp_y_coord|&|mp_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 |mp_right_type| of a node is |explicit| if and only if the |mp_left_type| of the following node is |explicit|. (iii)~|endpoint| types occur only at the ends, as mentioned above. @d left_curl mp_left_x /* curl information when entering this knot */ @d left_given mp_left_x /* given direction when entering this knot */ @d left_tension mp_left_y /* tension information when entering this knot */ @d right_curl mp_right_x /* curl information when leaving this knot */ @d right_given mp_right_x /* given direction when leaving this knot */ @d right_tension mp_right_y /* tension information when leaving this knot */ @ 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):=mp_metapost_user| when it appeared in the actual metapost program, and |originator(p):=mp_program_code| in all other cases. @d mp_originator(A) (A)->originator /* the creator of this knot */ @<Exported types@>= enum mp_knot_originator { mp_program_code = 0, /* not created by a user */ mp_metapost_user /* 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. @<Declarations@>= static void mp_pr_path (MP mp, mp_knot h); @ @c void mp_pr_path (MP mp, mp_knot h) { mp_knot p, q; /* for list traversal */ p = h; do { q = mp_next_knot (p); if ((p == NULL) || (q == NULL)) { mp_print_nl (mp, "???"); return; /* this won't happen */ @.???@> } @<Print information for adjacent knots |p| and |q|@>; DONE1: p = q; if ((p != h) || (mp_left_type (h) != mp_endpoint)) { @<Print two dots, followed by |given| or |curl| if present@>; } } while (p != h); if (mp_left_type (h) != mp_endpoint) mp_print (mp, "cycle"); } @ @<Print information for adjacent knots...@>= mp_print_two (mp, mp_x_coord (p), mp_y_coord (p)); switch (mp_right_type (p)) { case mp_endpoint: if (mp_left_type (p) == mp_open) mp_print (mp, "{open?}"); /* can't happen */ @.open?@>; if ((mp_left_type (q) != mp_endpoint) || (q != h)) q = NULL; /* force an error */ goto DONE1; break; case mp_explicit: @<Print control points between |p| and |q|, then |goto done1|@>; break; case mp_open: @<Print information for a curve that begins |open|@>; break; case mp_curl: case mp_given: @<Print information for a curve that begins |curl| or |given|@>; break; default: mp_print (mp, "???"); /* can't happen */ @.???@>; break; } if (mp_left_type (q) <= mp_explicit) { mp_print (mp, "..control?"); /* can't happen */ @.control?@> } else if ((right_tension (p) != unity) || (left_tension (q) != unity)) { @<Print tension between |p| and |q|@>; } @ 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...@>= { fraction n_sin; fraction n_cos; mp_print_nl (mp, " .."); if (mp_left_type (p) == mp_given) { mp_n_sin_cos (mp, left_given (p), &n_cos, &n_sin); mp_print_char (mp, xord ('{')); mp_print_scaled (mp, n_cos); mp_print_char (mp, xord (',')); mp_print_scaled (mp, n_sin); mp_print_char (mp, xord ('}')); } else if (mp_left_type (p) == mp_curl) { mp_print (mp, "{curl "); mp_print_scaled (mp, left_curl (p)); mp_print_char (mp, xord ('}')); } } @ @<Print tension between |p| and |q|@>= { mp_print (mp, "..tension "); if (right_tension (p) < 0) mp_print (mp, "atleast"); mp_print_scaled (mp, abs (right_tension (p))); if (right_tension (p) != left_tension (q)) { mp_print (mp, " and "); if (left_tension (q) < 0) mp_print (mp, "atleast"); mp_print_scaled (mp, abs (left_tension (q))); } } @ @<Print control points between |p| and |q|, then |goto done1|@>= { mp_print (mp, "..controls "); mp_print_two (mp, mp_right_x (p), mp_right_y (p)); mp_print (mp, " and "); if (mp_left_type (q) != mp_explicit) { mp_print (mp, "??"); /* can't happen */ @.??@> } else { mp_print_two (mp, mp_left_x (q), mp_left_y (q)); } goto DONE1; } @ @<Print information for a curve that begins |open|@>= if ((mp_left_type (p) != mp_explicit) && (mp_left_type (p) != mp_open)) { mp_print (mp, "{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. @<Print information for a curve that begins |curl|...@>= { if (mp_left_type (p) == mp_open) mp_print (mp, "??"); /* can't happen */ @.??@>; if (mp_right_type (p) == mp_curl) { mp_print (mp, "{curl "); mp_print_scaled (mp, right_curl (p)); } else { fraction n_sin; fraction n_cos; mp_n_sin_cos (mp, right_given (p), &n_cos, &n_sin); mp_print_char (mp, xord ('{')); mp_print_scaled (mp, n_cos); mp_print_char (mp, xord (',')); mp_print_scaled (mp, n_sin); } mp_print_char (mp, xord ('}')); } @ It is convenient to have another version of |pr_path| that prints the path as a diagnostic message. @<Declarations@>= static void mp_print_path (MP mp, mp_knot h, const char *s, boolean nuline); @ @c void mp_print_path (MP mp, mp_knot h, const char *s, boolean nuline) { mp_print_diagnostic (mp, "Path", s, nuline); mp_print_ln (mp); @.Path at line...@>; mp_pr_path (mp, h); mp_end_diagnostic (mp, true); } @ @<Declarations@>= static mp_knot mp_new_knot (MP mp); @ @c static mp_knot mp_new_knot (MP mp) { mp_knot q = mp_xmalloc (mp, 1, sizeof (struct mp_knot_data)); return q; } @ If we want to duplicate a knot node, we can say |copy_knot|: @c static mp_knot mp_copy_knot (MP mp, mp_knot p) { mp_knot q; /* the copy */ q = mp_new_knot (mp); memcpy (q, p, sizeof (struct mp_knot_data)); mp_next_knot (q) = NULL; return q; } @ The |copy_path| routine makes a clone of a given path. @c static mp_knot mp_copy_path (MP mp, mp_knot p) { mp_knot q, pp, qq; /* for list manipulation */ if (p == NULL) return NULL; q = mp_copy_knot (mp, p); qq = q; pp = mp_next_knot (p); while (pp != p) { mp_next_knot (qq) = mp_copy_knot (mp, pp); qq = mp_next_knot (qq); pp = mp_next_knot (pp); } mp_next_knot (qq) = q; return q; } @ Just before |ship_out|, knot lists are exported for printing. @ The |export_knot_list| routine therefore also makes a clone of a given path. @c static mp_knot mp_export_knot_list (MP mp, mp_knot p) { mp_knot q; /* the exported copy */ if (p == NULL) return NULL; q = mp_copy_path (mp, p); return q; } static mp_knot mp_import_knot_list (MP mp, mp_knot q) { mp_knot p; /* the imported copy */ if (q == NULL) return NULL; p = mp_copy_path (mp, q); return p; } @ 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. @c static mp_knot mp_htap_ypoc (MP mp, mp_knot p) { mp_knot q, pp, qq, rr; /* for list manipulation */ q = mp_new_knot (mp); /* this will correspond to |p| */ qq = q; pp = p; while (1) { mp_right_type (qq) = mp_left_type (pp); mp_left_type (qq) = mp_right_type (pp); mp_x_coord (qq) = mp_x_coord (pp); mp_y_coord (qq) = mp_y_coord (pp); mp_right_x (qq) = mp_left_x (pp); mp_right_y (qq) = mp_left_y (pp); mp_left_x (qq) = mp_right_x (pp); mp_left_y (qq) = mp_right_y (pp); mp_originator (qq) = mp_originator (pp); if (mp_next_knot (pp) == p) { mp_next_knot (q) = qq; mp->path_tail = pp; return q; } rr = mp_new_knot (mp); mp_next_knot (rr) = qq; qq = rr; pp = mp_next_knot (pp); } } @ @<Glob...@>= mp_knot path_tail; /* 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. @<Declarations@>= static void mp_toss_knot_list (MP mp, mp_knot p); @ @c void mp_toss_knot_list (MP mp, mp_knot p) { mp_knot q; /* the node being freed */ mp_knot r; /* the next node */ (void) mp; if (p == NULL) return; q = p; do { r = mp_next_knot (q); mp_xfree (q); q = r; } while (q != p); } @* 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 |mp_left_type| and |mp_right_type| aren't both open). @c static void mp_make_choices (MP mp, mp_knot knots) { mp_knot h; /* the first breakpoint */ mp_knot p, q; /* consecutive breakpoints being processed */ @<Other local variables for |make_choices|@>; check_arith; /* make sure that |arith_error=false| */ if (internal_value (mp_tracing_choices) > 0) mp_print_path (mp, 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; do { @<Fill in the control points between |p| and the next breakpoint, then advance |p| to that breakpoint@>; } while (p != h); if (internal_value (mp_tracing_choices) > 0) mp_print_path (mp, knots, ", after choices", true); if (mp->arith_error) { @<Report an unexpected problem during the choice-making@>; } } @ @<Report an unexpected problem during the choice...@>= { 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."); mp_put_get_error (mp); mp->arith_error = false; } @ 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; do { q = mp_next_knot (p); if (mp_x_coord (p) == mp_x_coord (q) && mp_y_coord (p) == mp_y_coord (q) && mp_right_type (p) > mp_explicit) { mp_right_type (p) = mp_explicit; if (mp_left_type (p) == mp_open) { mp_left_type (p) = mp_curl; left_curl (p) = unity; } mp_left_type (q) = mp_explicit; if (mp_right_type (q) == mp_open) { mp_right_type (q) = mp_curl; right_curl (q) = unity; } mp_right_x (p) = mp_x_coord (p); mp_left_x (q) = mp_x_coord (p); mp_right_y (p) = mp_y_coord (p); mp_left_y (q) = mp_y_coord (p); } p = q; } while (p != knots) @ If there are no breakpoints, it is necessary to compute the direction angles around an entire cycle. In this case the |mp_left_type| of the first node is temporarily changed to |end_cycle|. @<Find the first breakpoint, |h|, on the path...@>= h = knots; while (1) { if (mp_left_type (h) != mp_open) break; if (mp_right_type (h) != mp_open) break; h = mp_next_knot (h); if (h == knots) { mp_left_type (h) = mp_end_cycle; break; } } @ If |mp_right_type(p)<given| and |q=mp_link(p)|, we must have |mp_right_type(p)=mp_left_type(q)=mp_explicit| or |endpoint|. @<Fill in the control points between |p| and the next breakpoint...@>= q = mp_next_knot (p); if (mp_right_type (p) >= mp_given) { while ((mp_left_type (q) == mp_open) && (mp_right_type (q) == mp_open)) q = mp_next_knot (q); @<Fill in the control information between consecutive breakpoints |p| and |q|@>; } else if (mp_right_type (p) == mp_endpoint) { @<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 |mp_left_type| and |mp_right_type| fields. @<Give reasonable values for the unused control points between |p| and~|q|@>= { mp_right_x (p) = mp_x_coord (p); mp_right_y (p) = mp_y_coord (p); mp_left_x (q) = mp_x_coord (q); mp_left_y (q) = mp_y_coord (q); } @ 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=1$, $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@>; mp_solve_choices (mp, 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...@>= int path_size; /* maximum number of knots between breakpoints of a path */ scaled *delta_x; scaled *delta_y; scaled *delta; /* knot differences */ angle *psi; /* turning angles */ @ @<Dealloc variables@>= xfree (mp->delta_x); xfree (mp->delta_y); xfree (mp->delta); xfree (mp->psi); @ @<Other local variables for |make_choices|@>= int k, n; /* current and final knot numbers */ mp_knot s, t; /* registers for list traversal */ scaled delx, dely; /* directions where |open| meets |explicit| */ fraction sine, cosine; /* trig functions of various angles */ @ @<Calculate the turning angles...@>= { RESTART: k = 0; s = p; n = mp->path_size; do { t = mp_next_knot (s); mp->delta_x[k] = mp_x_coord (t) - mp_x_coord (s); mp->delta_y[k] = mp_y_coord (t) - mp_y_coord (s); mp->delta[k] = mp_pyth_add (mp, mp->delta_x[k], mp->delta_y[k]); if (k > 0) { sine = mp_make_fraction (mp, mp->delta_y[k - 1], mp->delta[k - 1]); cosine = mp_make_fraction (mp, mp->delta_x[k - 1], mp->delta[k - 1]); mp->psi[k] = mp_n_arg (mp, mp_take_fraction (mp, mp->delta_x[k], cosine) + mp_take_fraction (mp, mp->delta_y[k], sine), mp_take_fraction (mp, mp->delta_y[k], cosine) - mp_take_fraction (mp, mp->delta_x[k], sine)); } incr (k); s = t; if (k == mp->path_size) { mp_reallocate_paths (mp, mp->path_size + (mp->path_size / 4)); goto RESTART; /* retry, loop size has changed */ } if (s == q) n = k; } while (!((k >= n) && (mp_left_type (s) != mp_end_cycle))); if (k == n) mp->psi[n] = 0; else mp->psi[k] = mp->psi[1]; } @ When we get to this point of the code, |mp_right_type(p)| is either |given| or |curl| or |open|. If it is |open|, we must have |mp_left_type(p)=mp_end_cycle| or |mp_left_type(p)=mp_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, |mp_left_type(q)| is either |given| or |curl| or |open| or |mp_end_cycle|. The |open| possibility is reduced either to |given| or to |curl|. @<Remove |open| types at the breakpoints@>= if (mp_left_type (q) == mp_open) { delx = mp_right_x (q) - mp_x_coord (q); dely = mp_right_y (q) - mp_y_coord (q); if ((delx == 0) && (dely == 0)) { mp_left_type (q) = mp_curl; left_curl (q) = unity; } else { mp_left_type (q) = mp_given; left_given (q) = mp_n_arg (mp, delx, dely); } } if ((mp_right_type (p) == mp_open) && (mp_left_type (p) == mp_explicit)) { delx = mp_x_coord (p) - mp_left_x (p); dely = mp_y_coord (p) - mp_left_y (p); if ((delx == 0) && (dely == 0)) { mp_right_type (p) = mp_curl; right_curl (p) = unity; } else { mp_right_type (p) = mp_given; right_given (p) = mp_n_arg (mp, delx, dely); } } @ 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...@>= angle *theta; /* values of $\theta_k$ */ fraction *uu; /* values of $u_k$ */ angle *vv; /* values of $v_k$ */ fraction *ww; /* values of $w_k$ */ @ @<Dealloc variables@>= xfree (mp->theta); xfree (mp->uu); xfree (mp->vv); xfree (mp->ww); @ @<Declarations@>= static void mp_reallocate_paths (MP mp, int l); @ @c void mp_reallocate_paths (MP mp, int l) { XREALLOC (mp->delta_x, l, scaled); XREALLOC (mp->delta_y, l, scaled); XREALLOC (mp->delta, l, scaled); XREALLOC (mp->psi, l, angle); XREALLOC (mp->theta, l, angle); XREALLOC (mp->uu, l, fraction); XREALLOC (mp->vv, l, angle); XREALLOC (mp->ww, l, fraction); mp->path_size = l; } @ 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. @<Declarations@>= static void mp_solve_choices (MP mp, mp_knot p, mp_knot q, halfword n); @ @c void mp_solve_choices (MP mp, mp_knot p, mp_knot q, halfword n) { int k; /* current knot number */ mp_knot r, s, t; /* registers for list traversal */ @<Other local variables for |solve_choices|@>; k = 0; s = p; r = 0; while (1) { t = mp_next_knot (s); if (k == 0) { @<Get the linear equations started; or |return| with the control points in place, if linear equations needn't be solved@> } else { switch (mp_left_type (s)) { case mp_end_cycle: case mp_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@>; break; case mp_curl: @<Set up equation for a curl at $\theta_n$ and |goto found|@>; break; case mp_given: @<Calculate the given value of $\theta_n$ and |goto found|@>; break; } /* there are no other cases */ } r = s; s = t; incr (k); } FOUND: @<Finish choosing angles and assigning control points@>; } @ 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...@>= switch (mp_right_type (s)) { case mp_given: if (mp_left_type (t) == mp_given) { @<Reduce to simple case of two givens and |return|@> } else { @<Set up the equation for a given value of $\theta_0$@>; } break; case mp_curl: if (mp_left_type (t) == mp_curl) { @<Reduce to simple case of straight line and |return|@> } else { @<Set up the equation for a curl at $\theta_0$@>; } break; case mp_open: mp->uu[0] = 0; mp->vv[0] = 0; mp->ww[0] = fraction_one; /* this begins a cycle */ break; } /* 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|@>= fraction aa, bb, cc, ff, acc; /* temporary registers */ scaled dd, ee; /* likewise, but |scaled| */ scaled lt, rt; /* tension values */ @ @<Set up equation to match mock curvatures...@>= { @<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)$@>; mp->uu[k] = mp_take_fraction (mp, ff, bb); @<Calculate the values of $v_k$ and $w_k$@>; if (mp_left_type (s) == mp_end_cycle) { @<Adjust $\theta_n$ to equal $\theta_0$ and |goto found|@>; } } @ 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) { aa = fraction_half; dd = 2 * mp->delta[k]; } else { aa = mp_make_fraction (mp, unity, 3 * abs (right_tension (r)) - unity); dd = mp_take_fraction (mp, mp->delta[k], fraction_three - mp_make_fraction (mp, unity, abs (right_tension (r)))); } if (abs (left_tension (t)) == unity) { bb = fraction_half; ee = 2 * mp->delta[k - 1]; } else { bb = mp_make_fraction (mp, unity, 3 * abs (left_tension (t)) - unity); ee = mp_take_fraction (mp, mp->delta[k - 1], fraction_three - mp_make_fraction (mp, unity, abs (left_tension (t)))); } cc = fraction_one - mp_take_fraction (mp, mp->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 = mp_take_fraction (mp, dd, cc); lt = abs (left_tension (s)); rt = abs (right_tension (s)); if (lt != rt) { /* $\beta_k^{-1}\ne\alpha_k^{-1}$ */ if (lt < rt) { ff = mp_make_fraction (mp, lt, rt); ff = mp_take_fraction (mp, ff, ff); /* $\alpha_k^2/\beta_k^2$ */ dd = mp_take_fraction (mp, dd, ff); } else { ff = mp_make_fraction (mp, rt, lt); ff = mp_take_fraction (mp, ff, ff); /* $\beta_k^2/\alpha_k^2$ */ ee = mp_take_fraction (mp, ee, ff); } } ff = mp_make_fraction (mp, 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 = -mp_take_fraction (mp, mp->psi[k + 1], mp->uu[k]); if (mp_right_type (r) == mp_curl) { mp->ww[k] = 0; mp->vv[k] = acc - mp_take_fraction (mp, mp->psi[1], fraction_one - ff); } else { ff = mp_make_fraction (mp, fraction_one - ff, cc); /* this is $B_k/(C_k+B_k-u_{k-1}A_k)<5$ */ acc = acc - mp_take_fraction (mp, mp->psi[k], ff); ff = mp_take_fraction (mp, ff, aa); /* this is $A_k/(C_k+B_k-u_{k-1}A_k)$ */ mp->vv[k] = acc - mp_take_fraction (mp, mp->vv[k - 1], ff); if (mp->ww[k - 1] == 0) mp->ww[k] = 0; else mp->ww[k] = -mp_take_fraction (mp, mp->ww[k - 1], ff); } @ 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|@>= { aa = 0; bb = fraction_one; /* we have |k=n| */ do { decr (k); if (k == 0) k = n; aa = mp->vv[k] - mp_take_fraction (mp, aa, mp->uu[k]); bb = mp->ww[k] - mp_take_fraction (mp, bb, mp->uu[k]); } while (k != n); /* now $\theta_n=\\{aa}+\\{bb}\cdot\theta_n$ */ aa = mp_make_fraction (mp, aa, fraction_one - bb); mp->theta[n] = aa; mp->vv[0] = aa; for (k = 1; k < n; k++) { mp->vv[k] = mp->vv[k] + mp_take_fraction (mp, aa, mp->ww[k]); } goto FOUND; } @ @d reduce_angle(A) if ( abs((A))>one_eighty_deg ) { if ( (A)>0 ) (A)=(A)-three_sixty_deg; else (A)=(A)+three_sixty_deg; } @<Calculate the given value of $\theta_n$...@>= { mp->theta[n] = left_given (s) - mp_n_arg (mp, mp->delta_x[n - 1], mp->delta_y[n - 1]); reduce_angle (mp->theta[n]); goto FOUND; } @ @<Set up the equation for a given value of $\theta_0$@>= { mp->vv[0] = right_given (s) - mp_n_arg (mp, mp->delta_x[0], mp->delta_y[0]); reduce_angle (mp->vv[0]); mp->uu[0] = 0; mp->ww[0] = 0; } @ @<Set up the equation for a curl at $\theta_0$@>= { cc = right_curl (s); lt = abs (left_tension (t)); rt = abs (right_tension (s)); if ((rt == unity) && (lt == unity)) mp->uu[0] = mp_make_fraction (mp, cc + cc + unity, cc + two); else mp->uu[0] = mp_curl_ratio (mp, cc, rt, lt); mp->vv[0] = -mp_take_fraction (mp, mp->psi[1], mp->uu[0]); mp->ww[0] = 0; } @ @<Set up equation for a curl at $\theta_n$...@>= { cc = left_curl (s); lt = abs (left_tension (s)); rt = abs (right_tension (r)); if ((rt == unity) && (lt == unity)) ff = mp_make_fraction (mp, cc + cc + unity, cc + two); else ff = mp_curl_ratio (mp, cc, lt, rt); mp->theta[n] = -mp_make_fraction (mp, mp_take_fraction (mp, mp->vv[n - 1], ff), fraction_one - mp_take_fraction (mp, ff, mp->uu[n - 1])); goto FOUND; } @ 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. @<Declarations@>= static fraction mp_curl_ratio (MP mp, scaled gamma, scaled a_tension, scaled b_tension); @ @c fraction mp_curl_ratio (MP mp, scaled gamma, scaled a_tension, scaled b_tension) { fraction alpha, beta, num, denom, ff; /* registers */ alpha = mp_make_fraction (mp, unity, a_tension); beta = mp_make_fraction (mp, unity, b_tension); if (alpha <= beta) { ff = mp_make_fraction (mp, alpha, beta); ff = mp_take_fraction (mp, ff, ff); gamma = mp_take_fraction (mp, gamma, ff); beta = beta / 010000; /* convert |fraction| to |scaled| */ denom = mp_take_fraction (mp, gamma, alpha) + three - beta; num = mp_take_fraction (mp, gamma, fraction_three - alpha) + beta; } else { ff = mp_make_fraction (mp, beta, alpha); ff = mp_take_fraction (mp, ff, ff); beta = mp_take_fraction (mp, beta, ff) / 010000; /* convert |fraction| to |scaled| */ denom = mp_take_fraction (mp, gamma, alpha) + (ff / 1365) - beta; /* $1365\approx 2^{12}/3$ */ num = mp_take_fraction (mp, gamma, fraction_three - alpha) + beta; } if (num >= denom + denom + denom + denom) return fraction_four; else return mp_make_fraction (mp, num, denom); } @ We're in the home stretch now. @<Finish choosing angles and assigning control points@>= for (k = n - 1; k >= 0; k--) { mp->theta[k] = mp->vv[k] - mp_take_fraction (mp, mp->theta[k + 1], mp->uu[k]); } s = p; k = 0; do { fraction n_sin; fraction n_cos; t = mp_next_knot (s); mp_n_sin_cos (mp, mp->theta[k], &n_cos, &n_sin); mp->st = n_sin; mp->ct = n_cos; mp_n_sin_cos (mp, -mp->psi[k + 1] - mp->theta[k + 1], &n_cos, &n_sin); mp->sf = n_sin; mp->cf = n_cos; mp_set_controls (mp, s, t, k); incr (k); s = t; } while (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...@>= fraction st; fraction ct; fraction sf; fraction cf; /* sines and cosines */ @ @<Declarations@>= static void mp_set_controls (MP mp, mp_knot p, mp_knot q, integer k); @ @c void mp_set_controls (MP mp, mp_knot p, mp_knot q, integer k) { fraction rr, ss; /* velocities, divided by thrice the tension */ scaled lt, rt; /* tensions */ fraction sine; /* $\sin(\theta+\phi)$ */ lt = abs (left_tension (q)); rt = abs (right_tension (p)); rr = mp_velocity (mp, mp->st, mp->ct, mp->sf, mp->cf, rt); ss = mp_velocity (mp, mp->sf, mp->cf, mp->st, mp->ct, lt); if ((right_tension (p) < 0) || (left_tension (q) < 0)) { @<Decrease the velocities, if necessary, to stay inside the bounding triangle@>; } mp_right_x (p) = mp_x_coord (p) + mp_take_fraction (mp, mp_take_fraction (mp, mp->delta_x [k], mp->ct) - mp_take_fraction (mp, mp->delta_y [k], mp-> st), rr); mp_right_y (p) = mp_y_coord (p) + mp_take_fraction (mp, mp_take_fraction (mp, mp->delta_y[k], mp->ct) + mp_take_fraction (mp, mp->delta_x[k], mp->st), rr); mp_left_x (q) = mp_x_coord (q) - mp_take_fraction (mp, mp_take_fraction (mp, mp->delta_x[k], mp->cf) + mp_take_fraction (mp, mp->delta_y[k], mp->sf), ss); mp_left_y (q) = mp_y_coord (q) - mp_take_fraction (mp, mp_take_fraction (mp, mp->delta_y[k], mp->cf) - mp_take_fraction (mp, mp->delta_x[k], mp->sf), ss); mp_right_type (p) = mp_explicit; mp_left_type (q) = mp_explicit; } @ 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.'' @<Decrease the velocities, if necessary...@>= if (((mp->st >= 0) && (mp->sf >= 0)) || ((mp->st <= 0) && (mp->sf <= 0))) { sine = mp_take_fraction (mp, abs (mp->st), mp->cf) + mp_take_fraction (mp, abs (mp->sf), mp->ct); if (sine > 0) { sine = mp_take_fraction (mp, sine, fraction_one + unity); /* safety factor */ if (right_tension (p) < 0) if (mp_ab_vs_cd (mp, abs (mp->sf), fraction_one, rr, sine) < 0) rr = mp_make_fraction (mp, abs (mp->sf), sine); if (left_tension (q) < 0) if (mp_ab_vs_cd (mp, abs (mp->st), fraction_one, ss, sine) < 0) ss = mp_make_fraction (mp, abs (mp->st), sine); } } @ Only the simple cases remain to be handled. @<Reduce to simple case of two givens and |return|@>= { fraction n_sin; fraction n_cos; aa = mp_n_arg (mp, mp->delta_x[0], mp->delta_y[0]); mp_n_sin_cos (mp, right_given (p) - aa, &n_cos, &n_sin); mp->ct = n_cos; mp->st = n_sin; mp_n_sin_cos (mp, left_given (q) - aa, &n_cos, &n_sin); mp->cf = n_cos; mp->sf = -n_sin; mp_set_controls (mp, p, q, 0); return; } @ @<Reduce to simple case of straight line and |return|@>= { mp_right_type (p) = mp_explicit; mp_left_type (q) = mp_explicit; lt = abs (left_tension (q)); rt = abs (right_tension (p)); if (rt == unity) { if (mp->delta_x[0] >= 0) mp_right_x (p) = mp_x_coord (p) + ((mp->delta_x[0] + 1) / 3); else mp_right_x (p) = mp_x_coord (p) + ((mp->delta_x[0] - 1) / 3); if (mp->delta_y[0] >= 0) mp_right_y (p) = mp_y_coord (p) + ((mp->delta_y[0] + 1) / 3); else mp_right_y (p) = mp_y_coord (p) + ((mp->delta_y[0] - 1) / 3); } else { ff = mp_make_fraction (mp, unity, 3 * rt); /* $\alpha/3$ */ mp_right_x (p) = mp_x_coord (p) + mp_take_fraction (mp, mp->delta_x[0], ff); mp_right_y (p) = mp_y_coord (p) + mp_take_fraction (mp, mp->delta_y[0], ff); } if (lt == unity) { if (mp->delta_x[0] >= 0) mp_left_x (q) = mp_x_coord (q) - ((mp->delta_x[0] + 1) / 3); else mp_left_x (q) = mp_x_coord (q) - ((mp->delta_x[0] - 1) / 3); if (mp->delta_y[0] >= 0) mp_left_y (q) = mp_y_coord (q) - ((mp->delta_y[0] + 1) / 3); else mp_left_y (q) = mp_y_coord (q) - ((mp->delta_y[0] - 1) / 3); } else { ff = mp_make_fraction (mp, unity, 3 * lt); /* $\beta/3$ */ mp_left_x (q) = mp_x_coord (q) - mp_take_fraction (mp, mp->delta_x[0], ff); mp_left_y (q) = mp_y_coord (q) - mp_take_fraction (mp, mp->delta_y[0], ff); } return; } @* 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 { return (fraction_one+1); } @d one_crossing { return fraction_one; } @d zero_crossing { return 0; } @c static fraction mp_crossing_point (MP mp, integer a, integer b, integer c) { integer d; /* recursive counter */ integer x, xx, x0, x1, x2; /* temporary registers for bisection */ if (a < 0) zero_crossing; if (c >= 0) { if (b >= 0) { if (c > 0) { no_crossing; } else if ((a == 0) && (b == 0)) { no_crossing; } else { one_crossing; } } if (a == 0) zero_crossing; } else if (a == 0) { if (b <= 0) zero_crossing; } @<Use bisection to find the crossing point, if one exists@>; } @ 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; do { x = half (x1 + x2); if (x1 - x0 > x0) { x2 = x; x0 += x0; d += d; } else { xx = x1 + x - x0; if (xx > x0) { x2 = x; x0 += x0; d += d; } else { x0 = x0 - xx; if (x <= x0) { if (x + x2 <= x0) no_crossing; } x1 = x; d = d + d + 1; } } } while (d < fraction_one); return (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(A,B) ((A)-mp_take_fraction(mp,((A)-(B)),t)) @c static scaled mp_eval_cubic (MP mp, mp_knot p, mp_knot q, quarterword c, fraction t) { scaled x1, x2, x3; /* intermediate values */ if (c == mp_x_code) { x1 = t_of_the_way (mp_x_coord (p), mp_right_x (p)); x2 = t_of_the_way (mp_right_x (p), mp_left_x (q)); x3 = t_of_the_way (mp_left_x (q), mp_x_coord (q)); } else { x1 = t_of_the_way (mp_y_coord (p), mp_right_y (p)); x2 = t_of_the_way (mp_right_y (p), mp_left_y (q)); x3 = t_of_the_way (mp_left_y (q), mp_y_coord (q)); } x1 = t_of_the_way (x1, x2); x2 = t_of_the_way (x2, x3); return t_of_the_way (x1, x2); } @ 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. @<Types...@>= enum mp_bb_code { mp_x_code = 0, /* index for |minx| and |maxx| */ mp_y_code /* index for |miny| and |maxy| */ }; @ @d mp_minx mp->bbmin[mp_x_code] @d mp_maxx mp->bbmax[mp_x_code] @d mp_miny mp->bbmin[mp_y_code] @d mp_maxy mp->bbmax[mp_y_code] @<Glob...@>= scaled bbmin[mp_y_code + 1]; scaled bbmax[mp_y_code + 1]; /* 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|. @c static void mp_bound_cubic (MP mp, mp_knot p, mp_knot q, quarterword c) { boolean wavy; /* whether we need to look for extremes */ scaled del1, del2, del3, del, dmax; /* proportional to the control points of a quadratic derived from a cubic */ fraction t, tt; /* where a quadratic crosses zero */ scaled x; /* a value that |bbmin[c]| and |bbmax[c]| must accommodate */ x = (c == mp_x_code ? mp_x_coord (q) : mp_y_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) { if (c == mp_x_code) { del1 = mp_right_x (p) - mp_x_coord (p); del2 = mp_left_x (q) - mp_right_x (p); del3 = mp_x_coord (q) - mp_left_x (q); } else { del1 = mp_right_y (p) - mp_y_coord (p); del2 = mp_left_y (q) - mp_right_y (p); del3 = mp_y_coord (q) - mp_left_y (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) { negate (del1); negate (del2); negate (del3); }; t = mp_crossing_point (mp, del1, del2, del3); if (t < fraction_one) { @<Test the extremes of the cubic against the bounding box@>; } } } @ @<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@>= if (x < mp->bbmin[c]) mp->bbmin[c] = x; if (x > mp->bbmax[c]) mp->bbmax[c] = x @ @<Check the control points against the bounding box and set...@>= wavy = true; if (c == mp_x_code) { if (mp->bbmin[c] <= mp_right_x (p)) if (mp_right_x (p) <= mp->bbmax[c]) if (mp->bbmin[c] <= mp_left_x (q)) if (mp_left_x (q) <= mp->bbmax[c]) wavy = false; } else { if (mp->bbmin[c] <= mp_right_y (p)) if (mp_right_y (p) <= mp->bbmax[c]) if (mp->bbmin[c] <= mp_left_y (q)) if (mp_left_y (q) <= mp->bbmax[c]) 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) del = del1; else if (del2 != 0) del = del2; else del = del3; if (del != 0) { dmax = abs (del1); if (abs (del2) > dmax) dmax = abs (del2); if (abs (del3) > dmax) dmax = abs (del3); while (dmax < fraction_half) { dmax += dmax; del1 += del1; del2 += del2; del3 += del3; } } @ 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@>= { x = mp_eval_cubic (mp, p, q, c, 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) del2 = 0; tt = mp_crossing_point (mp, 0, -del2, -del3); if (tt < fraction_one) { @<Test the second extreme against the bounding box@>; } } @ @<Test the second extreme against the bounding box@>= { x = mp_eval_cubic (mp, p, q, c, t_of_the_way (tt, fraction_one)); @<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@>; } @ Finding the bounding box of a path is basically a matter of applying |bound_cubic| twice for each pair of adjacent knots. @c static void mp_path_bbox (MP mp, mp_knot h) { mp_knot p, q; /* a pair of adjacent knots */ mp_minx = mp_x_coord (h); mp_miny = mp_y_coord (h); mp_maxx = mp_minx; mp_maxy = mp_miny; p = h; do { if (mp_right_type (p) == mp_endpoint) return; q = mp_next_knot (p); mp_bound_cubic (mp, p, q, mp_x_code); mp_bound_cubic (mp, p, q, mp_y_code); p = q; } while (p != h); } @ 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|. @c static scaled mp_arc_test (MP mp, scaled dx0, scaled dy0, scaled dx1, scaled dy1, scaled dx2, scaled dy2, scaled v0, scaled v02, scaled v2, scaled a_goal, scaled tol) { boolean simple; /* are the control points confined to a $90^\circ$ sector? */ scaled dx01, dy01, dx12, dy12, dx02, dy02; /* bisection results */ scaled v002, v022; /* twice the velocity magnitudes at $t={1\over4}$ and $t={3\over4}$ */ scaled arc; /* best arc length estimate before recursion */ @<Other local variables in |arc_test|@>; @<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 && (abs (arc - v02 - halfp (v0 + v2)) <= tol)) { if (arc < a_goal) { return 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@>; } } @ 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@>= { @<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 = mp_arc_test (mp, dx0, dy0, dx01, dy01, dx02, dy02, v0, v002, halfp (v02), a_new, tol); if (a < 0) { return (-halfp (two - a)); } else { @<Update |a_new| to reduce |a_new+a_aux| by |a|@>; b = mp_arc_test (mp, dx02, dy02, dx12, dy12, dx2, dy2, halfp (v02), v022, v2, a_new, tol); if (b < 0) return (-halfp (-b) - half_unit); else return (a + half (b - a)); } } @ @<Other local variables in |arc_test|@>= scaled a, b; /* results of recursive calls */ scaled a_new, a_aux; /* 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) { a_aux = a_goal - a_aux; a_new = EL_GORDO; } else { a_new = a_goal + a_goal; a_aux = 0; } @ 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) { a_aux = a_aux - a; a_new = a_new + a_aux; } @ 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 = mp_pyth_add (mp, dx01 + half (dx0 + dx02), dy01 + half (dy0 + dy02)); v022 = mp_pyth_add (mp, 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)) { arc = arc + arc1; } else { mp->arith_error = true; if (a_goal == EL_GORDO) return (EL_GORDO); else return (-two); } @ @<Other local variables in |arc_test|@>= scaled tmp, tmp2; /* all purpose temporary registers */ scaled arc1; /* arc length estimate for the first half */ @ @<Test if the control points are confined to one quadrant or rotating...@>= simple = ((dx0 >= 0) && (dx1 >= 0) && (dx2 >= 0)) || ((dx0 <= 0) && (dx1 <= 0) && (dx2 <= 0)); if (simple) simple = ((dy0 >= 0) && (dy1 >= 0) && (dy2 >= 0)) || ((dy0 <= 0) && (dy1 <= 0) && (dy2 <= 0)); if (!simple) { simple = ((dx0 >= dy0) && (dx1 >= dy1) && (dx2 >= dy2)) || ((dx0 <= dy0) && (dx1 <= dy1) && (dx2 <= dy2)); if (simple) simple = ((-dx0 >= dy0) && (-dx1 >= dy1) && (-dx2 >= dy2)) || ((-dx0 <= dy0) && (-dx1 <= dy1) && (-dx2 <= dy2)); } @ 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...@>= { tmp = (v02 + 2) / 4; if (a_goal <= arc1) { tmp2 = halfp (v0); return (halfp (mp_solve_rising_cubic (mp, tmp2, arc1 - tmp2 - tmp, tmp, a_goal)) - two); } else { tmp2 = halfp (v2); return ((half_unit - two) + halfp (mp_solve_rising_cubic (mp, tmp, arc - arc1 - tmp - tmp2, tmp2, a_goal - arc1))); } } @ 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. @<Declarations@>= static scaled mp_solve_rising_cubic (MP mp, scaled a, scaled b, scaled c, scaled x); @ @c scaled mp_solve_rising_cubic (MP mp, scaled a, scaled b, scaled c, scaled x) { scaled ab, bc, ac; /* bisection results */ integer t; /* $2^k+q$ where unscaled answer is in $[q2^{-k},(q+1)2^{-k})$ */ integer xx; /* temporary for updating |x| */ if ((a < 0) || (c < 0)) mp_confusion (mp, "rising?"); @:this can't happen rising?}{\quad rising?@>; if (x <= 0) { return 0; } else if (x >= a + b + c) { return unity; } else { t = 1; @<Rescale if necessary to make sure |a|, |b|, and |c| are all less than |EL_GORDO div 3|@>; do { t += t; @<Subdivide the B\'ezier quadratic defined by |a|, |b|, |c|@>; xx = x - a - ab - ac; if (xx < -x) { x += x; b = ab; c = ac; } else { x = x + xx; a = ac; b = bc; t = t + 1; }; } while (t < unity); return (t - unity); } } @ @<Subdivide the B\'ezier quadratic defined by |a|, |b|, |c|@>= ab = half (a + b); bc = half (b + c); ac = half (ab + bc) @ The upper bound on |a|, |b|, and |c|: @d one_third_EL_GORDO ((math_data *)mp->math)->one_third_max_scaled_ @<Rescale if necessary to make sure |a|, |b|, and |c| are all less than...@>= while ((a > one_third_EL_GORDO) || (b > one_third_EL_GORDO) || (c > one_third_EL_GORDO)) { a = halfp (a); b = half (b); c = halfp (c); x = halfp (x); } @ 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 */ @c static scaled mp_do_arc_test (MP mp, scaled dx0, scaled dy0, scaled dx1, scaled dy1, scaled dx2, scaled dy2, scaled a_goal) { scaled v0, v1, v2; /* length of each $({\it dx},{\it dy})$ pair */ scaled v02; /* twice the norm of the quadratic at $t={1\over2}$ */ v0 = mp_pyth_add (mp, dx0, dy0); v1 = mp_pyth_add (mp, dx1, dy1); v2 = mp_pyth_add (mp, dx2, dy2); if ((v0 >= fraction_four) || (v1 >= fraction_four) || (v2 >= fraction_four)) { mp->arith_error = true; if (a_goal == EL_GORDO) return EL_GORDO; else return (-two); } else { v02 = mp_pyth_add (mp, dx1 + half (dx0 + dx2), dy1 + half (dy0 + dy2)); return (mp_arc_test (mp, dx0, dy0, dx1, dy1, dx2, dy2, v0, v02, v2, a_goal, arc_tol)); } } @ Now it is easy to find the arc length of an entire path. @c static scaled mp_get_arc_length (MP mp, mp_knot h) { mp_knot p, q; /* for traversing the path */ scaled a, a_tot; /* current and total arc lengths */ a_tot = 0; p = h; while (mp_right_type (p) != mp_endpoint) { q = mp_next_knot (p); a = mp_do_arc_test (mp, mp_right_x (p) - mp_x_coord (p), mp_right_y (p) - mp_y_coord (p), mp_left_x (q) - mp_right_x (p), mp_left_y (q) - mp_right_y (p), mp_x_coord (q) - mp_left_x (q), mp_y_coord (q) - mp_left_y (q), EL_GORDO); a_tot = mp_slow_add (mp, a, a_tot); if (q == h) break; else p = q; } check_arith; return a_tot; } @ 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. @c static scaled mp_get_arc_time (MP mp, mp_knot h, scaled arc0) { mp_knot p, q; /* for traversing the path */ scaled t_tot; /* accumulator for the result */ scaled t; /* the result of |do_arc_test| */ scaled arc; /* portion of |arc0| not used up so far */ integer n; /* number of extra times to go around the cycle */ if (arc0 < 0) { @<Deal with a negative |arc0| value and |return|@>; } if (arc0 == EL_GORDO) decr (arc0); t_tot = 0; arc = arc0; p = h; while ((mp_right_type (p) != mp_endpoint) && (arc > 0)) { q = mp_next_knot (p); t = mp_do_arc_test (mp, mp_right_x (p) - mp_x_coord (p), mp_right_y (p) - mp_y_coord (p), mp_left_x (q) - mp_right_x (p), mp_left_y (q) - mp_right_y (p), mp_x_coord (q) - mp_left_x (q), mp_y_coord (q) - mp_left_y (q), arc); @<Update |arc| and |t_tot| after |do_arc_test| has just returned |t|@>; if (q == h) { @<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; } check_arith; return t_tot; } @ @<Update |arc| and |t_tot| after |do_arc_test| has just returned |t|@>= if (t < 0) { t_tot = t_tot + t + two; arc = 0; } else { t_tot = t_tot + unity; arc = arc - t; } @ @<Deal with a negative |arc0| value and |return|@>= { if (mp_left_type (h) == mp_endpoint) { t_tot = 0; } else { p = mp_htap_ypoc (mp, h); t_tot = -mp_get_arc_time (mp, p, -arc0); mp_toss_knot_list (mp, p); } check_arith; return t_tot; } @ @<Update |t_tot| and |arc| to avoid going around the cyclic...@>= if (arc > 0) { n = arc / (arc0 - arc); arc = arc - n * (arc0 - arc); if (t_tot > (EL_GORDO / (n + 1))) { mp->arith_error = true; check_arith; return EL_GORDO; } t_tot = (n + 1) * t_tot; } @* 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 |mp_left_type| or |mp_right_type| fields. In fact, we don't need the |mp_left_x|, |mp_left_y|, |mp_right_x|, or |mp_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|. @ The |make_pen| procedure turns a path into a pen by initializing the |prev_knot| 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(A) mp_make_pen(mp, mp_copy_path(mp, (A)),false) @c static mp_knot mp_make_pen (MP mp, mp_knot h, boolean need_hull) { mp_knot p, q; /* two consecutive knots */ q = h; do { p = q; q = mp_next_knot (q); mp_prev_knot (q) = p; } while (q != h); if (need_hull) { h = mp_convex_hull (mp, h); @<Make sure |h| isn't confused with an elliptical pen@>; } return h; } @ 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(A) ((A)==mp_next_knot((A))) @c static mp_knot mp_get_pen_circle (MP mp, scaled diam) { mp_knot h; /* the knot node to return */ h = mp_new_knot (mp); mp_next_knot (h) = h; mp_prev_knot (h) = h; mp_originator (h) = mp_program_code; mp_x_coord (h) = 0; mp_y_coord (h) = 0; mp_left_x (h) = diam; mp_left_y (h) = 0; mp_right_x (h) = 0; mp_right_y (h) = diam; return h; } @ 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 |mp_left_x|, |mp_left_y|, |mp_right_x|, and |mp_right_y| fields. @<Make sure |h| isn't confused with an elliptical pen@>= if (pen_is_elliptical (h)) { mp_left_x (h) = mp_x_coord (h); mp_left_y (h) = mp_y_coord (h); mp_right_x (h) = mp_x_coord (h); mp_right_y (h) = mp_y_coord (h); } @ Printing a polygonal pen is very much like printing a path @<Declarations@>= static void mp_pr_pen (MP mp, mp_knot h); @ @c void mp_pr_pen (MP mp, mp_knot h) { mp_knot p, q; /* for list traversal */ if (pen_is_elliptical (h)) { @<Print the elliptical pen |h|@>; } else { p = h; do { mp_print_two (mp, mp_x_coord (p), mp_y_coord (p)); mp_print_nl (mp, " .. "); @<Advance |p| making sure the links are OK and |return| if there is a problem@>; } while (p != h); mp_print (mp, "cycle"); } } @ @<Advance |p| making sure the links are OK and |return| if there is...@>= q = mp_next_knot (p); if ((q == NULL) || (mp_prev_knot (q) != p)) { mp_print_nl (mp, "???"); return; /* this won't happen */ @.???@> } p = q @ @<Print the elliptical pen |h|@>= { mp_print (mp, "pencircle transformed ("); mp_print_scaled (mp, mp_x_coord (h)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, mp_y_coord (h)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, mp_left_x (h) - mp_x_coord (h)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, mp_right_x (h) - mp_x_coord (h)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, mp_left_y (h) - mp_y_coord (h)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, mp_right_y (h) - mp_y_coord (h)); mp_print_char (mp, xord (')')); } @ Here us another version of |pr_pen| that prints the pen as a diagnostic message. @<Declarations@>= static void mp_print_pen (MP mp, mp_knot h, const char *s, boolean nuline); @ @c void mp_print_pen (MP mp, mp_knot h, const char *s, boolean nuline) { mp_print_diagnostic (mp, "Pen", s, nuline); mp_print_ln (mp); @.Pen at line...@>; mp_pr_pen (mp, h); mp_end_diagnostic (mp, true); } @ Making a polygonal pen into a path involves restoring the |mp_left_type| and |mp_right_type| fields and setting the control points so as to make a polygonal path. @c static void mp_make_path (MP mp, mp_knot h) { mp_knot p; /* for traversing the knot list */ quarterword k; /* a loop counter */ @<Other local variables in |make_path|@>; if (pen_is_elliptical (h)) { @<Make the elliptical pen |h| into a path@>; } else { p = h; do { mp_left_type (p) = mp_explicit; mp_right_type (p) = mp_explicit; @<copy the coordinates of knot |p| into its control points@>; p = mp_next_knot (p); } while (p != h); } } @ @<copy the coordinates of knot |p| into its control points@>= mp_left_x (p) = mp_x_coord (p); mp_left_y (p) = mp_y_coord (p); mp_right_x (p) = mp_x_coord (p); mp_right_y (p) = mp_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@>= { @<Extract the transformation parameters from the elliptical pen~|h|@>; p = h; for (k = 0; k <= 7; k++) { @<Initialize |p| as the |k|th knot of a circle of unit diameter, transforming it appropriately@>; if (k == 7) mp_next_knot (p) = h; else mp_next_knot (p) = mp_new_knot (mp); p = mp_next_knot (p); } } @ @<Extract the transformation parameters from the elliptical pen~|h|@>= center_x = mp_x_coord (h); center_y = mp_y_coord (h); width_x = mp_left_x (h) - center_x; width_y = mp_left_y (h) - center_y; height_x = mp_right_x (h) - center_x; height_y = mp_right_y (h) - center_y @ @<Other local variables in |make_path|@>= scaled center_x, center_y; /* translation parameters for an elliptical pen */ scaled width_x, width_y; /* the effect of a unit change in $x$ */ scaled height_x, height_y; /* the effect of a unit change in $y$ */ scaled dx, dy; /* the vector from knot |p| to its right control point */ integer kk; /* |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) % 8; mp_x_coord (p) = center_x + mp_take_fraction (mp, mp->half_cos[k], width_x) + mp_take_fraction (mp, mp->half_cos[kk], height_x); mp_y_coord (p) = center_y + mp_take_fraction (mp, mp->half_cos[k], width_y) + mp_take_fraction (mp, mp->half_cos[kk], height_y); dx = -mp_take_fraction (mp, mp->d_cos[kk], width_x) + mp_take_fraction (mp, mp->d_cos[k], height_x); dy = -mp_take_fraction (mp, mp->d_cos[kk], width_y) + mp_take_fraction (mp, mp->d_cos[k], height_y); mp_right_x (p) = mp_x_coord (p) + dx; mp_right_y (p) = mp_y_coord (p) + dy; mp_left_x (p) = mp_x_coord (p) - dx; mp_left_y (p) = mp_y_coord (p) - dy; mp_left_type (p) = mp_explicit; mp_right_type (p) = mp_explicit; mp_originator (p) = mp_program_code @ @<Glob...@>= fraction half_cos[8]; /* ${1\over2}\cos(45k)$ */ fraction d_cos[8]; /* 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...@>= mp->half_cos[0] = fraction_half; mp->half_cos[1] = 94906266; /* $2^{26}\sqrt2\approx94906265.62$ */ mp->half_cos[2] = 0; mp->d_cos[0] = 35596755; /* $2^{28}d\approx35596754.69$ */ mp->d_cos[1] = 25170707; /* $2^{27}\sqrt2\,d\approx25170706.63$ */ mp->d_cos[2] = 0; for (k = 3; k <= 4; k++) { mp->half_cos[k] = -mp->half_cos[4 - k]; mp->d_cos[k] = -mp->d_cos[4 - k]; } for (k = 5; k <= 7; k++) { mp->half_cos[k] = mp->half_cos[8 - k]; mp->d_cos[k] = mp->d_cos[8 - k]; } @ 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]. @<Declarations@>= static mp_knot mp_convex_hull (MP mp, mp_knot h); @ @c mp_knot mp_convex_hull (MP mp, mp_knot h) { /* Make a polygonal pen convex */ mp_knot l, r; /* the leftmost and rightmost knots */ mp_knot p, q; /* knots being scanned */ mp_knot s; /* the starting point for an upcoming scan */ scaled dx, dy; /* a temporary pointer */ if (pen_is_elliptical (h)) { return h; } else { @<Set |l| to the leftmost knot in polygon~|h|@>; @<Set |r| to the rightmost knot in polygon~|h|@>; if (l != r) { s = mp_next_knot (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$@>; } if (l != mp_next_knot (l)) { @<Do a Gramm scan and remove vertices where there is no left turn@>; } return l; } } @ All comparisons are done primarily on $x$ and secondarily on $y$. @<Set |l| to the leftmost knot in polygon~|h|@>= l = h; p = mp_next_knot (h); while (p != h) { if (mp_x_coord (p) <= mp_x_coord (l)) if ((mp_x_coord (p) < mp_x_coord (l)) || (mp_y_coord (p) < mp_y_coord (l))) l = p; p = mp_next_knot (p); } @ @<Set |r| to the rightmost knot in polygon~|h|@>= r = h; p = mp_next_knot (h); while (p != h) { if (mp_x_coord (p) >= mp_x_coord (r)) if ((mp_x_coord (p) > mp_x_coord (r)) || (mp_y_coord (p) > mp_y_coord (r))) r = p; p = mp_next_knot (p); } @ @<Find any knots on the path from |l| to |r| above the |l|-|r| line...@>= dx = mp_x_coord (r) - mp_x_coord (l); dy = mp_y_coord (r) - mp_y_coord (l); p = mp_next_knot (l); while (p != r) { q = mp_next_knot (p); if (mp_ab_vs_cd (mp, dx, mp_y_coord (p) - mp_y_coord (l), dy, mp_x_coord (p) - mp_x_coord (l)) > 0) mp_move_knot (mp, p, r); p = q; } @ The |move_knot| procedure removes |p| from a doubly linked list and inserts it after |q|. @ @<Declarations@>= static void mp_move_knot (MP mp, mp_knot p, mp_knot q); @ @c void mp_move_knot (MP mp, mp_knot p, mp_knot q) { (void) mp; mp_next_knot (mp_prev_knot (p)) = mp_next_knot (p); mp_prev_knot (mp_next_knot (p)) = mp_prev_knot (p); mp_prev_knot (p) = q; mp_next_knot (p) = mp_next_knot (q); mp_next_knot (q) = p; mp_prev_knot (mp_next_knot (p)) = p; } @ @<Find any knots on the path from |s| to |l| below the |l|-|r| line...@>= p = s; while (p != l) { q = mp_next_knot (p); if (mp_ab_vs_cd (mp, dx, mp_y_coord (p) - mp_y_coord (l), dy, mp_x_coord (p) - mp_x_coord (l)) < 0) mp_move_knot (mp, p, l); p = q; } @ 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 = mp_next_knot (l); while (p != r) { q = mp_prev_knot (p); while (mp_x_coord (q) > mp_x_coord (p)) q = mp_prev_knot (q); while (mp_x_coord (q) == mp_x_coord (p)) { if (mp_y_coord (q) > mp_y_coord (p)) q = mp_prev_knot (q); else break; } if (q == mp_prev_knot (p)) { p = mp_next_knot (p); } else { p = mp_next_knot (p); mp_move_knot (mp, mp_prev_knot (p), q); } } @ @<Sort the path from |r| to |l| by decreasing $x$@>= p = mp_next_knot (r); while (p != l) { q = mp_prev_knot (p); while (mp_x_coord (q) < mp_x_coord (p)) q = mp_prev_knot (q); while (mp_x_coord (q) == mp_x_coord (p)) { if (mp_y_coord (q) < mp_y_coord (p)) q = mp_prev_knot (q); else break; } if (q == mp_prev_knot (p)) { p = mp_next_knot (p); } else { p = mp_next_knot (p); mp_move_knot (mp, mp_prev_knot (p), q); } } @ 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...@>= { p = l; q = mp_next_knot (l); while (1) { dx = mp_x_coord (q) - mp_x_coord (p); dy = mp_y_coord (q) - mp_y_coord (p); p = q; q = mp_next_knot (q); if (p == l) break; if (p != r) if (mp_ab_vs_cd (mp, dx, mp_y_coord (q) - mp_y_coord (p), dy, mp_x_coord (q) - mp_x_coord (p)) <= 0) { @<Remove knot |p| and back up |p| and |q| but don't go past |l|@>; } } } @ @<Remove knot |p| and back up |p| and |q| but don't go past |l|@>= { s = mp_prev_knot (p); mp_xfree (p); mp_next_knot (s) = q; mp_prev_knot (q) = s; if (s == l) { p = s; } else { p = mp_prev_knot (s); q = s; } } @ 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. @c static void mp_find_offset (MP mp, scaled x, scaled y, mp_knot h) { mp_knot p, q; /* consecutive knots */ scaled wx, wy, hx, hy; /* the transformation matrix for an elliptical pen */ fraction xx, yy; /* untransformed offset for an elliptical pen */ fraction d; /* a temporary register */ if (pen_is_elliptical (h)) { @<Find the offset for |(x,y)| on the elliptical pen~|h|@> } else { q = h; do { p = q; q = mp_next_knot (q); } while (! (mp_ab_vs_cd (mp, mp_x_coord (q) - mp_x_coord (p), y, mp_y_coord (q) - mp_y_coord (p), x) >= 0)); do { p = q; q = mp_next_knot (q); } while (! (mp_ab_vs_cd (mp, mp_x_coord (q) - mp_x_coord (p), y, mp_y_coord (q) - mp_y_coord (p), x) <= 0)); mp->cur_x = mp_x_coord (p); mp->cur_y = mp_y_coord (p); } } @ @<Glob...@>= scaled cur_x; scaled cur_y; /* all-purpose return value registers */ @ @<Find the offset for |(x,y)| on the elliptical pen~|h|@>= if ((x == 0) && (y == 0)) { mp->cur_x = mp_x_coord (h); mp->cur_y = mp_y_coord (h); } else { @<Find the non-constant part of the transformation for |h|@>; while ((abs (x) < fraction_half) && (abs (y) < fraction_half)) { x += x; y += y; }; @<Make |(xx,yy)| the offset on the untransformed \&{pencircle} for the untransformed version of |(x,y)|@>; mp->cur_x = mp_x_coord (h) + mp_take_fraction (mp, xx, wx) + mp_take_fraction (mp, yy, hx); mp->cur_y = mp_y_coord (h) + mp_take_fraction (mp, xx, wy) + mp_take_fraction (mp, yy, hy); } @ @<Find the non-constant part of the transformation for |h|@>= wx = mp_left_x (h) - mp_x_coord (h); wy = mp_left_y (h) - mp_y_coord (h); hx = mp_right_x (h) - mp_x_coord (h); hy = mp_right_y (h) - mp_y_coord (h) @ @<Make |(xx,yy)| the offset on the untransformed \&{pencircle} for the...@>= yy = -(mp_take_fraction (mp, x, hy) + mp_take_fraction (mp, y, -hx)); xx = mp_take_fraction (mp, x, -wy) + mp_take_fraction (mp, y, wx); d = mp_pyth_add (mp, xx, yy); if (d > 0) { xx = half (mp_make_fraction (mp, xx, d)); yy = half (mp_make_fraction (mp, yy, d)); } @ 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|. @c static void mp_pen_bbox (MP mp, mp_knot h) { mp_knot p; /* for scanning the knot list */ if (pen_is_elliptical (h)) { @<Find the bounding box of an elliptical pen@>; } else { mp_minx = mp_x_coord (h); mp_maxx = mp_minx; mp_miny = mp_y_coord (h); mp_maxy = mp_miny; p = mp_next_knot (h); while (p != h) { if (mp_x_coord (p) < mp_minx) mp_minx = mp_x_coord (p); if (mp_y_coord (p) < mp_miny) mp_miny = mp_y_coord (p); if (mp_x_coord (p) > mp_maxx) mp_maxx = mp_x_coord (p); if (mp_y_coord (p) > mp_maxy) mp_maxy = mp_y_coord (p); p = mp_next_knot (p); } } } @ @<Find the bounding box of an elliptical pen@>= { mp_find_offset (mp, 0, fraction_one, h); mp_maxx = mp->cur_x; mp_minx = 2 * mp_x_coord (h) - mp->cur_x; mp_find_offset (mp, -fraction_one, 0, h); mp_maxy = mp->cur_y; mp_miny = 2 * mp_y_coord (h) - mp->cur_y; } @* 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. @(mplib.h@>= enum mp_graphical_object_code { @<Graphical object codes@> mp_final_graphic }; @ Let's consider the types of graphical objects one at a time. First of all, a filled contour is represented by a eight-word node. The first word contains |type| and |link| fields, and the next six 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 mp_path_p(A) (A)->path_p_ /* a pointer to the path that needs filling */ @d mp_pen_p(A) (A)->pen_p_ /* a pointer to the pen to fill or stroke with */ @d mp_color_model(A) ((mp_fill_node)(A))->color_model_ /* the color model */ @d red_val(A) ((mp_fill_node)(A))->red_val_ /* the red component of the color in the range $0\ldots1$ */ @d cyan_val red_val @d grey_val red_val @d green_val(A) ((mp_fill_node)(A))->green_val_ /* the green component of the color in the range $0\ldots1$ */ @d magenta_val green_val @d blue_val(A) ((mp_fill_node)(A))->blue_val_ /* the blue component of the color in the range $0\ldots1$ */ @d yellow_val blue_val @d black_val(A) ((mp_fill_node)(A))->black_val_ /* the black component of the color in the range $0\ldots1$ */ @d ljoin_val(A) ((mp_fill_node)(A))->ljoin_val_ /* the value of \&{linejoin} */ @:mp_linejoin_}{\&{linejoin} primitive@> @d miterlim_val(A) ((mp_fill_node)(A))->miterlim_val_ /* the value of \&{miterlimit} */ @:mp_miterlimit_}{\&{miterlimit} primitive@> @d mp_pre_script(A) ((mp_fill_node)(A))->pre_script_ @d mp_post_script(A) ((mp_fill_node)(A))->post_script_ @(mpmp.h@>= typedef struct mp_fill_node_data { NODE_BODY; halfword color_model_; scaled red_val_; scaled green_val_; scaled blue_val_; scaled black_val_; str_number pre_script_; str_number post_script_; mp_knot path_p_; mp_knot pen_p_; quarterword ljoin_val_; halfword miterlim_val_; } mp_fill_node_data; typedef struct mp_fill_node_data *mp_fill_node; @ @<Graphical object codes@>= mp_fill_code = 1, @ Make a fill node for cyclic path |p| and color black. @d fill_node_size sizeof(struct mp_fill_node_data) @c static mp_node mp_new_fill_node (MP mp, mp_knot p) { mp_fill_node t = xmalloc (1, fill_node_size); add_var_used (fill_node_size); memset (t, 0, fill_node_size); mp_type (t) = mp_fill_node_type; mp_path_p (t) = p; mp_pen_p (t) = NULL; /* |NULL| means don't use a pen */ red_val (t) = 0; green_val (t) = 0; blue_val (t) = 0; black_val (t) = 0; mp_color_model (t) = mp_uninitialized_model; mp_pre_script (t) = NULL; mp_post_script (t) = NULL; @<Set the |ljoin_val| and |miterlim_val| fields in object |t|@>; return (mp_node) t; } @ @<Set the |ljoin_val| and |miterlim_val| fields in object |t|@>= if (internal_value (mp_linejoin) > unity) ljoin_val (t) = 2; else if (internal_value (mp_linejoin) > 0) ljoin_val (t) = 1; else ljoin_val (t) = 0; if (internal_value (mp_miterlimit) < unity) miterlim_val (t) = unity; else miterlim_val (t) = internal_value (mp_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 mp_dash_p(A) ((mp_stroked_node)(A))->dash_p_ /* a pointer to the edge structure that gives the dash pattern */ @d lcap_val(A) ((mp_stroked_node)(A))->lcap_val_ /* the value of \&{linecap} */ @:mp_linecap_}{\&{linecap} primitive@> @d dash_scale(A) ((mp_stroked_node)(A))->dash_scale_ /* dash lengths are scaled by this factor */ @(mpmp.h@>= typedef struct mp_stroked_node_data { NODE_BODY; halfword color_model_; scaled red_val_; scaled green_val_; scaled blue_val_; scaled black_val_; str_number pre_script_; str_number post_script_; mp_knot path_p_; mp_knot pen_p_; quarterword ljoin_val_; halfword miterlim_val_; quarterword lcap_val_; mp_node dash_p_; scaled dash_scale_; } mp_stroked_node_data; typedef struct mp_stroked_node_data *mp_stroked_node; @ @<Graphical object codes@>= mp_stroked_code = 2, @ Make a stroked node for path |p| with |mp_pen_p(p)| temporarily |NULL|. @d stroked_node_size sizeof(struct mp_stroked_node_data) @c static mp_node mp_new_stroked_node (MP mp, mp_knot p) { mp_stroked_node t = (mp_stroked_node) xmalloc (1, stroked_node_size); add_var_used (stroked_node_size); memset (t, 0, stroked_node_size); mp_type (t) = mp_stroked_node_type; mp_path_p (t) = p; mp_pen_p (t) = NULL; mp_dash_p (t) = NULL; dash_scale (t) = unity; red_val (t) = 0; green_val (t) = 0; blue_val (t) = 0; black_val (t) = 0; mp_color_model (t) = mp_uninitialized_model; mp_pre_script (t) = NULL; mp_post_script (t) = NULL; @<Set the |ljoin_val| and |miterlim_val| fields in object |t|@>; if (internal_value (mp_linecap) > unity) lcap_val (t) = 2; else if (internal_value (mp_linecap) > 0) lcap_val (t) = 1; else lcap_val (t) = 0; return (mp_node) t; } @ 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|. @ @c scaled mp_sqrt_det (MP mp, scaled a, scaled b, scaled c, scaled d) { scaled maxabs; /* $max(|a|,|b|,|c|,|d|)$ */ unsigned s; /* amount by which the result of |square_rt| needs to be scaled */ @<Initialize |maxabs|@>; s = 64; while ((maxabs < fraction_one) && (s > 1)) { a += a; b += b; c += c; d += d; maxabs += maxabs; s = (unsigned) (halfp (s)); } return (scaled) (s * (unsigned) mp_square_rt (mp, abs (mp_take_fraction (mp, a, d) - mp_take_fraction (mp, b, c)))); } @# static scaled mp_get_pen_scale (MP mp, mp_knot p) { if (p == NULL) return 0; return mp_sqrt_det (mp, mp_left_x (p) - mp_x_coord (p), mp_right_x (p) - mp_x_coord (p), mp_left_y (p) - mp_y_coord (p), mp_right_y (p) - mp_y_coord (p)); } @ @<Declarations@>= static scaled mp_sqrt_det (MP mp, scaled a, scaled b, scaled c, scaled d); @ @<Initialize |maxabs|@>= maxabs = abs (a); if (abs (b) > maxabs) maxabs = abs (b); if (abs (c) > maxabs) maxabs = abs (c); if (abs (d) > maxabs) 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 |mp_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 mp_text_p(A) ((mp_text_node)(A))->text_p_ /* a string pointer for the text to display */ @d mp_font_n(A) ((mp_text_node)(A))->font_n_ /* the font number */ @d width_val(A) ((mp_text_node)(A))->width_val_ /* unscaled width of the text */ @d height_val(A) ((mp_text_node)(A))->height_val_ /* unscaled height of the text */ @d depth_val(A) ((mp_text_node)(A))->depth_val_ /* unscaled depth of the text */ @d tx_val(A) ((mp_text_node)(A))->tx_val_ /* $x$ shift amount */ @d ty_val(A) ((mp_text_node)(A))->ty_val_ /* $y$ shift amount */ @d txx_val(A) ((mp_text_node)(A))->txx_val_ /* |txx| transformation parameter */ @d txy_val(A) ((mp_text_node)(A))->txy_val_ /* |txy| transformation parameter */ @d tyx_val(A) ((mp_text_node)(A))->tyx_val_ /* |tyx| transformation parameter */ @d tyy_val(A) ((mp_text_node)(A))->tyy_val_ /* |tyy| transformation parameter */ @(mpmp.h@>= typedef struct mp_text_node_data { NODE_BODY; halfword color_model_; scaled red_val_; scaled green_val_; scaled blue_val_; scaled black_val_; str_number pre_script_; str_number post_script_; str_number text_p_; halfword font_n_; scaled width_val_; scaled height_val_; scaled depth_val_; scaled tx_val_; scaled ty_val_; scaled txx_val_; scaled txy_val_; scaled tyx_val_; scaled tyy_val_; } mp_text_node_data; typedef struct mp_text_node_data *mp_text_node; @ @<Graphical object codes@>= mp_text_code = 3, @ Make a text node for font |f| and text string |s|. @d text_node_size sizeof(struct mp_text_node_data) @c static mp_node mp_new_text_node (MP mp, char *f, str_number s) { mp_text_node t = (mp_text_node) xmalloc (1, text_node_size); add_var_used (text_node_size); memset (t, 0, text_node_size); mp_type (t) = mp_text_node_type; mp_text_p (t) = s; mp_font_n (t) = (halfword) mp_find_font (mp, f); /* this identifies the font */ red_val (t) = 0; green_val (t) = 0; blue_val (t) = 0; black_val (t) = 0; mp_color_model (t) = mp_uninitialized_model; mp_pre_script (t) = NULL; mp_post_script (t) = NULL; tx_val (t) = 0; ty_val (t) = 0; txx_val (t) = unity; txy_val (t) = 0; tyx_val (t) = 0; tyy_val (t) = unity; mp_set_text_box (mp, (mp_node) t); /* this finds the bounding box */ return (mp_node) t; } @ 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 node whose |path_p| gives the relevant path, then there is the list of objects to clip or bound followed by a closing node. @d has_color(A) (mp_type((A))<mp_start_clip_node_type) /* does a graphical object have color fields? */ @d has_pen(A) (mp_type((A))<mp_text_node_type) /* does a graphical object have a |mp_pen_p| field? */ @d is_start_or_stop(A) (mp_type((A))>=mp_start_clip_node_type) @d is_stop(A) (mp_type((A))>=mp_stop_clip_node_type) @(mpmp.h@>= typedef struct mp_start_clip_node_data { NODE_BODY; mp_knot path_p_; } mp_start_clip_node_data; typedef struct mp_start_clip_node_data *mp_start_clip_node; typedef struct mp_start_bounds_node_data { NODE_BODY; mp_knot path_p_; } mp_start_bounds_node_data; typedef struct mp_start_bounds_node_data *mp_start_bounds_node; typedef struct mp_stop_clip_node_data { NODE_BODY; } mp_stop_clip_node_data; typedef struct mp_stop_clip_node_data *mp_stop_clip_node; typedef struct mp_stop_bounds_node_data { NODE_BODY; } mp_stop_bounds_node_data; typedef struct mp_stop_bounds_node_data *mp_stop_bounds_node; @ @<Graphical object codes@>= mp_start_clip_code = 4, /* |type| of a node that starts clipping */ mp_start_bounds_code = 5, /* |type| of a node that gives a \&{setbounds} path */ mp_stop_clip_code = 6, /* |type| of a node that stops clipping */ mp_stop_bounds_code = 7, /* |type| of a node that stops \&{setbounds} */ @ @d start_clip_size sizeof(struct mp_start_clip_node_data) @d stop_clip_size sizeof(struct mp_stop_clip_node_data) @d start_bounds_size sizeof(struct mp_start_bounds_node_data) @d stop_bounds_size sizeof(struct mp_stop_bounds_node_data) @c static mp_node mp_new_bounds_node (MP mp, mp_knot p, quarterword c) { /* make a node of type |c| where |p| is the clipping or \&{setbounds} path */ if (c == mp_start_clip_node_type) { mp_start_clip_node t; /* the new node */ t = (mp_start_clip_node) xmalloc (1, start_clip_size); add_var_used (start_clip_size); t->path_p_ = p; mp_type (t) = c; t->link = NULL; return (mp_node) t; } else if (c == mp_start_bounds_node_type) { mp_start_bounds_node t; /* the new node */ t = (mp_start_bounds_node) xmalloc (1, start_bounds_size); add_var_used (start_bounds_size); t->path_p_ = p; mp_type (t) = c; t->link = NULL; return (mp_node) t; } else if (c == mp_stop_clip_node_type) { mp_stop_clip_node t; /* the new node */ t = (mp_stop_clip_node) xmalloc (1, stop_clip_size); add_var_used (stop_clip_size); mp_type (t) = c; t->link = NULL; return (mp_node) t; } else if (c == mp_stop_bounds_node_type) { mp_stop_bounds_node t; /* the new node */ t = (mp_stop_bounds_node) xmalloc (1, stop_bounds_size); add_var_used (stop_bounds_size); mp_type (t) = c; t->link = NULL; return (mp_node) t; } else { assert (0); } return NULL; } @ 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. The |dash_info| is explained below. @d dash_list(A) ((mp_dash_node)(A))->link /* in an edge header this points to the first dash node */ @d start_x(A) ((mp_dash_node)(A))->start_x_ /* the starting $x$~coordinate in a dash node */ @d stop_x(A) ((mp_dash_node)(A))->stop_x_ /* the ending $x$~coordinate in a dash node */ @d dash_y(A) ((mp_dash_node)(A))->dash_y_ /* $y$ value for the dash list in an edge header */ @(mpmp.h@>= typedef struct mp_dash_node_data { NODE_BODY; scaled start_x_; scaled stop_x_; scaled dash_y_; mp_node dash_info_; } mp_dash_node_data; typedef struct mp_dash_node_data *mp_dash_node; @ @<Initialize table entries@>= mp->null_dash = mp_get_dash_node (mp); @ @<Free table entries@>= mp_free_node (mp, mp->null_dash, dash_node_size); @ @d dash_node_size sizeof(struct mp_dash_node_data) @c static mp_node mp_get_dash_node (MP mp) { mp_dash_node p = (mp_dash_node) xmalloc (1, dash_node_size); add_var_used (dash_node_size); memset (p, 0, dash_node_size); mp_type (p) = mp_dash_node_type; return (mp_node) p; } @ 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 |mp_start_bounds_node| depends on the value of \&{truecorners}, the bounding box @:mp_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(A) ((mp_edge_header_node)(A))->minx_val_ @d miny_val(A) ((mp_edge_header_node)(A))->miny_val_ @d maxx_val(A) ((mp_edge_header_node)(A))->maxx_val_ @d maxy_val(A) ((mp_edge_header_node)(A))->maxy_val_ @d bblast(A) ((mp_edge_header_node)(A))->bblast_ /* last item considered in bounding box computation */ @d bbtype(A) ((mp_edge_header_node)(A))->bbtype_ /* tells how bounding box data depends on \&{truecorners} */ @d dummy_loc(A) ((mp_edge_header_node)(A))->dummy_ /* where the object list begins in an edge header */ @(mpmp.h@>= typedef struct mp_edge_header_node_data { NODE_BODY; scaled start_x_; scaled stop_x_; scaled dash_y_; mp_node dash_info_; scaled minx_val_; scaled miny_val_; scaled maxx_val_; scaled maxy_val_; mp_node bblast_; halfword bbtype_; mp_node dummy_; mp_node obj_tail_; /* explained below */ halfword ref_count_; /* explained below */ } mp_edge_header_node_data; typedef struct mp_edge_header_node_data *mp_edge_header_node; @ @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$ */ @c static void mp_init_bbox (MP mp, mp_node h) { /* Initialize the bounding box information in edge structure |h| */ (void) mp; 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; } @ 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(A) ((mp_edge_header_node)(A))->obj_tail_ /* points to the last entry in the object list */ @d edge_ref_count(A) ((mp_edge_header_node)(A))->ref_count_ @d edge_header_size sizeof(struct mp_edge_header_node_data) @c static mp_node mp_get_edge_header_node (MP mp) { mp_edge_header_node p = (mp_edge_header_node) xmalloc (1, edge_header_size); add_var_used (edge_header_size); memset (p, 0, edge_header_size); mp_type (p) = mp_edge_header_node_type; p->dummy_ = mp_get_token_node (mp); /* or whatever, just a need a link handle */ return (mp_node) p; } static void mp_init_edges (MP mp, mp_node h) { /* initialize an edge header to NULL values */ dash_list (h) = mp->null_dash; obj_tail (h) = dummy_loc (h); mp_link (dummy_loc (h)) = NULL; edge_ref_count (h) = 0; mp_init_bbox (mp, h); } @ 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(A) incr(edge_ref_count((A))) @d delete_edge_ref(A) { if ( edge_ref_count((A))==0 ) mp_toss_edges(mp, (A)); else decr(edge_ref_count((A))); } @<Declarations@>= static void mp_flush_dash_list (MP mp, mp_node h); static mp_node mp_toss_gr_object (MP mp, mp_node p); static void mp_toss_edges (MP mp, mp_node h); @ @c void mp_toss_edges (MP mp, mp_node h) { mp_node p, q; /* pointers that scan the list being recycled */ mp_node r; /* an edge structure that object |p| refers to */ mp_flush_dash_list (mp, h); q = mp_link (dummy_loc (h)); while ((q != NULL)) { p = q; q = mp_link (q); r = mp_toss_gr_object (mp, p); if (r != NULL) delete_edge_ref (r); } mp_free_node (mp, ((mp_edge_header_node) h)->dummy_, token_node_size); mp_free_node (mp, h, edge_header_size); } void mp_flush_dash_list (MP mp, mp_node h) { mp_node p, q; /* pointers that scan the list being recycled */ q = dash_list (h); while (q != mp->null_dash) { /* todo: NULL check should not be needed */ p = q; q = mp_link (q); mp_free_node (mp, p, dash_node_size); } dash_list (h) = mp->null_dash; } mp_node mp_toss_gr_object (MP mp, mp_node p) { /* returns an edge structure that needs to be dereferenced */ mp_node e = NULL; /* the edge structure to return */ switch (mp_type (p)) { case mp_fill_node_type: mp_toss_knot_list (mp, mp_path_p ((mp_fill_node) p)); if (mp_pen_p ((mp_fill_node) p) != NULL) mp_toss_knot_list (mp, mp_pen_p ((mp_fill_node) p)); if (mp_pre_script (p) != NULL) delete_str_ref (mp_pre_script (p)); if (mp_post_script (p) != NULL) delete_str_ref (mp_post_script (p)); mp_free_node (mp, p, fill_node_size); break; case mp_stroked_node_type: mp_toss_knot_list (mp, mp_path_p ((mp_stroked_node) p)); if (mp_pen_p ((mp_stroked_node) p) != NULL) mp_toss_knot_list (mp, mp_pen_p ((mp_stroked_node) p)); if (mp_pre_script (p) != NULL) delete_str_ref (mp_pre_script (p)); if (mp_post_script (p) != NULL) delete_str_ref (mp_post_script (p)); e = mp_dash_p (p); mp_free_node (mp, p, stroked_node_size); break; case mp_text_node_type: delete_str_ref (mp_text_p (p)); if (mp_pre_script (p) != NULL) delete_str_ref (mp_pre_script (p)); if (mp_post_script (p) != NULL) delete_str_ref (mp_post_script (p)); mp_free_node (mp, p, text_node_size); break; case mp_start_clip_node_type: mp_toss_knot_list (mp, mp_path_p ((mp_start_clip_node) p)); mp_free_node (mp, p, start_clip_size); break; case mp_start_bounds_node_type: mp_toss_knot_list (mp, mp_path_p ((mp_start_bounds_node) p)); mp_free_node (mp, p, start_bounds_size); break; case mp_stop_clip_node_type: mp_free_node (mp, p, stop_clip_size); break; case mp_stop_bounds_node_type: mp_free_node (mp, p, stop_bounds_size); break; default: /* there are no other valid cases, but please the compiler */ return e; } return e; } @ 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. @c static mp_node mp_private_edges (MP mp, mp_node h) { /* make a private copy of the edge structure headed by |h| */ mp_node hh; /* the edge header for the new copy */ mp_node p, pp; /* pointers for copying the dash list */ assert (mp_type (h) == mp_edge_header_node_type); if (edge_ref_count (h) == 0) { return h; } else { decr (edge_ref_count (h)); hh = mp_copy_objects (mp, mp_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@>; return hh; } } @ Here we use the fact that |dash_list(hh)=mp_link(hh)|. @^data structure assumptions@> @<Copy the dash list from |h| to |hh|@>= pp = hh; p = dash_list (h); while ((p != mp->null_dash)) { mp_link (pp) = mp_get_dash_node (mp); pp = mp_link (pp); start_x (pp) = start_x (p); stop_x (pp) = stop_x (p); p = mp_link (p); } mp_link (pp) = mp->null_dash; dash_y (hh) = dash_y (h) @ |h| is an edge structure @c static mp_dash_object *mp_export_dashes (MP mp, mp_stroked_node q, scaled * w) { mp_dash_object *d; mp_node p, h; scaled scf; /* scale factor */ int *dashes = NULL; int num_dashes = 1; h = mp_dash_p (q); if (h == NULL || dash_list (h) == mp->null_dash) return NULL; p = dash_list (h); scf = mp_get_pen_scale (mp, mp_pen_p (q)); if (scf == 0) { if (*w == 0) scf = dash_scale (q); else return NULL; } else { scf = mp_make_scaled (mp, *w, scf); scf = mp_take_scaled (mp, scf, dash_scale (q)); } *w = scf; d = xmalloc (1, sizeof (mp_dash_object)); add_var_used (sizeof (mp_dash_object)); start_x (mp->null_dash) = start_x (p) + dash_y (h); while (p != mp->null_dash) { dashes = xrealloc (dashes, (num_dashes + 2), sizeof (scaled)); dashes[(num_dashes - 1)] = mp_take_scaled (mp, (stop_x (p) - start_x (p)), scf); dashes[(num_dashes)] = mp_take_scaled (mp, (start_x (mp_link (p)) - stop_x (p)), scf); dashes[(num_dashes + 1)] = -1; /* terminus */ num_dashes += 2; p = mp_link (p); } d->array = dashes; d->offset = mp_take_scaled (mp, mp_dash_offset (mp, h), scf); return d; } @ @<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))) { if (p == NULL) mp_confusion (mp, "bblast"); @:this can't happen bblast}{\quad bblast@>; p = mp_link (p); pp = mp_link (pp); } 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|. @<Declarations@>= static mp_node mp_copy_objects (MP mp, mp_node p, mp_node q); @ @c mp_node mp_copy_objects (MP mp, mp_node p, mp_node q) { mp_node hh; /* the new edge header */ mp_node pp; /* the last newly copied object */ quarterword k = 0; /* temporary register */ hh = mp_get_edge_header_node (mp); dash_list (hh) = mp->null_dash; edge_ref_count (hh) = 0; pp = dummy_loc (hh); while (p != q) { @<Make |mp_link(pp)| point to a copy of object |p|, and update |p| and |pp|@>; } obj_tail (hh) = pp; mp_link (pp) = NULL; return hh; } @ @<Make |mp_link(pp)| point to a copy of object |p|, and update |p| and |pp|@>= { switch (mp_type (p)) { case mp_start_clip_node_type: k = start_clip_size; break; case mp_start_bounds_node_type: k = start_bounds_size; break; case mp_fill_node_type: k = fill_node_size; break; case mp_stroked_node_type: k = stroked_node_size; break; case mp_text_node_type: k = text_node_size; break; case mp_stop_clip_node_type: k = stop_clip_size; break; case mp_stop_bounds_node_type: k = stop_bounds_size; break; default: /* there are no other valid cases, but please the compiler */ break; } mp_link (pp) = xmalloc (1, (size_t) k); /* |gr_object| */ add_var_used ((size_t)k); pp = mp_link (pp); memcpy (pp, p, (size_t) k); pp->link = NULL; @<Fix anything in graphical object |pp| that should differ from the corresponding field in |p|@>; p = mp_link (p); } @ @<Fix anything in graphical object |pp| that should differ from the...@>= switch (mp_type (p)) { case mp_start_clip_node_type: mp_path_p ((mp_start_clip_node) pp) = mp_copy_path (mp, mp_path_p ((mp_start_clip_node) p)); break; case mp_start_bounds_node_type: mp_path_p ((mp_start_bounds_node) pp) = mp_copy_path (mp, mp_path_p ((mp_start_bounds_node) p)); break; case mp_fill_node_type: mp_path_p ((mp_fill_node) pp) = mp_copy_path (mp, mp_path_p ((mp_fill_node) p)); if (mp_pre_script (p) != NULL) add_str_ref (mp_pre_script (p)); if (mp_post_script (p) != NULL) add_str_ref (mp_post_script (p)); if (mp_pen_p ((mp_fill_node) p) != NULL) mp_pen_p ((mp_fill_node) pp) = copy_pen (mp_pen_p ((mp_fill_node) p)); break; case mp_stroked_node_type: if (mp_pre_script (p) != NULL) add_str_ref (mp_pre_script (p)); if (mp_post_script (p) != NULL) add_str_ref (mp_post_script (p)); mp_path_p ((mp_stroked_node) pp) = mp_copy_path (mp, mp_path_p ((mp_stroked_node) p)); mp_pen_p ((mp_stroked_node) pp) = copy_pen (mp_pen_p ((mp_stroked_node) p)); if (mp_dash_p (p) != NULL) add_edge_ref (mp_dash_p (pp)); break; case mp_text_node_type: if (mp_pre_script (p) != NULL) add_str_ref (mp_pre_script (p)); if (mp_post_script (p) != NULL) add_str_ref (mp_post_script (p)); add_str_ref (mp_text_p (pp)); break; case mp_stop_clip_node_type: case mp_stop_bounds_node_type: break; default: /* there are no other valid cases, but please the compiler */ break; } @ 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(A) if ( ! is_start_or_stop((A)) ) (A)=mp_link((A)); else if ( ! is_stop((A)) ) (A)=mp_skip_1component(mp, (A)); else @c static mp_node mp_skip_1component (MP mp, mp_node p) { integer lev; /* current nesting level */ lev = 0; (void) mp; do { if (is_start_or_stop (p)) { if (is_stop (p)) decr (lev); else incr (lev); } p = mp_link (p); } while (lev != 0); return p; } @ Here is a diagnostic routine for printing an edge structure in symbolic form. @<Declarations@>= static void mp_print_edges (MP mp, mp_node h, const char *s, boolean nuline); @ @c void mp_print_edges (MP mp, mp_node h, const char *s, boolean nuline) { mp_node p; /* a graphical object to be printed */ mp_node hh, pp; /* temporary pointers */ scaled scf; /* a scale factor for the dash pattern */ boolean ok_to_dash; /* |false| for polygonal pen strokes */ mp_print_diagnostic (mp, "Edge structure", s, nuline); p = dummy_loc (h); while (mp_link (p) != NULL) { p = mp_link (p); mp_print_ln (mp); switch (mp_type (p)) { @<Cases for printing graphical object node |p|@>; default: mp_print (mp, "[unknown object type!]"); break; } } mp_print_nl (mp, "End edges"); if (p != obj_tail (h)) mp_print (mp, "?"); @.End edges?@>; mp_end_diagnostic (mp, true); } @ @<Cases for printing graphical object node |p|@>= case mp_fill_node_type: mp_print (mp, "Filled contour "); mp_print_obj_color (mp, p); mp_print_char (mp, xord (':')); mp_print_ln (mp); mp_pr_path (mp, mp_path_p ((mp_fill_node) p)); mp_print_ln (mp); if ((mp_pen_p ((mp_fill_node) p) != NULL)) { @<Print join type for graphical object |p|@>; mp_print (mp, " with pen"); mp_print_ln (mp); mp_pr_pen (mp, mp_pen_p ((mp_fill_node) p)); } break; @ @<Print join type for graphical object |p|@>= switch (ljoin_val (p)) { case 0: mp_print (mp, "mitered joins limited "); mp_print_scaled (mp, miterlim_val (p)); break; case 1: mp_print (mp, "round joins"); break; case 2: mp_print (mp, "beveled joins"); break; default: mp_print (mp, "?? joins"); @.??@>; break; } @ For stroked nodes, we need to print |lcap_val(p)| as well. @<Print join and cap types for stroked node |p|@>= switch (lcap_val (p)) { case 0: mp_print (mp, "butt"); break; case 1: mp_print (mp, "round"); break; case 2: mp_print (mp, "square"); break; default: mp_print (mp, "??"); break; @.??@> } mp_print (mp, " 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). @<Declarations@>= static void mp_print_obj_color (MP mp, mp_node p); @ @c void mp_print_obj_color (MP mp, mp_node p) { if (mp_color_model (p) == mp_grey_model) { if (grey_val (p) > 0) { mp_print (mp, "greyed "); mp_print_char (mp, xord ('(')); mp_print_scaled (mp, grey_val (p)); mp_print_char (mp, xord (')')); }; } else if (mp_color_model (p) == mp_cmyk_model) { if ((cyan_val (p) > 0) || (magenta_val (p) > 0) || (yellow_val (p) > 0) || (black_val (p) > 0)) { mp_print (mp, "processcolored "); mp_print_char (mp, xord ('(')); mp_print_scaled (mp, cyan_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, magenta_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, yellow_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, black_val (p)); mp_print_char (mp, xord (')')); }; } else if (mp_color_model (p) == mp_rgb_model) { if ((red_val (p) > 0) || (green_val (p) > 0) || (blue_val (p) > 0)) { mp_print (mp, "colored "); mp_print_char (mp, xord ('(')); mp_print_scaled (mp, red_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, green_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, blue_val (p)); mp_print_char (mp, xord (')')); }; } } @ @<Cases for printing graphical object node |p|@>= case mp_stroked_node_type: mp_print (mp, "Filled pen stroke "); mp_print_obj_color (mp, p); mp_print_char (mp, xord (':')); mp_print_ln (mp); mp_pr_path (mp, mp_path_p ((mp_stroked_node) p)); if (mp_dash_p (p) != NULL) { mp_print_nl (mp, "dashed ("); @<Finish printing the dash pattern that |p| refers to@>; } mp_print_ln (mp); @<Print join and cap types for stroked node |p|@>; mp_print (mp, " with pen"); mp_print_ln (mp); if (mp_pen_p ((mp_stroked_node) p) == NULL) { mp_print (mp, "???"); /* shouldn't happen */ @.???@> } else { mp_pr_pen (mp, mp_pen_p ((mp_stroked_node) p)); } break; @ 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 |mp_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 (mp_pen_p ((mp_stroked_node) p)); if (!ok_to_dash) scf = unity; else scf = dash_scale (p); hh = mp_dash_p (p); pp = dash_list (hh); if ((pp == mp->null_dash) || (dash_y (hh) < 0)) { mp_print (mp, " ??"); } else { start_x (mp->null_dash) = start_x (pp) + dash_y (hh); while (pp != mp->null_dash) { mp_print (mp, "on "); mp_print_scaled (mp, mp_take_scaled (mp, stop_x (pp) - start_x (pp), scf)); mp_print (mp, " off "); mp_print_scaled (mp, mp_take_scaled (mp, start_x (mp_link (pp)) - stop_x (pp), scf)); pp = mp_link (pp); if (pp != mp->null_dash) mp_print_char (mp, xord (' ')); } mp_print (mp, ") shifted "); mp_print_scaled (mp, -mp_take_scaled (mp, mp_dash_offset (mp, hh), scf)); if (!ok_to_dash || (dash_y (hh) == 0)) mp_print (mp, " (this will be ignored)"); } @ @<Declarations@>= static scaled mp_dash_offset (MP mp, mp_node h); @ @c scaled mp_dash_offset (MP mp, mp_node h) { scaled x; /* the answer */ if (dash_list (h) == mp->null_dash || dash_y (h) < 0) mp_confusion (mp, "dash0"); @:this can't happen dash0}{\quad dash0@>; if (dash_y (h) == 0) { x = 0; } else { x = -(start_x (dash_list (h)) % dash_y (h)); if (x < 0) x = x + dash_y (h); } return x; } @ @<Cases for printing graphical object node |p|@>= case mp_text_node_type: mp_print_char (mp, xord ('"')); mp_print_str (mp, mp_text_p (p)); mp_print (mp, "\" infont \""); mp_print (mp, mp->font_name[mp_font_n (p)]); mp_print_char (mp, xord ('"')); mp_print_ln (mp); mp_print_obj_color (mp, p); mp_print (mp, "transformed "); mp_print_char (mp, xord ('(')); mp_print_scaled (mp, tx_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, ty_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, txx_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, txy_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, tyx_val (p)); mp_print_char (mp, xord (',')); mp_print_scaled (mp, tyy_val (p)); mp_print_char (mp, xord (')')); break; @ @<Cases for printing graphical object node |p|@>= case mp_start_clip_node_type: mp_print (mp, "clipping path:"); mp_print_ln (mp); mp_pr_path (mp, mp_path_p ((mp_start_clip_node) p)); break; case mp_stop_clip_node_type: mp_print (mp, "stop clipping"); break; @ @<Cases for printing graphical object node |p|@>= case mp_start_bounds_node_type: mp_print (mp, "setbounds path:"); mp_print_ln (mp); mp_pr_path (mp, mp_path_p ((mp_start_bounds_node) p)); break; case mp_stop_bounds_node_type: mp_print (mp, "end of setbounds"); break; @ 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$. @c static mp_node mp_make_dashes (MP mp, mp_node h) { /* returns |h| or |NULL| */ mp_node p; /* this scans the stroked nodes in the object list */ mp_node p0; /* if not |NULL| this points to the first stroked node */ mp_knot pp, qq, rr; /* pointers into |mp_path_p(p)| */ mp_node d, dd; /* pointers used to create the dash list */ scaled y0; @<Other local variables in |make_dashes|@>; y0 = 0; /* the initial $y$ coordinate */ if (dash_list (h) != mp->null_dash) return h; p0 = NULL; p = mp_link (dummy_loc (h)); while (p != NULL) { if (mp_type (p) != mp_stroked_node_type) { @<Compain that the edge structure contains a node of the wrong type and |goto not_found|@>; } pp = mp_path_p ((mp_stroked_node) p); if (p0 == NULL) { p0 = p; y0 = mp_y_coord (pp); } @<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 = mp_link (p); } if (dash_list (h) == mp->null_dash) 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@>; return h; NOT_FOUND: @<Flush the dash list, recycle |h| and return |NULL|@>; } @ @<Compain that the edge structure contains a node of the wrong type...@>= { 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."); mp_put_get_error (mp); goto NOT_FOUND; } @ A similar error occurs when monotonicity fails. @<Declarations@>= static void mp_x_retrace_error (MP mp); @ @c void mp_x_retrace_error (MP mp) { 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."); mp_put_get_error (mp); } @ We stash |p| in |dash_info(d)| if |mp_dash_p(p)<>0| so that subsequent processing can handle the case where the pen stroke |p| is itself dashed. @d dash_info(A) ((mp_dash_node)(A))->dash_info_ /* in an edge header this points to the first dash node */ @<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 (mp_next_knot (pp) != pp) { do { qq = rr; rr = mp_next_knot (rr); @<Check for retracing between knots |qq| and |rr| and |goto not_found| if there is a problem@>; } while (mp_right_type (rr) != mp_endpoint); } d = mp_get_dash_node (mp); if (mp_dash_p (p) == NULL) dash_info (d) = NULL; else dash_info (d) = p; if (mp_x_coord (pp) < mp_x_coord (rr)) { start_x (d) = mp_x_coord (pp); stop_x (d) = mp_x_coord (rr); } else { start_x (d) = mp_x_coord (rr); stop_x (d) = mp_x_coord (pp); } @ 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 = mp_x_coord (qq); x1 = mp_right_x (qq); x2 = mp_left_x (rr); x3 = mp_x_coord (rr); if ((x0 > x1) || (x1 > x2) || (x2 > x3)) { if ((x0 < x1) || (x1 < x2) || (x2 < x3)) { if (mp_ab_vs_cd (mp, x2 - x1, x2 - x1, x1 - x0, x3 - x2) > 0) { mp_x_retrace_error (mp); goto NOT_FOUND; } } } if ((mp_x_coord (pp) > x0) || (x0 > x3)) { if ((mp_x_coord (pp) < x0) || (x0 < x3)) { mp_x_retrace_error (mp); goto NOT_FOUND; } } @ @<Other local variables in |make_dashes|@>= scaled x0, x1, x2, x3; /* $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)) || (black_val (p) != black_val (p0)) || (green_val (p) != green_val (p0)) || (blue_val (p) != blue_val (p0))) { 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."); mp_put_get_error (mp); goto NOT_FOUND; } @ @<Insert |d| into the dash list and |goto not_found| if there is an error@>= start_x (mp->null_dash) = stop_x (d); dd = h; /* this makes |mp_link(dd)=dash_list(h)| */ while (start_x (mp_link (dd)) < stop_x (d)) dd = mp_link (dd); if (dd != h) { if ((stop_x (dd) > start_x (d))) { mp_x_retrace_error (mp); goto NOT_FOUND; }; } mp_link (d) = mp_link (dd); mp_link (dd) = d @ @<Set |dash_y(h)| and merge the first and last dashes if necessary@>= d = dash_list (h); while ((mp_link (d) != mp->null_dash)) d = mp_link (d); dd = dash_list (h); dash_y (h) = stop_x (d) - start_x (dd); if (abs (y0) > dash_y (h)) { dash_y (h) = abs (y0); } else if (d != dd) { dash_list (h) = mp_link (dd); stop_x (d) = stop_x (dd) + dash_y (h); mp_free_node (mp, dd, dash_node_size); } @ 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|@>= mp_flush_dash_list (mp, h); delete_edge_ref (h); return 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 |mp_link(d)=dash_list(h)| */ while (mp_link (d) != mp->null_dash) { ds = dash_info (mp_link (d)); if (ds == NULL) { d = mp_link (d); } else { hh = mp_dash_p (ds); hsf = dash_scale (ds); if ((hh == NULL)) mp_confusion (mp, "dash1"); @:this can't happen dash0}{\quad dash1@>; if (dash_y (hh) == 0) { d = mp_link (d); } else { if (dash_list (hh) == NULL) mp_confusion (mp, "dash1"); @:this can't happen dash0}{\quad dash1@>; @<Replace |mp_link(d)| by a dashed version as determined by edge header |hh| and scale factor |ds|@>; } } } @ @<Other local variables in |make_dashes|@>= mp_node dln; /* |mp_link(d)| */ mp_node hh; /* an edge header that tells how to break up |dln| */ scaled hsf; /* the dash pattern from |hh| gets scaled by this */ mp_node ds; /* the stroked node from which |hh| and |hsf| are derived */ scaled xoff; /* added to $x$ values in |dash_list(hh)| to match |dln| */ @ @<Replace |mp_link(d)| by a dashed version as determined by edge header...@>= dln = mp_link (d); dd = dash_list (hh); xoff = start_x (dln) - mp_take_scaled (mp, hsf, start_x (dd)) - mp_take_scaled (mp, hsf, mp_dash_offset (mp, hh)); start_x (mp->null_dash) = mp_take_scaled (mp, hsf, start_x (dd)) + mp_take_scaled (mp, hsf, dash_y (hh)); stop_x (mp->null_dash) = start_x (mp->null_dash); @<Advance |dd| until finding the first dash that overlaps |dln| when offset by |xoff|@>; while (start_x (dln) <= stop_x (dln)) { @<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 = mp_link (dd); start_x (dln) = xoff + mp_take_scaled (mp, hsf, start_x (dd)); } mp_link (d) = mp_link (dln); mp_free_node (mp, dln, dash_node_size) @ The name of this module is a bit of a lie because we 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 + mp_take_scaled (mp, hsf, stop_x (dd)) < start_x (dln)) { dd = mp_link (dd); } @ @<If |dd| has `fallen off the end', back up to the beginning and fix...@>= if (dd == mp->null_dash) { dd = dash_list (hh); xoff = xoff + mp_take_scaled (mp, hsf, dash_y (hh)); } @ 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 + mp_take_scaled (mp, hsf, start_x (dd))) <= stop_x (dln)) { mp_link (d) = mp_get_dash_node (mp); d = mp_link (d); mp_link (d) = dln; if (start_x (dln) > (xoff + mp_take_scaled (mp, hsf, start_x (dd)))) start_x (d) = start_x (dln); else start_x (d) = xoff + mp_take_scaled (mp, hsf, start_x (dd)); if (stop_x (dln) < (xoff + mp_take_scaled (mp, hsf, stop_x (dd)))) stop_x (d) = stop_x (dln); else stop_x (d) = xoff + mp_take_scaled (mp, hsf, stop_x (dd)); } @ 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|.) @c static void mp_adjust_bbox (MP mp, mp_node h) { if (mp_minx < minx_val (h)) minx_val (h) = mp_minx; if (mp_miny < miny_val (h)) miny_val (h) = mp_miny; if (mp_maxx > maxx_val (h)) maxx_val (h) = mp_maxx; if (mp_maxy > maxy_val (h)) maxy_val (h) = mp_maxy; } @ 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|. @c static void mp_box_ends (MP mp, mp_knot p, mp_knot pp, mp_node h) { mp_knot q; /* a knot node adjacent to knot |p| */ fraction dx, dy; /* a unit vector in the direction out of the path at~|p| */ scaled d; /* a factor for adjusting the length of |(dx,dy)| */ scaled z; /* a coordinate being tested against the bounding box */ scaled xx, yy; /* the extreme pen vertex in the |(dx,dy)| direction */ integer i; /* a loop counter */ if (mp_right_type (p) != mp_endpoint) { q = mp_next_knot (p); while (1) { @<Make |(dx,dy)| the final direction for the path segment from |q| to~|p|; set~|d|@>; d = mp_pyth_add (mp, dx, dy); if (d > 0) { @<Normalize the direction |(dx,dy)| and find the pen offset |(xx,yy)|@>; for (i = 1; i <= 2; i++) { @<Use |(dx,dy)| to generate a vertex of the square end cap and update the bounding box to accommodate it@>; dx = -dx; dy = -dy; } } if (mp_right_type (p) == mp_endpoint) { return; } else { @<Advance |p| to the end of the path and make |q| the previous knot@>; } } } } @ @<Make |(dx,dy)| the final direction for the path segment from...@>= if (q == mp_next_knot (p)) { dx = mp_x_coord (p) - mp_right_x (p); dy = mp_y_coord (p) - mp_right_y (p); if ((dx == 0) && (dy == 0)) { dx = mp_x_coord (p) - mp_left_x (q); dy = mp_y_coord (p) - mp_left_y (q); } } else { dx = mp_x_coord (p) - mp_left_x (p); dy = mp_y_coord (p) - mp_left_y (p); if ((dx == 0) && (dy == 0)) { dx = mp_x_coord (p) - mp_right_x (q); dy = mp_y_coord (p) - mp_right_y (q); } } dx = mp_x_coord (p) - mp_x_coord (q); dy = mp_y_coord (p) - mp_y_coord (q) @ @<Normalize the direction |(dx,dy)| and find the pen offset |(xx,yy)|@>= dx = mp_make_fraction (mp, dx, d); dy = mp_make_fraction (mp, dy, d); mp_find_offset (mp, -dy, dx, pp); xx = mp->cur_x; yy = mp->cur_y @ @<Use |(dx,dy)| to generate a vertex of the square end cap and...@>= mp_find_offset (mp, dx, dy, pp); d = mp_take_fraction (mp, xx - mp->cur_x, dx) + mp_take_fraction (mp, yy - mp->cur_y, dy); if (((d < 0) && (i == 1)) || ((d > 0) && (i == 2))) mp_confusion (mp, "box_ends"); @:this can't happen box ends}{\quad\\{box\_ends}@>; z = mp_x_coord (p) + mp->cur_x + mp_take_fraction (mp, d, dx); if (z < minx_val (h)) minx_val (h) = z; if (z > maxx_val (h)) maxx_val (h) = z; z = mp_y_coord (p) + mp->cur_y + mp_take_fraction (mp, d, dy); if (z < miny_val (h)) miny_val (h) = z; if (z > maxy_val (h)) maxy_val (h) = z @ @<Advance |p| to the end of the path and make |q| the previous knot@>= do { q = p; p = mp_next_knot (p); } while (mp_right_type (p) != mp_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. @c void mp_set_bbox (MP mp, mp_node h, boolean top_level) { mp_node p; /* a graphical object being considered */ scaled sminx, sminy, smaxx, smaxy; /* for saving the bounding box during recursive calls */ scaled x0, x1, y0, y1; /* temporary registers */ integer lev; /* nesting level for |mp_start_bounds_node| nodes */ @<Wipe out any existing bounding box information if |bbtype(h)| is incompatible with |internal[mp_true_corners]|@>; while (mp_link (bblast (h)) != NULL) { p = mp_link (bblast (h)); bblast (h) = p; switch (mp_type (p)) { case mp_stop_clip_node_type: if (top_level) mp_confusion (mp, "bbox"); else return; @:this can't happen bbox}{\quad bbox@>; break; @<Other cases for updating the bounding box based on the type of object |p|@>; default: /* there are no other valid cases, but please the compiler */ break; } } if (!top_level) mp_confusion (mp, "bbox"); } @ @<Declarations@>= static void mp_set_bbox (MP mp, mp_node h, boolean top_level); @ @<Wipe out any existing bounding box information if |bbtype(h)| is...@>= switch (bbtype (h)) { case no_bounds: break; case bounds_set: if (internal_value (mp_true_corners) > 0) mp_init_bbox (mp, h); break; case bounds_unset: if (internal_value (mp_true_corners) <= 0) mp_init_bbox (mp, h); break; } /* there are no other cases */ @ @<Other cases for updating the bounding box...@>= case mp_fill_node_type: mp_path_bbox (mp, mp_path_p ((mp_fill_node) p)); if (mp_pen_p ((mp_fill_node) p) != NULL) { x0 = mp_minx; y0 = mp_miny; x1 = mp_maxx; y1 = mp_maxy; mp_pen_bbox (mp, mp_pen_p ((mp_fill_node) p)); mp_minx = mp_minx + x0; mp_miny = mp_miny + y0; mp_maxx = mp_maxx + x1; mp_maxy = mp_maxy + y1; } mp_adjust_bbox (mp, h); break; @ @<Other cases for updating the bounding box...@>= case mp_start_bounds_node_type: if (internal_value (mp_true_corners) > 0) { bbtype (h) = bounds_unset; } else { bbtype (h) = bounds_set; mp_path_bbox (mp, mp_path_p ((mp_start_bounds_node) p)); mp_adjust_bbox (mp, h); @<Scan to the matching |mp_stop_bounds_node| node and update |p| and |bblast(h)|@>; } break; case mp_stop_bounds_node_type: if (internal_value (mp_true_corners) <= 0) mp_confusion (mp, "bbox2"); @:this can't happen bbox2}{\quad bbox2@>; break; @ @<Scan to the matching |mp_stop_bounds_node| node and update |p| and...@>= lev = 1; while (lev != 0) { if (mp_link (p) == NULL) mp_confusion (mp, "bbox2"); @:this can't happen bbox2}{\quad bbox2@>; p = mp_link (p); if (mp_type (p) == mp_start_bounds_node_type) incr (lev); else if (mp_type (p) == mp_stop_bounds_node_type) decr (lev); } 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...@>= case mp_stroked_node_type: mp_path_bbox (mp, mp_path_p ((mp_stroked_node) p)); x0 = mp_minx; y0 = mp_miny; x1 = mp_maxx; y1 = mp_maxy; mp_pen_bbox (mp, mp_pen_p ((mp_stroked_node) p)); mp_minx = mp_minx + x0; mp_miny = mp_miny + y0; mp_maxx = mp_maxx + x1; mp_maxy = mp_maxy + y1; mp_adjust_bbox (mp, h); if ((mp_left_type (mp_path_p ((mp_stroked_node) p)) == mp_endpoint) && (lcap_val (p) == 2)) mp_box_ends (mp, mp_path_p ((mp_stroked_node) p), mp_pen_p ((mp_stroked_node) p), h); break; @ 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...@>= case mp_text_node_type: x1 = mp_take_scaled (mp, txx_val (p), width_val (p)); y0 = mp_take_scaled (mp, txy_val (p), -depth_val (p)); y1 = mp_take_scaled (mp, txy_val (p), height_val (p)); mp_minx = tx_val (p); mp_maxx = mp_minx; if (y0 < y1) { mp_minx = mp_minx + y0; mp_maxx = mp_maxx + y1; } else { mp_minx = mp_minx + y1; mp_maxx = mp_maxx + y0; } if (x1 < 0) mp_minx = mp_minx + x1; else mp_maxx = mp_maxx + x1; x1 = mp_take_scaled (mp, tyx_val (p), width_val (p)); y0 = mp_take_scaled (mp, tyy_val (p), -depth_val (p)); y1 = mp_take_scaled (mp, tyy_val (p), height_val (p)); mp_miny = ty_val (p); mp_maxy = mp_miny; if (y0 < y1) { mp_miny = mp_miny + y0; mp_maxy = mp_maxy + y1; } else { mp_miny = mp_miny + y1; mp_maxy = mp_maxy + y0; } if (x1 < 0) mp_miny = mp_miny + x1; else mp_maxy = mp_maxy + x1; mp_adjust_bbox (mp, h); break; @ This case involves a recursive call that advances |bblast(h)| to the node of type |mp_stop_clip_node| that matches |p|. @<Other cases for updating the bounding box...@>= case mp_start_clip_node_type: mp_path_bbox (mp, mp_path_p ((mp_start_clip_node) p)); x0 = mp_minx; y0 = mp_miny; x1 = mp_maxx; y1 = mp_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 |mp_link(p)|@>; @<Clip the bounding box in |h| to the rectangle given by |x0|, |x1|, |y0|, |y1|@>; mp_minx = sminx; mp_miny = sminy; mp_maxx = smaxx; mp_maxy = smaxy; mp_adjust_bbox (mp, h); break; @ @<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; mp_set_bbox (mp, h, false) @ @<Clip the bounding box in |h| to the rectangle given by |x0|, |x1|,...@>= if (minx_val (h) < x0) minx_val (h) = x0; if (miny_val (h) < y0) miny_val (h) = y0; if (maxx_val (h) > x1) maxx_val (h) = x1; if (maxy_val (h) > y1) maxy_val (h) = y1 @* 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 |mp_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...@>= integer spec_offset; /* number of pen edges between |h| and the initial offset */ @ @c static mp_knot mp_offset_prep (MP mp, mp_knot c, mp_knot h) { halfword n; /* the number of vertices in the pen polygon */ mp_knot c0, p, q, q0, r, w, ww; /* for list manipulation */ integer k_needed; /* amount to be added to |mp_info(p)| when it is computed */ mp_knot w0; /* a pointer to pen offset to use just before |p| */ scaled dxin, dyin; /* the direction into knot |p| */ integer turn_amt; /* change in pen offsets for the current cubic */ @<Other local variables for |offset_prep|@>; dx0 = 0; dy0 = 0; @<Initialize the pen size~|n|@>; @<Initialize the incoming direction and pen offset at |c|@>; p = c; c0 = c; k_needed = 0; do { q = mp_next_knot (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@>; NOT_FOUND: @<Advance |p| to node |q|, removing any ``dead'' cubics that might have been introduced by the splitting process@>; } while (q != c); @<Fix the offset change in |mp_knot_info(c)| and set |c| to the return value of |offset_prep|@>; return c; } @ 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...@>= mp_knot spec_p1; mp_knot spec_p2; /* pointers to distinguished knots */ @ @<Set init...@>= mp->spec_p1 = NULL; mp->spec_p2 = NULL; @ @<Initialize the pen size~|n|@>= n = 0; p = h; do { incr (n); p = mp_next_knot (p); } while (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 = mp_x_coord (mp_next_knot (h)) - mp_x_coord (mp_prev_knot (h)); dyin = mp_y_coord (mp_next_knot (h)) - mp_y_coord (mp_prev_knot (h)); if ((dxin == 0) && (dyin == 0)) { dxin = mp_y_coord (mp_prev_knot (h)) - mp_y_coord (h); dyin = mp_x_coord (h) - mp_x_coord (mp_prev_knot (h)); } 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, the target node |q| can be collapsed to the initial node |p| which might be the same as the initial node |c| of the curve. This would cause the |offset_prep| routine to bail out too early, causing distress later on. (See for example the testcase reported by Bogus\l{}aw Jackowski in tracker id 267, case 52c on Sarovar.) @<Advance |p| to node |q|, removing any ``dead'' cubics...@>= q0 = q; do { r = mp_next_knot (p); if (mp_x_coord (p) == mp_right_x (p) && mp_y_coord (p) == mp_right_y (p) && mp_x_coord (p) == mp_left_x (r) && mp_y_coord (p) == mp_left_y (r) && mp_x_coord (p) == mp_x_coord (r) && mp_y_coord (p) == mp_y_coord (r) && r != p) { @<Remove the cubic following |p| and update the data structures to merge |r| into |p|@>; } p = r; } while (p != q); /* Check if we removed too much */ if ((q != q0) && (q != c || c == c0)) q = mp_next_knot (q) @ @<Remove the cubic following |p| and update the data structures...@>= { k_needed = mp_knot_info (p) - zero_off; if (r == q) { q = p; } else { mp_knot_info (p) = k_needed + mp_knot_info (r); k_needed = 0; } if (r == c) { mp_knot_info (p) = mp_knot_info (c); c = p; } if (r == mp->spec_p1) mp->spec_p1 = p; if (r == mp->spec_p2) mp->spec_p2 = p; r = p; mp_remove_cubic (mp, p); } @ Not setting the |info| field of the newly created knot allows the splitting routine to work for paths. @<Declarations@>= static void mp_split_cubic (MP mp, mp_knot p, fraction t); @ @c void mp_split_cubic (MP mp, mp_knot p, fraction t) { /* splits the cubic after |p| */ scaled v; /* an intermediate value */ mp_knot q, r; /* for list manipulation */ q = mp_next_knot (p); r = mp_new_knot (mp); mp_next_knot (p) = r; mp_next_knot (r) = q; mp_originator (r) = mp_program_code; mp_left_type (r) = mp_explicit; mp_right_type (r) = mp_explicit; v = t_of_the_way (mp_right_x (p), mp_left_x (q)); mp_right_x (p) = t_of_the_way (mp_x_coord (p), mp_right_x (p)); mp_left_x (q) = t_of_the_way (mp_left_x (q), mp_x_coord (q)); mp_left_x (r) = t_of_the_way (mp_right_x (p), v); mp_right_x (r) = t_of_the_way (v, mp_left_x (q)); mp_x_coord (r) = t_of_the_way (mp_left_x (r), mp_right_x (r)); v = t_of_the_way (mp_right_y (p), mp_left_y (q)); mp_right_y (p) = t_of_the_way (mp_y_coord (p), mp_right_y (p)); mp_left_y (q) = t_of_the_way (mp_left_y (q), mp_y_coord (q)); mp_left_y (r) = t_of_the_way (mp_right_y (p), v); mp_right_y (r) = t_of_the_way (v, mp_left_y (q)); mp_y_coord (r) = t_of_the_way (mp_left_y (r), mp_right_y (r)); } @ This does not set |mp_knot_info(p)| or |mp_right_type(p)|. @<Declarations@>= static void mp_remove_cubic (MP mp, mp_knot p); @ @c void mp_remove_cubic (MP mp, mp_knot p) { /* removes the dead cubic following~|p| */ mp_knot q; /* the node that disappears */ (void) mp; q = mp_next_knot (p); mp_next_knot (p) = mp_next_knot (q); mp_right_x (p) = mp_right_x (q); mp_right_y (p) = mp_right_y (q); mp_xfree (q); } @ 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...@>= mp_knot_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 |mp_knot_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 = mp_pen_walk (mp, w0, turn_amt) @ @<Declarations@>= static mp_knot mp_pen_walk (MP mp, mp_knot w, integer k); @ @c mp_knot mp_pen_walk (MP mp, mp_knot w, integer k) { /* walk |k| steps around a pen from |w| */ (void) mp; while (k > 0) { w = mp_next_knot (w); decr (k); } while (k < 0) { w = mp_prev_knot (w); incr (k); } return w; } @ 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|@>= integer x0, x1, x2, y0, y1, y2; /* representatives of derivatives */ integer t0, t1, t2; /* coefficients of polynomial for slope testing */ integer du, dv, dx, dy; /* for directions of the pen and the curve */ integer dx0, dy0; /* initial direction for the first cubic in the curve */ integer max_coef; /* used while scaling */ integer x0a, x1a, x2a, y0a, y1a, y2a; /* intermediate values */ fraction t; /* where the derivative passes through zero */ fraction s; /* a temporary value */ @ @<Prepare for derivative computations...@>= x0 = mp_right_x (p) - mp_x_coord (p); x2 = mp_x_coord (q) - mp_left_x (q); x1 = mp_left_x (q) - mp_right_x (p); y0 = mp_right_y (p) - mp_y_coord (p); y2 = mp_y_coord (q) - mp_left_y (q); y1 = mp_left_y (q) - mp_right_y (p); max_coef = abs (x0); if (abs (x1) > max_coef) max_coef = abs (x1); if (abs (x2) > max_coef) max_coef = abs (x2); if (abs (y0) > max_coef) max_coef = abs (y0); if (abs (y1) > max_coef) max_coef = abs (y1); if (abs (y2) > max_coef) max_coef = abs (y2); if (max_coef == 0) goto NOT_FOUND; while (max_coef < fraction_half) { double (max_coef); double (x0); double (x1); double (x2); double (y0); double (y1); double (y2); } @ 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 |mp_info(p)| should already be set properly. The |turn_amt| parameter gives the absolute value of the overall net change in pen offsets. @<Declarations@>= static void mp_fin_offset_prep (MP mp, mp_knot p, mp_knot w, integer x0, integer x1, integer x2, integer y0, integer y1, integer y2, integer rise, integer turn_amt); @ @c void mp_fin_offset_prep (MP mp, mp_knot p, mp_knot w, integer x0, integer x1, integer x2, integer y0, integer y1, integer y2, integer rise, integer turn_amt) { mp_knot ww; /* for list manipulation */ scaled du, dv; /* for slope calculation */ integer t0, t1, t2; /* test coefficients */ fraction t; /* place where the derivative passes a critical slope */ fraction s; /* slope or reciprocal slope */ integer v; /* intermediate value for updating |x0..y2| */ mp_knot q; /* original |mp_next_knot(p)| */ q = mp_next_knot (p); while (1) { if (rise > 0) ww = mp_next_knot (w); /* a pointer to $w\k$ */ else ww = mp_prev_knot (w); /* a pointer to $w_{k-1}$ */ @<Compute test coefficients |(t0,t1,t2)| for $d(t)$ versus $d_k$ or $d_{k-1}$@>; t = mp_crossing_point (mp, t0, t1, t2); if (t >= fraction_one) { if (turn_amt > 0) t = fraction_one; else return; } @<Split the cubic at $t$, and split off another cubic if the derivative crosses back@>; w = ww; } } @ 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 = mp_x_coord (ww) - mp_x_coord (w); dv = mp_y_coord (ww) - mp_y_coord (w); if (abs (du) >= abs (dv)) { s = mp_make_fraction (mp, dv, du); t0 = mp_take_fraction (mp, x0, s) - y0; t1 = mp_take_fraction (mp, x1, s) - y1; t2 = mp_take_fraction (mp, x2, s) - y2; if (du < 0) { negate (t0); negate (t1); negate (t2); } } else { s = mp_make_fraction (mp, du, dv); t0 = x0 - mp_take_fraction (mp, y0, s); t1 = x1 - mp_take_fraction (mp, y1, s); t2 = x2 - mp_take_fraction (mp, y2, s); if (dv < 0) { negate (t0); negate (t1); negate (t2); } } if (t0 < 0) 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 and return towards $s_{k-1}$ or $s_k$, respectively, yielding another solution of $(*)$. @<Split the cubic at $t$, and split off another...@>= { mp_split_cubic (mp, p, t); p = mp_next_knot (p); mp_knot_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) { t1 = t_of_the_way (t1, t2); if (t1 > 0) t1 = 0; /* without rounding error, |t1| would be |<=0| */ t = mp_crossing_point (mp, 0, -t1, -t2); if (t > fraction_one) t = fraction_one; incr (turn_amt); if ((t == fraction_one) && (mp_next_knot (p) != q)) { mp_knot_info (mp_next_knot (p)) = mp_knot_info (mp_next_knot (p)) - rise; } else { mp_split_cubic (mp, p, t); mp_knot_info (mp_next_knot (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); } } } @ 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 && dy == 0) { dx = x1; dy = y1; if (dx == 0 && dy == 0) { dx = x2; dy = y2; } } if (p == c) { dx0 = dx; dy0 = dy; } @ @<Find the final direction |(dxin,dyin)|@>= dxin = x2; dyin = y2; if (dxin == 0 && dyin == 0) { dxin = x1; dyin = y1; if (dxin == 0 && dyin == 0) { dxin = x0; dyin = y0; } } @ 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 |mp_knot_info(p)| and find the offset $w_k$ such that...@>= turn_amt = mp_get_turn_amt (mp, w0, dx, dy, (mp_ab_vs_cd (mp, dy, dxin, dx, dyin) >= 0)); w = mp_pen_walk (mp, w0, turn_amt); w0 = w; mp_knot_info (p) = mp_knot_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. @<Declarations@>= static integer mp_get_turn_amt (MP mp, mp_knot w, scaled dx, scaled dy, boolean ccw); @ @c integer mp_get_turn_amt (MP mp, mp_knot w, scaled dx, scaled dy, boolean ccw) { mp_knot ww; /* a neighbor of knot~|w| */ integer s; /* turn amount so far */ integer t; /* |ab_vs_cd| result */ s = 0; (void) mp; if (ccw) { ww = mp_next_knot (w); do { t = mp_ab_vs_cd (mp, dy, (mp_x_coord (ww) - mp_x_coord (w)), dx, (mp_y_coord (ww) - mp_y_coord (w))); if (t < 0) break; incr (s); w = ww; ww = mp_next_knot (ww); } while (t > 0); } else { ww = mp_prev_knot (w); while (mp_ab_vs_cd (mp, dy, (mp_x_coord (w) - mp_x_coord (ww)), dx, (mp_y_coord (w) - mp_y_coord (ww))) < 0) { decr (s); w = ww; ww = mp_prev_knot (ww); } } return s; } @ 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 |mp_info(c)| which was erroneously based on an incoming offset of~|h|. @d fix_by(A) mp_knot_info(c)=mp_knot_info(c)+(A) @<Fix the offset change in |mp_knot_info(c)| and set |c| to the return value of...@>= mp->spec_offset = mp_knot_info (c) - zero_off; if (mp_next_knot (c) == c) { mp_knot_info (c) = zero_off + n; } else { fix_by (k_needed); while (w0 != h) { fix_by (1); w0 = mp_next_knot (w0); } while (mp_knot_info (c) <= zero_off - n) fix_by (n); while (mp_knot_info (c) > zero_off) fix_by (-n); if ((mp_knot_info (c) != zero_off) && (mp_ab_vs_cd (mp, dy0, dxin, dx0, dyin) >= 0)) fix_by (n); } @ 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 = mp_prev_knot (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) { mp_fin_offset_prep (mp, p, w, x0, x1, x2, y0, y1, y2, 1, turn_amt); } else { mp_split_cubic (mp, p, t); r = mp_next_knot (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); mp_fin_offset_prep (mp, p, w, x0, x1a, x2a, y0, y1a, y2a, 1, 0); x0 = x2a; y0 = y2a; mp_knot_info (r) = zero_off - 1; if (turn_amt >= 0) { t1 = t_of_the_way (t1, t2); if (t1 > 0) t1 = 0; t = mp_crossing_point (mp, 0, -t1, -t2); if (t > fraction_one) t = fraction_one; @<Split off another rising cubic for |fin_offset_prep|@>; mp_fin_offset_prep (mp, r, ww, x0, x1, x2, y0, y1, y2, -1, 0); } else { mp_fin_offset_prep (mp, r, ww, x0, x1, x2, y0, y1, y2, -1, (-1 - turn_amt)); } } @ @<Split off another rising cubic for |fin_offset_prep|@>= mp_split_cubic (mp, r, t); mp_knot_info (mp_next_knot (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); mp_fin_offset_prep (mp, mp_next_knot (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 = mp_crossing_point (mp, t0, t1, t2); if (turn_amt >= 0) { if (t2 < 0) { t = fraction_one + 1; } else { u0 = t_of_the_way (x0, x1); u1 = t_of_the_way (x1, x2); ss = mp_take_fraction (mp, -du, t_of_the_way (u0, u1)); v0 = t_of_the_way (y0, y1); v1 = t_of_the_way (y1, y2); ss = ss + mp_take_fraction (mp, -dv, t_of_the_way (v0, v1)); if (ss < 0) t = fraction_one + 1; } } else if (t > fraction_one) { t = fraction_one; } @ @<Other local variables for |offset_prep|@>= integer u0, u1, v0, v1; /* intermediate values for $d(t)$ calculation */ integer ss = 0; /* the part of the dot product computed so far */ int d_sign; /* 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 = mp_ab_vs_cd (mp, dx, dyin, dxin, dy); if (d_sign == 0) { @<Check rotation direction based on node position@> } if (d_sign == 0) { if (dx == 0) { if (dy > 0) d_sign = 1; else d_sign = -1; } else { if (dx > 0) 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 = mp_get_turn_amt (mp, w, dxin, dyin, (d_sign > 0)); if (ss < 0) turn_amt = turn_amt - d_sign * n @ We check rotation direction by looking at the vector connecting the current node with the next. If its angle with incoming and outgoing tangents has the same sign, we pick this as |d_sign|, since it means we have a flex, not a cusp. Otherwise we proceed to the cusp code. @<Check rotation direction based on node position@>= u0 = mp_x_coord (q) - mp_x_coord (p); u1 = mp_y_coord (q) - mp_y_coord (p); d_sign = half (mp_ab_vs_cd (mp, dx, u1, u0, dy) + mp_ab_vs_cd (mp, u0, dyin, dxin, u1)); @ 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 (mp_take_fraction (mp, x0, y2)) - half (mp_take_fraction (mp, x2, y0)); t1 = half (mp_take_fraction (mp, x1, (y0 + y2))) - half (mp_take_fraction (mp, y1, (x0 + x2))); if (t0 == 0) t0 = d_sign; /* path reversal always negates |d_sign| */ if (t0 > 0) { t = mp_crossing_point (mp, 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); } else { t = mp_crossing_point (mp, -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); } ss = mp_take_fraction (mp, (x0 + x2), t_of_the_way (u0, u1)) + mp_take_fraction (mp, (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. @c static void mp_print_spec (MP mp, mp_knot cur_spec, mp_knot cur_pen, const char *s) { mp_knot p, q; /* list traversal */ mp_knot w; /* the current pen offset */ mp_print_diagnostic (mp, "Envelope spec", s, true); p = cur_spec; w = mp_pen_walk (mp, cur_pen, mp->spec_offset); mp_print_ln (mp); mp_print_two (mp, mp_x_coord (cur_spec), mp_y_coord (cur_spec)); mp_print (mp, " % beginning with offset "); mp_print_two (mp, mp_x_coord (w), mp_y_coord (w)); do { while (1) { q = mp_next_knot (p); @<Print the cubic between |p| and |q|@>; p = q; if ((p == cur_spec) || (mp_knot_info (p) != zero_off)) break; } if (mp_knot_info (p) != zero_off) { @<Update |w| as indicated by |mp_knot_info(p)| and print an explanation@>; } } while (p != cur_spec); mp_print_nl (mp, " & cycle"); mp_end_diagnostic (mp, true); } @ @<Update |w| as indicated by |mp_knot_info(p)| and print an explanation@>= { w = mp_pen_walk (mp, w, (mp_knot_info (p) - zero_off)); mp_print (mp, " % "); if (mp_knot_info (p) > zero_off) mp_print (mp, "counter"); mp_print (mp, "clockwise to offset "); mp_print_two (mp, mp_x_coord (w), mp_y_coord (w)); } @ @<Print the cubic between |p| and |q|@>= { mp_print_nl (mp, " ..controls "); mp_print_two (mp, mp_right_x (p), mp_right_y (p)); mp_print (mp, " and "); mp_print_two (mp, mp_left_x (q), mp_left_y (q)); mp_print_nl (mp, " .."); mp_print_two (mp, mp_x_coord (q), mp_y_coord (q)); } @ 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|. @c static mp_knot mp_make_envelope (MP mp, mp_knot c, mp_knot h, quarterword ljoin, quarterword lcap, scaled miterlim) { mp_knot p, q, r, q0; /* for manipulating the path */ mp_knot w, w0; /* the pen knot for the current offset */ scaled qx, qy; /* unshifted coordinates of |q| */ halfword k, k0; /* controls pen edge insertion */ int join_type = 0; /* codes |0..3| for mitered, round, beveled, or square */ @<Other local variables for |make_envelope|@>; dxin = 0; dyin = 0; dxout = 0; dyout = 0; mp->spec_p1 = NULL; mp->spec_p2 = NULL; @<If endpoint, 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; do { q = mp_next_knot (p); q0 = q; qx = mp_x_coord (q); qy = mp_y_coord (q); k = mp_knot_info (q); k0 = k; w0 = w; if (k != zero_off) { @<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) { @<Step |w| and move |k| one step closer to |zero_off|@>; if ((join_type == 1) || (k == zero_off)) q = mp_insert_knot (mp, q, qx + mp_x_coord (w), qy + mp_y_coord (w)); }; if (q != mp_next_knot (p)) { @<Set |p=mp_link(p)| and add knots between |p| and |q| as required by |join_type|@>; } p = q; } while (q0 != c); return c; } @ @<Use |offset_prep| to compute the envelope spec then walk |h| around to...@>= c = mp_offset_prep (mp, c, h); if (internal_value (mp_tracing_specs) > 0) mp_print_spec (mp, c, h, ""); h = mp_pen_walk (mp, h, mp->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) { join_type = 2; } else { if ((q != mp->spec_p1) && (q != mp->spec_p2)) join_type = ljoin; else if (lcap == 2) join_type = 3; else join_type = 2 - lcap; if ((join_type == 0) || (join_type == 3)) { @<Set the incoming and outgoing directions at |q|; in case of degeneracy set |join_type:=2|@>; if (join_type == 0) { @<If |miterlim| is less than the secant of half the angle at |q| then set |join_type:=2|@>; } } } @ @<If |miterlim| is less than the secant of half the angle at |q|...@>= { tmp = mp_take_fraction (mp, miterlim, fraction_half + half (mp_take_fraction (mp, dxin, dxout) + mp_take_fraction (mp, dyin, dyout))); if (tmp < unity) if (mp_take_scaled (mp, miterlim, tmp) < unity) join_type = 2; } @ @<Other local variables for |make_envelope|@>= fraction dxin, dyin, dxout, dyout; /* directions at |q| when square or mitered */ scaled tmp; /* 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|@>= mp_right_x (p) = mp_right_x (p) + mp_x_coord (w); mp_right_y (p) = mp_right_y (p) + mp_y_coord (w); mp_left_x (q) = mp_left_x (q) + mp_x_coord (w); mp_left_y (q) = mp_left_y (q) + mp_y_coord (w); mp_x_coord (q) = mp_x_coord (q) + mp_x_coord (w); mp_y_coord (q) = mp_y_coord (q) + mp_y_coord (w); mp_left_type (q) = mp_explicit; mp_right_type (q) = mp_explicit @ @<Step |w| and move |k| one step closer to |zero_off|@>= if (k > zero_off) { w = mp_next_knot (w); decr (k); } else { w = mp_prev_knot (w); incr (k); } @ The cubic from |q| to the new knot at |(x,y)| becomes a line segment and the |mp_right_x| and |mp_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.'' @<Declarations@>= static mp_knot mp_insert_knot (MP mp, mp_knot q, scaled x, scaled y); @ @c mp_knot mp_insert_knot (MP mp, mp_knot q, scaled x, scaled y) { /* returns the inserted knot */ mp_knot r; /* the new knot */ r = mp_new_knot (mp); mp_next_knot (r) = mp_next_knot (q); mp_next_knot (q) = r; mp_right_x (r) = mp_right_x (q); mp_right_y (r) = mp_right_y (q); mp_x_coord (r) = x; mp_y_coord (r) = y; mp_right_x (q) = mp_x_coord (q); mp_right_y (q) = mp_y_coord (q); mp_left_x (r) = mp_x_coord (r); mp_left_y (r) = mp_y_coord (r); mp_left_type (r) = mp_explicit; mp_right_type (r) = mp_explicit; mp_originator (r) = mp_program_code; return r; } @ After setting |p:=mp_link(p)|, either |join_type=1| or |q=mp_link(p)|. @<Set |p=mp_link(p)| and add knots between |p| and |q| as...@>= { p = mp_next_knot (p); if ((join_type == 0) || (join_type == 3)) { if (join_type == 0) { @<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) { mp_right_x (r) = mp_x_coord (r); mp_right_y (r) = mp_y_coord (r); } } } @ 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@>= { det = mp_take_fraction (mp, dyout, dxin) - mp_take_fraction (mp, dxout, dyin); if (abs (det) < 26844) { r = NULL; /* sine $<10^{-4}$ */ } else { tmp = mp_take_fraction (mp, mp_x_coord (q) - mp_x_coord (p), dyout) - mp_take_fraction (mp, mp_y_coord (q) - mp_y_coord (p), dxout); tmp = mp_make_fraction (mp, tmp, det); r = mp_insert_knot (mp, p, mp_x_coord (p) + mp_take_fraction (mp, tmp, dxin), mp_y_coord (p) + mp_take_fraction (mp, tmp, dyin)); } } @ @<Other local variables for |make_envelope|@>= fraction det; /* a determinant used for mitered join calculations */ @ @<Make |r| the last of two knots inserted between |p| and |q| to form a...@>= { ht_x = mp_y_coord (w) - mp_y_coord (w0); ht_y = mp_x_coord (w0) - mp_x_coord (w); while ((abs (ht_x) < fraction_half) && (abs (ht_y) < fraction_half)) { ht_x += ht_x; ht_y += ht_y; } @<Scan the pen polygon between |w0| and |w| and make |max_ht| the range dot product with |(ht_x,ht_y)|@>; tmp = mp_make_fraction (mp, max_ht, mp_take_fraction (mp, dxin, ht_x) + mp_take_fraction (mp, dyin, ht_y)); r = mp_insert_knot (mp, p, mp_x_coord (p) + mp_take_fraction (mp, tmp, dxin), mp_y_coord (p) + mp_take_fraction (mp, tmp, dyin)); tmp = mp_make_fraction (mp, max_ht, mp_take_fraction (mp, dxout, ht_x) + mp_take_fraction (mp, dyout, ht_y)); r = mp_insert_knot (mp, r, mp_x_coord (q) + mp_take_fraction (mp, tmp, dxout), mp_y_coord (q) + mp_take_fraction (mp, tmp, dyout)); } @ @<Other local variables for |make_envelope|@>= fraction ht_x, ht_y; /* perpendicular to the segment from |p| to |q| */ scaled max_ht; /* maximum height of the pen polygon above the |w0|-|w| line */ halfword kk; /* keeps track of the pen vertices being scanned */ mp_knot ww; /* 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; while (1) { @<Step |ww| and move |kk| one step closer to |k0|@>; if (kk == k0) break; tmp = mp_take_fraction (mp, (mp_x_coord (ww) - mp_x_coord (w0)), ht_x) + mp_take_fraction (mp, (mp_y_coord (ww) - mp_y_coord (w0)), ht_y); if (tmp > max_ht) max_ht = tmp; } @ @<Step |ww| and move |kk| one step closer to |k0|@>= if (kk > k0) { ww = mp_next_knot (ww); decr (kk); } else { ww = mp_prev_knot (ww); incr (kk); } @ @<If endpoint, double the path |c|, and set |spec_p1| and |spec_p2|@>= if (mp_left_type (c) == mp_endpoint) { mp->spec_p1 = mp_htap_ypoc (mp, c); mp->spec_p2 = mp->path_tail; mp_originator (mp->spec_p1) = mp_program_code; mp_next_knot (mp->spec_p2) = mp_next_knot (mp->spec_p1); mp_next_knot (mp->spec_p1) = c; mp_remove_cubic (mp, mp->spec_p1); c = mp->spec_p1; if (c != mp_next_knot (c)) { mp_originator (mp->spec_p2) = mp_program_code; mp_remove_cubic (mp, mp->spec_p2); } else { @<Make |c| look like a cycle of length one@>; } } @ @<Make |c| look like a cycle of length one@>= { mp_left_type (c) = mp_explicit; mp_right_type (c) = mp_explicit; mp_left_x (c) = mp_x_coord (c); mp_left_y (c) = mp_y_coord (c); mp_right_x (c) = mp_x_coord (c); mp_right_y (c) = mp_y_coord (c); } @ 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 = mp_x_coord (q) - mp_left_x (q); dyin = mp_y_coord (q) - mp_left_y (q); if ((dxin == 0) && (dyin == 0)) { dxin = mp_x_coord (q) - mp_right_x (p); dyin = mp_y_coord (q) - mp_right_y (p); if ((dxin == 0) && (dyin == 0)) { dxin = mp_x_coord (q) - mp_x_coord (p); dyin = mp_y_coord (q) - mp_y_coord (p); if (p != c) { /* the coordinates of |p| have been offset by |w| */ dxin = dxin + mp_x_coord (w); dyin = dyin + mp_y_coord (w); } } } tmp = mp_pyth_add (mp, dxin, dyin); if (tmp == 0) { join_type = 2; } else { dxin = mp_make_fraction (mp, dxin, tmp); dyin = mp_make_fraction (mp, dyin, tmp); @<Set the outgoing direction at |q|@>; } @ 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 = mp_right_x (q) - mp_x_coord (q); dyout = mp_right_y (q) - mp_y_coord (q); if ((dxout == 0) && (dyout == 0)) { r = mp_next_knot (q); dxout = mp_left_x (r) - mp_x_coord (q); dyout = mp_left_y (r) - mp_y_coord (q); if ((dxout == 0) && (dyout == 0)) { dxout = mp_x_coord (r) - mp_x_coord (q); dyout = mp_y_coord (r) - mp_y_coord (q); } } if (q == c) { dxout = dxout - mp_x_coord (h); dyout = dyout - mp_y_coord (h); } tmp = mp_pyth_add (mp, dxout, dyout); if (tmp == 0) mp_confusion (mp, "degenerate spec"); @:this can't happen degerate spec}{\quad degenerate spec@>; dxout = mp_make_fraction (mp, dxout, tmp); dyout = mp_make_fraction (mp, dyout, tmp) @* 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.'' @c static scaled mp_find_direction_time (MP mp, scaled x, scaled y, mp_knot h) { scaled max; /* $\max\bigl(\vert x\vert,\vert y\vert\bigr)$ */ mp_knot p, q; /* for list traversal */ scaled n; /* the direction time at knot |p| */ scaled tt; /* the direction time within a cubic */ @<Other local variables for |find_direction_time|@>; @<Normalize the given direction for better accuracy; but |return| with zero result if it's zero@>; n = 0; p = h; phi = 0; while (1) { if (mp_right_type (p) == mp_endpoint) break; q = mp_next_knot (p); @<Rotate the cubic between |p| and |q|; then |goto found| if the rotated cubic travels due east at some time |tt|; but |break| if an entire cyclic path has been traversed@>; p = q; n = n + unity; } return (-unity); FOUND: return (n + tt); } @ @<Normalize the given direction for better accuracy...@>= if (abs (x) < abs (y)) { x = mp_make_fraction (mp, x, abs (y)); if (y > 0) y = fraction_one; else y = -fraction_one; } else if (x == 0) { return 0; } else { y = mp_make_fraction (mp, y, abs (x)); if (x > 0) x = fraction_one; else x = -fraction_one; } @ Since we're interested in the tangent directions, we work with the derivative $${1\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) if (x1 >= 0) goto FOUND; if (n > 0) { @<Exit to |found| if an eastward direction occurs at knot |p|@>; if (p == h) break; }; if ((x3 != 0) || (y3 != 0)) phi = mp_n_arg (mp, 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|@>= scaled x1, x2, x3, y1, y2, y3; /* multiples of rotated derivatives */ angle theta, phi; /* angles of exit and entry at a knot */ fraction t; /* temp storage */ @ @<Set local variables |x1,x2,x3| and |y1,y2,y3| to multiples...@>= x1 = mp_right_x (p) - mp_x_coord (p); x2 = mp_left_x (q) - mp_right_x (p); x3 = mp_x_coord (q) - mp_left_x (q); y1 = mp_right_y (p) - mp_y_coord (p); y2 = mp_left_y (q) - mp_right_y (p); y3 = mp_y_coord (q) - mp_left_y (q); max = abs (x1); if (abs (x2) > max) max = abs (x2); if (abs (x3) > max) max = abs (x3); if (abs (y1) > max) max = abs (y1); if (abs (y2) > max) max = abs (y2); if (abs (y3) > max) max = abs (y3); if (max == 0) goto FOUND; while (max < fraction_half) { max += max; x1 += x1; x2 += x2; x3 += x3; y1 += y1; y2 += y2; y3 += y3; } t = x1; x1 = mp_take_fraction (mp, x1, x) + mp_take_fraction (mp, y1, y); y1 = mp_take_fraction (mp, y1, x) - mp_take_fraction (mp, t, y); t = x2; x2 = mp_take_fraction (mp, x2, x) + mp_take_fraction (mp, y2, y); y2 = mp_take_fraction (mp, y2, x) - mp_take_fraction (mp, t, y); t = x3; x3 = mp_take_fraction (mp, x3, x) + mp_take_fraction (mp, y3, y); y3 = mp_take_fraction (mp, y3, x) - mp_take_fraction (mp, t, y) @ @<Exit to |found| if an eastward direction occurs at knot |p|@>= theta = mp_n_arg (mp, x1, y1); if (theta >= 0 && phi <= 0 && phi >= theta - one_eighty_deg) goto FOUND; if (theta <= 0 && phi >= 0 && phi <= theta + one_eighty_deg) 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) if (x2 < 0) if (x3 < 0) goto DONE; if (mp_ab_vs_cd (mp, y1, y3, y2, y2) == 0) { @<Handle the test for eastward directions when $y_1y_3=y_2^2$; either |goto found| or |goto done|@>; } if (y1 <= 0) { if (y1 < 0) { y1 = -y1; y2 = -y2; y3 = -y3; } else if (y2 > 0) { y2 = -y2; y3 = -y3; }; } @<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 { tt=(t+04000) / 010000; goto FOUND; } @<Check the places where $B(y_1,y_2,y_3;t)=0$...@>= t = mp_crossing_point (mp, y1, y2, y3); if (t > fraction_one) 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) we_found_it; if (y2 > 0) y2 = 0; tt = t; t = mp_crossing_point (mp, 0, -y2, -y3); if (t > fraction_one) goto DONE; x1 = t_of_the_way (x1, x2); x2 = t_of_the_way (x2, x3); if (t_of_the_way (x1, x2) >= 0) { t = t_of_the_way (tt, fraction_one); we_found_it; } @ @<Handle the test for eastward directions when $y_1y_3=y_2^2$; either |goto found| or |goto done|@>= { if (mp_ab_vs_cd (mp, y1, y2, 0, 0) < 0) { t = mp_make_fraction (mp, 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) we_found_it; } else if (y3 == 0) { if (y1 == 0) { @<Exit to |found| if the derivative $B(x_1,x_2,x_3;t)$ becomes |>=0|@>; } else if (x3 >= 0) { tt = unity; goto FOUND; } } goto DONE; } @ 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|...@>= { t = mp_crossing_point (mp, -x1, -x2, -x3); if (t <= fraction_one) we_found_it; if (mp_ab_vs_cd (mp, x1, x3, x2, x2) <= 0) { t = mp_make_fraction (mp, x1, x1 - x2); we_found_it; } } @ 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 $2^lu\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...@>= unsigned int tol_step; /* 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(A) mp->bisect_stack[(A)] /* $U_1$, $V_1$, $X_1$, or $Y_1$ */ @d stack_2(A) mp->bisect_stack[(A)+1] /* $U_2$, $V_2$, $X_2$, or $Y_2$ */ @d stack_3(A) mp->bisect_stack[(A)+2] /* $U_3$, $V_3$, $X_3$, or $Y_3$ */ @d stack_min(A) mp->bisect_stack[(A)+3] /* $U\submin$, $V\submin$, $X\submin$, or $Y\submin$ */ @d stack_max(A) mp->bisect_stack[(A)+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(A) ((A)-5) @d v_packet(A) ((A)-10) @d x_packet(A) ((A)-15) @d y_packet(A) ((A)-20) @d l_packets (mp->bisect_ptr-int_packets) @d r_packets mp->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 mp->bisect_stack[mp->bisect_ptr] /* stacked value of |delx| */ @d stack_dy mp->bisect_stack[mp->bisect_ptr+1] /* stacked value of |dely| */ @d stack_tol mp->bisect_stack[mp->bisect_ptr+2] /* stacked value of |tol| */ @d stack_uv mp->bisect_stack[mp->bisect_ptr+3] /* stacked value of |uv| */ @d stack_xy mp->bisect_stack[mp->bisect_ptr+4] /* stacked value of |xy| */ @d int_increment (int_packets+int_packets+5) /* number of stack words per level */ @<Glob...@>= integer *bisect_stack; integer bisect_ptr; @ @<Allocate or initialize ...@>= mp->bisect_stack = xmalloc ((bistack_size + 1), sizeof (integer)); @ @<Dealloc variables@>= xfree (mp->bisect_stack); @ @<Check the ``constant''...@>= if (int_packets + 17 * int_increment > bistack_size) mp->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(A) if ( stack_1((A))<0 ) { if ( stack_3((A))>=0 ) { if ( stack_2((A))<0 ) stack_min((A))=stack_1((A))+stack_2((A)); else stack_min((A))=stack_1((A)); stack_max((A))=stack_1((A))+stack_2((A))+stack_3((A)); if ( stack_max((A))<0 ) stack_max((A))=0; } else { stack_min((A))=stack_1((A))+stack_2((A))+stack_3((A)); if ( stack_min((A))>stack_1((A)) ) stack_min((A))=stack_1((A)); stack_max((A))=stack_1((A))+stack_2((A)); if ( stack_max((A))<0 ) stack_max((A))=0; } } else if ( stack_3((A))<=0 ) { if ( stack_2((A))>0 ) stack_max((A))=stack_1((A))+stack_2((A)); else stack_max((A))=stack_1((A)); stack_min((A))=stack_1((A))+stack_2((A))+stack_3((A)); if ( stack_min((A))>0 ) stack_min((A))=0; } else { stack_max((A))=stack_1((A))+stack_2((A))+stack_3((A)); if ( stack_max((A))<stack_1((A)) ) stack_max((A))=stack_1((A)); stack_min((A))=stack_1((A))+stack_2((A)); if ( stack_min((A))>0 ) stack_min((A))=0; } @ 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...@>= integer cur_t; integer cur_tt; /* controls and results of |cubic_intersection| */ integer time_to_go; /* this many backtracks before giving up */ integer max_t; /* 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,mp_link(p))| and |(pp,mp_link(pp))|, respectively. @c static void mp_cubic_intersection (MP mp, mp_knot p, mp_knot pp) { mp_knot q, qq; /* |mp_link(p)|, |mp_link(pp)| */ mp->time_to_go = max_patience; mp->max_t = 2; @<Initialize for intersections at level zero@>; CONTINUE: while (1) { if (mp->delx - mp->tol <= stack_max (x_packet (mp->xy)) - stack_min (u_packet (mp->uv))) if (mp->delx + mp->tol >= stack_min (x_packet (mp->xy)) - stack_max (u_packet (mp->uv))) if (mp->dely - mp->tol <= stack_max (y_packet (mp->xy)) - stack_min (v_packet (mp->uv))) if (mp->dely + mp->tol >= stack_min (y_packet (mp->xy)) - stack_max (v_packet (mp->uv))) { if (mp->cur_t >= mp->max_t) { if (mp->max_t == two) { /* we've done 17 bisections */ mp->cur_t = halfp (mp->cur_t + 1); mp->cur_tt = halfp (mp->cur_tt + 1); return; } mp->max_t += mp->max_t; mp->appr_t = mp->cur_t; mp->appr_tt = mp->cur_tt; } @<Subdivide for a new level of intersection@>; goto CONTINUE; } if (mp->time_to_go > 0) { decr (mp->time_to_go); } else { while (mp->appr_t < unity) { mp->appr_t += mp->appr_t; mp->appr_tt += mp->appr_tt; } mp->cur_t = mp->appr_t; mp->cur_tt = mp->appr_tt; return; } @<Advance to the next pair |(cur_t,cur_tt)|@>; } } @ 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...@>= integer delx; integer dely; /* the components of $\Delta=2^l(w_0-z_0)$ */ integer tol; /* bound on the uncertainty in the overlap test */ integer uv; integer xy; /* pointers to the current packets of interest */ integer three_l; /* |tol_step| times the bisection level */ integer appr_t; integer appr_tt; /* best approximations known to the answers */ @ We shall assume that the coordinates are sufficiently non-extreme that integer overflow will not occur. @^overflow in arithmetic@> @<Initialize for intersections at level zero@>= q = mp_next_knot (p); qq = mp_next_knot (pp); mp->bisect_ptr = int_packets; u1r = mp_right_x (p) - mp_x_coord (p); u2r = mp_left_x (q) - mp_right_x (p); u3r = mp_x_coord (q) - mp_left_x (q); set_min_max (ur_packet); v1r = mp_right_y (p) - mp_y_coord (p); v2r = mp_left_y (q) - mp_right_y (p); v3r = mp_y_coord (q) - mp_left_y (q); set_min_max (vr_packet); x1r = mp_right_x (pp) - mp_x_coord (pp); x2r = mp_left_x (qq) - mp_right_x (pp); x3r = mp_x_coord (qq) - mp_left_x (qq); set_min_max (xr_packet); y1r = mp_right_y (pp) - mp_y_coord (pp); y2r = mp_left_y (qq) - mp_right_y (pp); y3r = mp_y_coord (qq) - mp_left_y (qq); set_min_max (yr_packet); mp->delx = mp_x_coord (p) - mp_x_coord (pp); mp->dely = mp_y_coord (p) - mp_y_coord (pp); mp->tol = 0; mp->uv = r_packets; mp->xy = r_packets; mp->three_l = 0; mp->cur_t = 1; mp->cur_tt = 1 @ @<Subdivide for a new level of intersection@>= stack_dx = mp->delx; stack_dy = mp->dely; stack_tol = mp->tol; stack_uv = mp->uv; stack_xy = mp->xy; mp->bisect_ptr = mp->bisect_ptr + int_increment; mp->cur_t += mp->cur_t; mp->cur_tt += mp->cur_tt; u1l = stack_1 (u_packet (mp->uv)); u3r = stack_3 (u_packet (mp->uv)); u2l = half (u1l + stack_2 (u_packet (mp->uv))); u2r = half (u3r + stack_2 (u_packet (mp->uv))); u3l = half (u2l + u2r); u1r = u3l; set_min_max (ul_packet); set_min_max (ur_packet); v1l = stack_1 (v_packet (mp->uv)); v3r = stack_3 (v_packet (mp->uv)); v2l = half (v1l + stack_2 (v_packet (mp->uv))); v2r = half (v3r + stack_2 (v_packet (mp->uv))); v3l = half (v2l + v2r); v1r = v3l; set_min_max (vl_packet); set_min_max (vr_packet); x1l = stack_1 (x_packet (mp->xy)); x3r = stack_3 (x_packet (mp->xy)); x2l = half (x1l + stack_2 (x_packet (mp->xy))); x2r = half (x3r + stack_2 (x_packet (mp->xy))); x3l = half (x2l + x2r); x1r = x3l; set_min_max (xl_packet); set_min_max (xr_packet); y1l = stack_1 (y_packet (mp->xy)); y3r = stack_3 (y_packet (mp->xy)); y2l = half (y1l + stack_2 (y_packet (mp->xy))); y2r = half (y3r + stack_2 (y_packet (mp->xy))); y3l = half (y2l + y2r); y1r = y3l; set_min_max (yl_packet); set_min_max (yr_packet); mp->uv = l_packets; mp->xy = l_packets; mp->delx += mp->delx; mp->dely += mp->dely; mp->tol = mp->tol - mp->three_l + (integer) mp->tol_step; mp->tol += mp->tol; mp->three_l = mp->three_l + (integer) mp->tol_step @ @<Advance to the next pair |(cur_t,cur_tt)|@>= NOT_FOUND: if (odd (mp->cur_tt)) { if (odd (mp->cur_t)) { @<Descend to the previous level and |goto not_found|@>; } else { incr (mp->cur_t); mp->delx = mp->delx + stack_1 (u_packet (mp->uv)) + stack_2 (u_packet (mp->uv)) + stack_3 (u_packet (mp->uv)); mp->dely = mp->dely + stack_1 (v_packet (mp->uv)) + stack_2 (v_packet (mp->uv)) + stack_3 (v_packet (mp->uv)); mp->uv = mp->uv + int_packets; /* switch from |l_packets| to |r_packets| */ decr (mp->cur_tt); mp->xy = mp->xy - int_packets; /* switch from |r_packets| to |l_packets| */ mp->delx = mp->delx + stack_1 (x_packet (mp->xy)) + stack_2 (x_packet (mp->xy)) + stack_3 (x_packet (mp->xy)); mp->dely = mp->dely + stack_1 (y_packet (mp->xy)) + stack_2 (y_packet (mp->xy)) + stack_3 (y_packet (mp->xy)); } } else { incr (mp->cur_tt); mp->tol = mp->tol + mp->three_l; mp->delx = mp->delx - stack_1 (x_packet (mp->xy)) - stack_2 (x_packet (mp->xy)) - stack_3 (x_packet (mp->xy)); mp->dely = mp->dely - stack_1 (y_packet (mp->xy)) - stack_2 (y_packet (mp->xy)) - stack_3 (y_packet (mp->xy)); mp->xy = mp->xy + int_packets; /* switch from |l_packets| to |r_packets| */ } @ @<Descend to the previous level...@>= { mp->cur_t = halfp (mp->cur_t); mp->cur_tt = halfp (mp->cur_tt); if (mp->cur_t == 0) return; mp->bisect_ptr -= int_increment; mp->three_l -= (integer) mp->tol_step; mp->delx = stack_dx; mp->dely = stack_dy; mp->tol = stack_tol; mp->uv = stack_uv; mp->xy = stack_xy; goto NOT_FOUND; } @ 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|. @c static void mp_path_intersection (MP mp, mp_knot h, mp_knot hh) { mp_knot p, pp; /* link registers that traverse the given paths */ integer n, nn; /* integer parts of intersection times, minus |unity| */ @<Change one-point paths into dead cycles@>; mp->tol_step = 0; do { n = -unity; p = h; do { if (mp_right_type (p) != mp_endpoint) { nn = -unity; pp = hh; do { if (mp_right_type (pp) != mp_endpoint) { mp_cubic_intersection (mp, p, pp); if (mp->cur_t > 0) { mp->cur_t = mp->cur_t + n; mp->cur_tt = mp->cur_tt + nn; return; } } nn = nn + unity; pp = mp_next_knot (pp); } while (pp != hh); } n = n + unity; p = mp_next_knot (p); } while (p != h); mp->tol_step = mp->tol_step + 3; } while (mp->tol_step <= 3); mp->cur_t = -unity; mp->cur_tt = -unity; } @ @<Change one-point paths...@>= if (mp_right_type (h) == mp_endpoint) { mp_right_x (h) = mp_x_coord (h); mp_left_x (h) = mp_x_coord (h); mp_right_y (h) = mp_y_coord (h); mp_left_y (h) = mp_y_coord (h); mp_right_type (h) = mp_explicit; } if (mp_right_type (hh) == mp_endpoint) { mp_right_x (hh) = mp_x_coord (hh); mp_left_x (hh) = mp_x_coord (hh); mp_right_y (hh) = mp_y_coord (hh); mp_left_y (hh) = mp_y_coord (hh); mp_right_type (hh) = mp_explicit; } @* 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)=mp_known| is the nice case, when |value(p)| is the |scaled| value of the variable whose address is~|p|. \smallskip\hang |type(p)=mp_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)=mp_independent| means that |value(p)=s|, where |s>0| is a ``serial number'' reflecting the time this variable was first used in an equation; and there is an extra field |indep_scale(p)=m|, with |0<=m<64|, 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)=mp_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 |mp_independent|; once |mp_independent|, it never again becomes |mp_numeric_type|; and once |mp_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 |mp_independent| again, when one of the independent variables it depends on is reverting to |undefined|. @d indep_scale(A) ((mp_value_node)(A))->data.scale @d set_indep_scale(A,B) ((mp_value_node)(A))->data.scale=(B) @d new_indep(A) /* create a new independent variable */ { if ( mp->serial_no>=max_integer ) mp_fatal_error(mp, "variable instance identifiers exhausted"); mp_type((A))=mp_independent; mp->serial_no=mp->serial_no+1; set_indep_scale((A),0); set_value((A),mp->serial_no); } @<Glob...@>= integer serial_no; /* the most recent serial number */ @ 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 |dep_value(q)= @t$\alpha_1$@>| (which is a |fraction|); |dep_info(q)| points to the location of $\alpha_1$; and |mp_link(p)| points to the dependency list $\alpha_2v_2+\cdots+\alpha_kv_k+\beta$. On the other hand if |k=0|, then |dep_value(q)=@t$\beta$@>| (which is |scaled|) and |dep_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 |mp_link(dep_head)=dep_head| and |prev_dep(dep_head)=dep_head|; otherwise |mp_link(dep_head)| points to the first dependent variable, say~|p|, and |prev_dep(p)=dep_head|. We have |type(p)=mp_dependent|, and |dep_list(p)| points to its dependency list. If the final link of that dependency list occurs in location~|q|, then |mp_link(q)| points to the next dependent variable (say~|r|); and we have |prev_dep(r)=q|, etc. Dependency nodes sometimes mutate into value nodes and vice versa, so their structures have to match. @d dep_value(A) ((mp_value_node)(A))->data.val /* half of the |value| field in a |dependent| variable */ @d set_dep_value(A,B) do { ((mp_value_node)(A))->data.val=(B); /* half of the |value| field in a |dependent| variable */ FUNCTION_TRACE4("set_dep_value(%p,%d) on %d\n",A,B,__LINE__); set_dep_list((A), NULL); set_prev_dep((A), NULL); } while (0) @d dep_info(A) get_dep_info(mp,(A)) @d set_dep_info(A,B) do { mp_value_node d = (mp_value_node)(B); FUNCTION_TRACE4("set_dep_info(%p,%p) on %d\n",A,d,__LINE__); ((mp_value_node)(A))->parent_ = (mp_node)d; } while (0) @d dep_list(A) ((mp_value_node)(A))->attr_head_ /* half of the |value| field in a |dependent| variable */ @d set_dep_list(A,B) do { mp_value_node d = (mp_value_node)(B); FUNCTION_TRACE4("set_dep_list(%p,%p) on %d\n",A,d,__LINE__); dep_list((A)) = (mp_node)d; } while (0) @d prev_dep(A) ((mp_value_node)(A))->subscr_head_ /* the other half; makes a doubly linked list */ @d set_prev_dep(A,B) do { mp_value_node d = (mp_value_node)(B); FUNCTION_TRACE4("set_prev_dep(%p,%p) on %d\n",A,d,__LINE__); prev_dep((A)) = (mp_node)d; } while (0) @c static mp_node get_dep_info (MP mp, mp_value_node p) { mp_node d; d = p->parent_; /* half of the |value| field in a |dependent| variable */ FUNCTION_TRACE3 ("%p = dep_info(%p)\n", d, p); return d; } @ @<Declarations...@>= static mp_node get_dep_info (MP mp, mp_value_node p); @ @c static mp_value_node mp_get_dep_node (MP mp) { mp_value_node p = (mp_value_node) mp_get_value_node (mp); mp_type (p) = mp_dep_node_type; return p; } static void mp_free_dep_node (MP mp, mp_value_node p) { mp_free_node (mp, (mp_node) p, value_node_size); } @ @<Declarations...@>= static void mp_free_dep_node (MP mp, mp_value_node p); @ @<Initialize table entries@>= mp->serial_no = 0; mp->dep_head = mp_get_dep_node (mp); set_mp_link (mp->dep_head, (mp_node) mp->dep_head); set_prev_dep (mp->dep_head, (mp_node) mp->dep_head); set_dep_info (mp->dep_head, NULL); set_dep_list (mp->dep_head, NULL); @ @<Free table entries@>= mp_free_dep_node (mp, mp->dep_head); @ Actually the description above contains a little white lie. There's another kind of variable called |mp_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 |mp_proto_dependent|, to indicate the scaling of the coefficients. @<Declarations@>= static void mp_print_dependency (MP mp, mp_value_node p, quarterword t); @ @c void mp_print_dependency (MP mp, mp_value_node p, quarterword t) { integer v; /* a coefficient */ mp_value_node pp; /* for list manipulation */ mp_node q; pp = p; while (true) { v = abs (dep_value (p)); q = dep_info (p); if (q == NULL) { /* the constant term */ if ((v != 0) || (p == pp)) { if (dep_value (p) > 0) if (p != pp) mp_print_char (mp, xord ('+')); mp_print_scaled (mp, dep_value (p)); } return; } @<Print the coefficient, unless it's $\pm1.0$@>; if (mp_type (q) != mp_independent) mp_confusion (mp, "dep"); @:this can't happen dep}{\quad dep@>; mp_print_variable_name (mp, q); v = indep_scale(q); while (v > 0) { mp_print (mp, "*4"); v = v - 2; } p = (mp_value_node) mp_link (p); } } @ @<Print the coefficient, unless it's $\pm1.0$@>= if (dep_value (p) < 0) mp_print_char (mp, xord ('-')); else if (p != pp) mp_print_char (mp, xord ('+')); if (t == mp_dependent) v = mp_round_fraction (mp, v); if (v != unity) mp_print_scaled (mp, v) @ The maximum absolute value of a coefficient in a given dependency list is returned by the following simple function. @c static fraction mp_max_coef (MP mp, mp_value_node p) { fraction x; /* the maximum so far */ (void) mp; x = 0; while (dep_info (p) != NULL) { if (abs (dep_value (p)) > x) x = abs (dep_value (p)); p = (mp_value_node) mp_link (p); } return x; } @ 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 04525252525 /* |fraction| approximation to 7/3 */ @d independent_needing_fix 0 @<Glob...@>= boolean fix_needed; /* does at least one |independent| variable need scaling? */ boolean watch_coefs; /* should we scale coefficients that exceed |coef_bound|? */ mp_value_node dep_final; /* location of the constant term and final link */ @ @<Set init...@>= mp->fix_needed = false; mp->watch_coefs = true; @ The |p_plus_fq| procedure has a fourth parameter, |t|, that should be set to |mp_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 |mp_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| */ @<Declarations@>= static mp_value_node mp_p_plus_fq (MP mp, mp_value_node p, integer f, mp_value_node q, mp_variable_type t, mp_variable_type tt); @ @c static mp_value_node mp_p_plus_fq (MP mp, mp_value_node p, integer f, mp_value_node q, mp_variable_type t, mp_variable_type tt) { mp_node pp, qq; /* |dep_info(p)| and |dep_info(q)|, respectively */ mp_value_node r, s; /* for list manipulation */ integer threshold; /* defines a neighborhood of zero */ integer v, vv; /* temporary registers */ if (t == mp_dependent) threshold = fraction_threshold; else threshold = scaled_threshold; r = (mp_value_node) mp->temp_head; pp = dep_info (p); qq = dep_info (q); while (1) { if (pp == qq) { if (pp == NULL) { break; } else { @<Contribute a term from |p|, plus |f| times the corresponding term from |q|@> } } else { v = (pp == NULL ? 0 : value (pp)); vv = (qq == NULL ? 0 : value (qq)); if (v < vv) { @<Contribute a term from |q|, multiplied by~|f|@> } else { set_mp_link (r, (mp_node) p); r = p; p = (mp_value_node) mp_link (p); pp = dep_info (p); } } } if (t == mp_dependent) set_dep_value (p, mp_slow_add (mp, dep_value (p), mp_take_fraction (mp, dep_value (q), f))); else set_dep_value (p, mp_slow_add (mp, dep_value (p), mp_take_scaled (mp, dep_value (q), f))); set_mp_link (r, (mp_node) p); mp->dep_final = p; return (mp_value_node) mp_link (mp->temp_head); } @ @<Contribute a term from |p|, plus |f|...@>= { if (tt == mp_dependent) v = dep_value (p) + mp_take_fraction (mp, f, dep_value (q)); else v = dep_value (p) + mp_take_scaled (mp, f, dep_value (q)); set_dep_value (p, v); s = p; p = (mp_value_node) mp_link (p); if (abs (v) < threshold) { mp_free_dep_node (mp, s); } else { if ((abs (v) >= coef_bound) && mp->watch_coefs) { mp_type (qq) = independent_needing_fix; mp->fix_needed = true; } set_mp_link (r, (mp_node) s); r = s; } pp = dep_info (p); q = (mp_value_node) mp_link (q); qq = dep_info (q); } @ @<Contribute a term from |q|, multiplied by~|f|@>= { if (tt == mp_dependent) v = mp_take_fraction (mp, f, dep_value (q)); else v = mp_take_scaled (mp, f, dep_value (q)); if (abs (v) > halfp (threshold)) { s = mp_get_dep_node (mp); set_dep_info (s, qq); set_dep_value (s, v); if ((abs (v) >= coef_bound) && mp->watch_coefs) { mp_type (qq) = independent_needing_fix; mp->fix_needed = true; } set_mp_link (r, (mp_node) s); r = s; } q = (mp_value_node) mp_link (q); qq = dep_info (q); } @ 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 |mp_proto_dependent|). @c static mp_value_node mp_p_plus_q (MP mp, mp_value_node p, mp_value_node q, mp_variable_type t) { mp_node pp, qq; /* |dep_info(p)| and |dep_info(q)|, respectively */ mp_value_node s; /* for list manipulation */ mp_value_node r; /* for list manipulation */ integer threshold; /* defines a neighborhood of zero */ integer v, vv; /* temporary register */ if (t == mp_dependent) threshold = fraction_threshold; else threshold = scaled_threshold; r = (mp_value_node) mp->temp_head; pp = dep_info (p); qq = dep_info (q); while (1) { if (pp == qq) { if (pp == NULL) { break; } else { @<Contribute a term from |p|, plus the corresponding term from |q|@> } } else { v = (pp == NULL ? 0 : value (pp)); vv = (qq == NULL ? 0 : value (qq)); if (v < vv) { s = mp_get_dep_node (mp); set_dep_info (s, qq); set_dep_value (s, dep_value (q)); q = (mp_value_node) mp_link (q); qq = dep_info (q); set_mp_link (r, (mp_node) s); r = s; } else { set_mp_link (r, (mp_node) p); r = p; p = (mp_value_node) mp_link (p); pp = dep_info (p); } } } set_dep_value (p, mp_slow_add (mp, dep_value (p), dep_value (q))); set_mp_link (r, (mp_node) p); mp->dep_final = p; return (mp_value_node) mp_link (mp->temp_head); } @ @<Contribute a term from |p|, plus the...@>= { v = dep_value (p) + dep_value (q); set_dep_value (p, v); s = p; p = (mp_value_node) mp_link (p); pp = dep_info (p); if (abs (v) < threshold) { mp_free_dep_node (mp, s); } else { if ((abs (v) >= coef_bound) && mp->watch_coefs) { mp_type (qq) = independent_needing_fix; mp->fix_needed = true; } set_mp_link (r, (mp_node) s); r = s; } q = (mp_value_node) mp_link (q); qq = dep_info (q); } @ 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=mp_dependent| and |t1=mp_proto_dependent| and |v_is_scaled=true|. @c static mp_value_node mp_p_times_v (MP mp, mp_value_node p, integer v, quarterword t0, quarterword t1, boolean v_is_scaled) { mp_value_node r, s; /* for list manipulation */ integer w; /* tentative coefficient */ integer threshold; boolean scaling_down; if (t0 != t1) scaling_down = true; else scaling_down = (!v_is_scaled); if (t1 == mp_dependent) threshold = half_fraction_threshold; else threshold = half_scaled_threshold; r = (mp_value_node) mp->temp_head; while (dep_info (p) != NULL) { if (scaling_down) w = mp_take_fraction (mp, v, dep_value (p)); else w = mp_take_scaled (mp, v, dep_value (p)); if (abs (w) <= threshold) { s = (mp_value_node) mp_link (p); mp_free_dep_node (mp, p); p = s; } else { if (abs (w) >= coef_bound) { mp->fix_needed = true; mp_type (dep_info (p)) = independent_needing_fix; } set_mp_link (r, (mp_node) p); r = p; set_dep_value (p, w); p = (mp_value_node) mp_link (p); } } set_mp_link (r, (mp_node) p); if (v_is_scaled) set_dep_value (p, mp_take_scaled (mp, dep_value (p), v)); else set_dep_value (p, mp_take_fraction (mp, dep_value (p), v)); return (mp_value_node) mp_link (mp->temp_head); } @ Similarly, we sometimes need to divide a dependency list by a given |scaled| constant. @<Declarations@>= static mp_value_node mp_p_over_v (MP mp, mp_value_node p, scaled v, quarterword t0, quarterword t1); @ @c mp_value_node mp_p_over_v (MP mp, mp_value_node p, scaled v, quarterword t0, quarterword t1) { mp_value_node r, s; /* for list manipulation */ integer w; /* tentative coefficient */ integer threshold; boolean scaling_down; if (t0 != t1) scaling_down = true; else scaling_down = false; if (t1 == mp_dependent) threshold = half_fraction_threshold; else threshold = half_scaled_threshold; r = (mp_value_node) mp->temp_head; while (dep_info (p) != NULL) { if (scaling_down) { if (abs (v) < 02000000) w = mp_make_scaled (mp, dep_value (p), v * 010000); else w = mp_make_scaled (mp, mp_round_fraction (mp, dep_value (p)), v); } else { w = mp_make_scaled (mp, dep_value (p), v); } if (abs (w) <= threshold) { s = (mp_value_node) mp_link (p); mp_free_dep_node (mp, p); p = s; } else { if (abs (w) >= coef_bound) { mp->fix_needed = true; mp_type (dep_info (p)) = independent_needing_fix; } set_mp_link (r, (mp_node) p); r = p; set_dep_value (p, w); p = (mp_value_node) mp_link (p); } } set_mp_link (r, (mp_node) p); set_dep_value (p, mp_make_scaled (mp, dep_value (p), v)); return (mp_value_node) mp_link (mp->temp_head); } @ 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 |mp_proto_dependent|. However, the global variable |dep_final| is not altered if |x| does not occur in list~|p|. @c static mp_value_node mp_p_with_x_becoming_q (MP mp, mp_value_node p, mp_node x, mp_node q, quarterword t) { mp_value_node r, s; /* for list manipulation */ integer v; /* coefficient of |x| */ integer sx; /* serial number of |x| */ s = p; r = (mp_value_node) mp->temp_head; sx = value (x); while (dep_info (s) != NULL && value (dep_info (s)) > sx) { r = s; s = (mp_value_node) mp_link (s); } if (dep_info (s) != x) { return p; } else { set_mp_link (mp->temp_head, (mp_node) p); set_mp_link (r, mp_link (s)); v = dep_value (s); mp_free_dep_node (mp, s); return mp_p_plus_fq (mp, (mp_value_node) mp_link (mp->temp_head), v, (mp_value_node) q, t, mp_dependent); } } @ 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. @<Declarations@>= static void mp_val_too_big (MP mp, scaled x); @ @c void mp_val_too_big (MP mp, scaled x) { if (internal_value (mp_warning_check) > 0) { print_err ("Value is too large ("); mp_print_scaled (mp, x); mp_print_char (mp, xord (')')); @.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.)"); mp_error (mp); } } @ 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). @<Declarations@>= static void mp_make_known (MP mp, mp_value_node p, mp_value_node q); @ @c void mp_make_known (MP mp, mp_value_node p, mp_value_node q) { mp_variable_type t; /* the previous type */ set_prev_dep (mp_link (q), prev_dep (p)); set_mp_link (prev_dep (p), mp_link (q)); t = mp_type (p); mp_type (p) = mp_known; set_value (p, dep_value (q)); mp_free_dep_node (mp, q); if (abs (value (p)) >= fraction_one) mp_val_too_big (mp, value (p)); if ((internal_value (mp_tracing_equations) > 0) && mp_interesting (mp, (mp_node) p)) { mp_begin_diagnostic (mp); mp_print_nl (mp, "#### "); @:]]]\#\#\#\#_}{\.{\#\#\#\#}@>; mp_print_variable_name (mp, (mp_node) p); mp_print_char (mp, xord ('=')); mp_print_scaled (mp, value (p)); mp_end_diagnostic (mp, false); } if (cur_exp_node () == (mp_node) p && mp->cur_exp.type == t) { mp->cur_exp.type = mp_known; set_cur_exp_value (value (p)); mp_free_node (mp, (mp_node) p, value_node_size); } } @ 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. @<Declarations@>= static void mp_fix_dependencies (MP mp); @ @c static void mp_fix_dependencies (MP mp) { mp_value_node p, q, r, s, t; /* list manipulation registers */ mp_node x; /* an independent variable */ r = (mp_value_node) mp_link (mp->dep_head); s = NULL; while (r != mp->dep_head) { t = r; @<Run through the dependency list for variable |t|, fixing all nodes, and ending with final link~|q|@>; r = (mp_value_node) mp_link (q); if (q == (mp_value_node) dep_list (t)) mp_make_known (mp, t, q); } while (s != NULL) { p = (mp_value_node) mp_link (s); x = dep_info (s); mp_free_dep_node (mp, s); s = p; mp_type (x) = mp_independent; set_indep_scale (x, indep_scale (x) + 2); } mp->fix_needed = false; } @ @d independent_being_fixed 1 /* this variable already appears in |s| */ @<Run through the dependency list for variable |t|...@>= set_mp_link (r, dep_list (t)); /* start off one item before the actual |dep_list| */ while (1) { q = (mp_value_node) mp_link (r); x = dep_info (q); if (x == NULL) break; if (mp_type (x) <= independent_being_fixed) { if (mp_type (x) < independent_being_fixed) { p = mp_get_dep_node (mp); set_mp_link (p, (mp_node) s); s = p; set_dep_info (s, x); mp_type (x) = independent_being_fixed; } set_dep_value (q, dep_value (q) / 4); if (dep_value (q) == 0) { set_mp_link (r, mp_link (q)); mp_free_dep_node (mp, q); q = r; } } r = q; } @ The |new_dep| routine installs a dependency list~|p| based on the value node~|q|, linking it into the list of all known dependencies. It replaces |q| with the new dependency node. We assume that |dep_final| points to the final node of list~|p|. @c static void mp_new_dep (MP mp, mp_node q, mp_variable_type newtype, mp_value_node p) { mp_node r; /* what used to be the first dependency */ FUNCTION_TRACE4 ("mp_new_dep(%p,%d,%p)\n", q, newtype, p); mp_type (q) = newtype; set_dep_list (q, (mp_node) p); set_prev_dep (q, (mp_node) mp->dep_head); r = mp_link (mp->dep_head); set_mp_link (mp->dep_final, r); set_prev_dep (r, (mp_node) mp->dep_final); set_mp_link (mp->dep_head, q); } @ 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. @c static mp_value_node mp_const_dependency (MP mp, scaled v) { mp->dep_final = mp_get_dep_node (mp); set_dep_value (mp->dep_final, v); set_dep_info (mp->dep_final, NULL); FUNCTION_TRACE3 ("%p = mp_const_dependency(%d)\n", mp->dep_final, v); return mp->dep_final; } @ 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|. @d two_to_the(A) (1<<(unsigned)(A)) @c static mp_value_node mp_single_dependency (MP mp, mp_node p) { mp_value_node q, rr; /* the new dependency list */ integer m; /* the number of doublings */ m = indep_scale (p); if (m > 28) { q = mp_const_dependency (mp, 0); } else { q = mp_get_dep_node (mp); set_dep_value (q, (integer) two_to_the (28 - m)); set_dep_info (q, p); rr = mp_const_dependency (mp, 0); set_mp_link (q, (mp_node) rr); } FUNCTION_TRACE3 ("%p = mp_single_dependency(%p)\n", q, p); return q; } @ We sometimes need to make an exact copy of a dependency list. @c static mp_value_node mp_copy_dep_list (MP mp, mp_value_node p) { mp_value_node q; /* the new dependency list */ FUNCTION_TRACE2 ("mp_copy_dep_list(%p)\n", p); q = mp_get_dep_node (mp); mp->dep_final = q; while (1) { set_dep_info (mp->dep_final, dep_info (p)); set_dep_value (mp->dep_final, dep_value (p)); if (dep_info (mp->dep_final) == NULL) break; set_mp_link (mp->dep_final, (mp_node) mp_get_dep_node (mp)); mp->dep_final = (mp_value_node) mp_link (mp->dep_final); p = (mp_value_node) mp_link (p); } return q; } @ 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 |mp_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. @c static void mp_linear_eq (MP mp, mp_value_node p, quarterword t) { mp_value_node q, r; /* for link manipulation */ mp_value_node s; mp_node x; /* the variable that loses its independence */ integer n; /* the number of times |x| had been halved */ integer v; /* the coefficient of |x| in list |p| */ mp_value_node prev_r; /* lags one step behind |r| */ mp_value_node final_node; /* the constant term of the new dependency list */ integer w; /* a tentative coefficient */ FUNCTION_TRACE3 ("mp_linear_eq(%p,%d)\n", p, t); @<Find a node |q| in list |p| whose coefficient |v| is largest@>; x = dep_info (q); n = indep_scale (x); @<Divide list |p| by |-v|, removing node |q|@>; if (internal_value (mp_tracing_equations) > 0) { @<Display the new dependency@>; } @<Simplify all existing dependencies by substituting for |x|@>; @<Change variable |x| from |independent| to |dependent| or |known|@>; if (mp->fix_needed) mp_fix_dependencies (mp); } @ @<Find a node |q| in list |p| whose coefficient |v| is largest@>= q = p; r = (mp_value_node) mp_link (p); v = dep_value (q); while (dep_info (r) != NULL) { if (abs (dep_value (r)) > abs (v)) { q = r; v = dep_value (r); } r = (mp_value_node) mp_link (r); } @ 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=mp_dependent|. @<Divide list |p| by |-v|, removing node |q|@>= s = (mp_value_node) mp->temp_head; set_mp_link (s, (mp_node) p); r = p; do { if (r == q) { set_mp_link (s, mp_link (r)); mp_free_dep_node (mp, r); } else { w = mp_make_fraction (mp, dep_value (r), v); if (abs (w) <= half_fraction_threshold) { set_mp_link (s, mp_link (r)); mp_free_dep_node (mp, r); } else { set_dep_value (r, -w); s = r; } } r = (mp_value_node) mp_link (s); } while (dep_info (r) != NULL); if (t == mp_proto_dependent) { set_dep_value (r, (-mp_make_scaled (mp, dep_value (r), v))); } else if (v != -fraction_one) { set_dep_value (r, (-mp_make_fraction (mp, dep_value (r), v))); } final_node = r; p = (mp_value_node) mp_link (mp->temp_head) @ @<Display the new dependency@>= if (mp_interesting (mp, (mp_node) x)) { mp_begin_diagnostic (mp); mp_print_nl (mp, "## "); mp_print_variable_name (mp, (mp_node) x); @:]]]\#\#_}{\.{\#\#}@>; w = n; while (w > 0) { mp_print (mp, "*4"); w = w - 2; } mp_print_char (mp, xord ('=')); mp_print_dependency (mp, p, mp_dependent); mp_end_diagnostic (mp, false); } @ @<Simplify all existing dependencies by substituting for |x|@>= prev_r = (mp_value_node) mp->dep_head; r = (mp_value_node) mp_link (mp->dep_head); while (r != mp->dep_head) { s = (mp_value_node) dep_list (r); q = mp_p_with_x_becoming_q (mp, s, x, (mp_node) p, mp_type (r)); if (dep_info (q) == NULL) { mp_make_known (mp, r, q); } else { set_dep_list (r, (mp_node) q); do { q = (mp_value_node) mp_link (q); } while (dep_info (q) != NULL); prev_r = q; } r = (mp_value_node) mp_link (prev_r); } @ @<Change variable |x| from |independent| to |dependent| or |known|@>= if (n > 0) @<Divide list |p| by $2^n$@>; if (dep_info (p) == NULL) { mp_type (x) = mp_known; set_value (x, dep_value (p)); if (abs (value (x)) >= fraction_one) mp_val_too_big (mp, value (x)); mp_free_dep_node (mp, p); if (cur_exp_node () == x && mp->cur_exp.type == mp_independent) { set_cur_exp_value (value (x)); mp->cur_exp.type = mp_known; mp_free_node (mp, x, value_node_size); } } else { mp->dep_final = final_node; mp_new_dep (mp, x, mp_dependent, p); if (cur_exp_node () == x && mp->cur_exp.type == mp_independent) { mp->cur_exp.type = mp_dependent; } } @ @<Divide list |p| by $2^n$@>= { s = (mp_value_node) mp->temp_head; set_mp_link (mp->temp_head, (mp_node) p); r = p; do { if (n > 30) w = 0; else w = dep_value (r) / two_to_the (n); if ((abs (w) <= half_fraction_threshold) && (dep_info (r) != NULL)) { set_mp_link (s, mp_link (r)); mp_free_dep_node (mp, r); } else { set_dep_value (r, w); s = r; } r = (mp_value_node) mp_link (s); } while (dep_info (s) != NULL); p = (mp_value_node) mp_link (mp->temp_head); } @* 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 |mp_unknown_boolean|; similarly, there are variables whose type is |mp_unknown_string|, |mp_unknown_pen|, |mp_unknown_path|, and |mp_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). @c static mp_node mp_new_ring_entry (MP mp, mp_node p) { mp_node q; /* the new capsule node */ q = mp_get_value_node (mp); mp_name_type (q) = mp_capsule; mp_type (q) = mp_type (p); if (value_node (p) == NULL) set_value_node (q, p); else set_value_node (q, value_node (p)); set_value_node (p, q); return q; } @ 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. @<Declarations@>= static void mp_ring_delete (MP mp, mp_node p); @ @c void mp_ring_delete (MP mp, mp_node p) { mp_node q; (void) mp; q = value_node (p); if (q != NULL && q != p) { while (value_node (q) != p) q = value_node (q); set_value_node (q, value_node (p)); } } @ 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. @c static void mp_nonlinear_eq (MP mp, mp_value v, mp_node p, boolean flush_p) { mp_variable_type t; /* the type of ring |p| */ mp_node q, r; /* link manipulation registers */ t = (mp_type (p) - unknown_tag); q = value_node (p); if (flush_p) mp_type (p) = mp_vacuous; else p = q; do { r = value_node (q); mp_type (q) = t; switch (t) { case mp_boolean_type: set_value (q, v.data.val); break; case mp_string_type: set_str_value (q, v.data.str); add_str_ref (v.data.str); break; case mp_pen_type: set_knot_value (q, copy_pen (v.data.p)); break; case mp_path_type: set_knot_value (q, mp_copy_path (mp, v.data.p)); break; case mp_picture_type: set_value_node (q, v.data.node); add_edge_ref (v.data.node); break; default: break; } /* there ain't no more cases */ q = r; } while (q != p); } @ 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. @c static void mp_ring_merge (MP mp, mp_node p, mp_node q) { mp_node r; /* traverses one list */ r = value_node (p); while (r != p) { if (r == q) { @<Exclaim about a redundant equation@>; return; }; r = value_node (r); } r = value_node (p); set_value_node (p, value_node (q)); set_value_node (q, r); } @ @<Exclaim about a redundant equation@>= { 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."); mp_put_get_error (mp); } @* 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...@>= integer cur_cmd; /* current command set by |get_next| */ integer cur_mod; /* operand of current command */ mp_node cur_mod_node; /* operand of current command, if it is a node */ str_number cur_mod_str; /* operand of current command, if it is a string */ mp_sym cur_sym; /* the current symbol */ mp_sym cur_sym2; /* a secondary symbol, needed for use of \&{delimiters} */ quarterword cur_sym_mod; /* the |name_type| of |param_stack| cases of |cur_sym| */ @ 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. @<Declarations@>= static void mp_print_cmd_mod (MP mp, integer c, integer m); @ @c void mp_print_cmd_mod (MP mp, integer c, integer m) { switch (c) { @<Cases of |print_cmd_mod| for symbolic printing of primitives@> default: mp_print (mp, "[unknown command code!]"); break; } } @ Here is a procedure that displays a given command in braces, in the user's transcript file. @d show_cur_cmd_mod mp_show_cmd_mod(mp, mp->cur_cmd,mp->cur_mod) @c static void mp_show_cmd_mod (MP mp, integer c, integer m) { mp_begin_diagnostic (mp); mp_print_nl (mp, "{"); mp_print_cmd_mod (mp, c, m); mp_print_char (mp, xord ('}')); mp_end_diagnostic (mp, false); } @* 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...@>= typedef struct { char *long_name_field; halfword start_field, loc_field, limit_field; mp_node nstart_field, nloc_field; str_number name_field; quarterword index_field; } in_state_record; @ @<Glob...@>= in_state_record *input_stack; integer input_ptr; /* first unused location of |input_stack| */ integer max_in_stack; /* largest value of |input_ptr| when pushing */ in_state_record cur_input; /* the ``top'' input state */ int stack_size; /* maximum number of simultaneous input sources */ @ @<Allocate or initialize ...@>= mp->stack_size = 16; mp->input_stack = xmalloc ((mp->stack_size + 1), sizeof (in_state_record)); @ @<Dealloc variables@>= xfree (mp->input_stack); @ 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 iindex mp->cur_input.index_field /* reference for buffer information */ @d start mp->cur_input.start_field /* starting position in |buffer| */ @d limit mp->cur_input.limit_field /* end of current line in |buffer| */ @d name mp->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 (str_number)0 /* |name| value when reading from the terminal for normal input */ @d is_read (str_number)1 /* |name| value when executing a \&{readstring} or \&{readfrom} */ @d is_scantok (str_number)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 mp->input_file[iindex] /* the current |void *| variable */ @d line mp->line_stack[iindex] /* current line number in the current source file */ @d in_ext mp->inext_stack[iindex] /* a string used to construct \.{MPX} file names */ @d in_name mp->iname_stack[iindex] /* a string used to construct \.{MPX} file names */ @d in_area mp->iarea_stack[iindex] /* another string for naming \.{MPX} files */ @d absent (str_number)1 /* |name_field| value for unused |mpx_in_stack| entries */ @d mpx_reading (mp->mpx_name[iindex]>absent) /* when reading a file, is it an \.{MPX} file? */ @d mpx_finished 0 /* |name_field| value when the corresponding \.{MPX} file is finished */ @<Glob...@>= integer in_open; /* the number of lines in the buffer, less one */ integer in_open_max; /* highest value of |in_open| ever seen */ int open_parens; /* the number of open text files */ void **input_file; integer *line_stack; /* the line number for each file */ char **inext_stack; /* used for naming \.{MPX} files */ char **iname_stack; /* used for naming \.{MPX} files */ char **iarea_stack; /* used for naming \.{MPX} files */ str_number *mpx_name; @ @<Declarations@>= static void mp_reallocate_input_stack (MP mp, int newsize); @ @c static void mp_reallocate_input_stack (MP mp, int newsize) { int k; int n = newsize +1; XREALLOC (mp->input_file, n, void *); XREALLOC (mp->line_stack, n, integer); XREALLOC (mp->inext_stack, n, char *); XREALLOC (mp->iname_stack, n, char *); XREALLOC (mp->iarea_stack, n, char *); XREALLOC (mp->mpx_name, n, str_number); for (k = mp->max_in_open; k <= n; k++) { mp->input_file[k] = NULL; mp->line_stack[k] = 0; mp->inext_stack[k] = NULL; mp->iname_stack[k] = NULL; mp->iarea_stack[k] = NULL; mp->mpx_name[k] = NULL; } mp->max_in_open = newsize; } @ This has to be more than |file_bottom|, so: @<Allocate or ...@>= mp_reallocate_input_stack (mp, file_bottom+4); @ @<Dealloc variables@>= { int l; for (l = 0; l <= mp->max_in_open; l++) { xfree (mp->inext_stack[l]); xfree (mp->iname_stack[l]); xfree (mp->iarea_stack[l]); } } xfree (mp->input_file); xfree (mp->line_stack); xfree (mp->inext_stack); xfree (mp->iname_stack); xfree (mp->iarea_stack); xfree (mp->mpx_name); @ 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 |iindex>max_in_open|, and the conventions about the other state variables are different: \yskip\hang|nloc| is a pointer to the current node in the token list, i.e., the node that will be read next. If |nloc=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 |iindex| 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 nloc mp->cur_input.nloc_field /* location of next node node */ @d nstart mp->cur_input.nstart_field /* location of next node node */ @d token_type iindex /* type of current token list */ @d token_state (iindex<=macro) /* are we scanning a token list? */ @d file_state (iindex>macro) /* are we scanning a file line? */ @d param_start limit /* base of macro parameters in |param_stack| */ @d forever_text 0 /* |token_type| code for loop texts */ @d loop_text 1 /* |token_type| code for loop texts */ @d parameter 2 /* |token_type| code for parameter texts */ @d backed_up 3 /* |token_type| code for texts to be reread */ @d inserted 4 /* |token_type| code for inserted texts */ @d macro 5 /* |token_type| code for macro replacement texts */ @d file_bottom 6 /* lowest file code */ @ 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, and is dynamically reallocated when needed. @<Glob...@>= mp_node *param_stack; /* token list pointers for parameters */ integer param_ptr; /* first unused entry in |param_stack| */ integer max_param_stack; /* largest value of |param_ptr| */ @ @<Allocate or initialize ...@>= mp->param_stack = xmalloc ((mp->param_size + 1), sizeof (mp_node)); @ @c static void mp_check_param_size (MP mp, int k) { while (k >= mp->param_size) { XREALLOC (mp->param_stack, (k + k / 4), mp_node); mp->param_size = k + k / 4; } } @ @<Dealloc variables@>= xfree (mp->param_stack); @ Notice that the |line| isn't valid when |token_state| is true because it depends on |iindex|. If we really need to know the line number for the topmost file in the iindex 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@> @<Declarations@>= static integer mp_true_line (MP mp); @ @c integer mp_true_line (MP mp) { int k; /* an index into the input stack */ if (file_state && (name > max_spec_src)) { return line; } else { k = mp->input_ptr; while ((k > 0) && ((mp->input_stack[(k - 1)].index_field < file_bottom) || (mp->input_stack[(k - 1)].name_field <= max_spec_src))) { decr (k); } return (k > 0 ? mp->line_stack[(k - 1) + file_bottom] : 0); } } @ 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...@>= integer file_ptr; /* 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. @c void mp_show_context (MP mp) { /* prints where the scanner is */ unsigned old_setting; /* saved |selector| setting */ @<Local variables for formatting calculations@>; mp->file_ptr = mp->input_ptr; mp->input_stack[mp->file_ptr] = mp->cur_input; /* store current state */ while (1) { mp->cur_input = mp->input_stack[mp->file_ptr]; /* enter into the context */ @<Display the current context@>; if (file_state) if ((name > max_spec_src) || (mp->file_ptr == 0)) break; decr (mp->file_ptr); } mp->cur_input = mp->input_stack[mp->input_ptr]; /* restore original state */ } @ @<Display the current context@>= if ((mp->file_ptr == mp->input_ptr) || file_state || (token_type != backed_up) || (nloc != NULL)) { /* we omit backed-up token lists that have already been read */ mp->tally = 0; /* get ready to count characters */ old_setting = mp->selector; if (file_state) { @<Print location of current line@>; @<Pseudoprint the line@>; } else { @<Print type of token list@>; @<Pseudoprint the token list@>; } mp->selector = old_setting; /* stop pseudoprinting */ @<Print two lines using the tricky pseudoprinted information@>; } @ 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) { mp_print_nl (mp, "l."); mp_print_int (mp, mp_true_line (mp)); } else if (terminal_input) { if (mp->file_ptr == 0) mp_print_nl (mp, "<*>"); else mp_print_nl (mp, "<insert>"); } else if (name == is_scantok) { mp_print_nl (mp, "<scantokens>"); } else { mp_print_nl (mp, "<read>"); } mp_print_char (mp, xord (' ')) @ Can't use case statement here because the |token_type| is not a constant expression. @<Print type of token list@>= { if (token_type == forever_text) { mp_print_nl (mp, "<forever> "); } else if (token_type == loop_text) { @<Print the current loop value@>; } else if (token_type == parameter) { mp_print_nl (mp, "<argument> "); } else if (token_type == backed_up) { if (nloc == NULL) mp_print_nl (mp, "<recently read> "); else mp_print_nl (mp, "<to be read again> "); } else if (token_type == inserted) { mp_print_nl (mp, "<inserted text> "); } else if (token_type == macro) { mp_print_ln (mp); if (name != NULL) mp_print_str (mp, name); else @<Print the name of a \&{vardef}'d macro@>; mp_print (mp, "->"); } else { mp_print_nl (mp, "?"); /* this should never happen */ @.?\relax@> } } @ 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. @<Print the current loop value@>= { mp_node pp; mp_print_nl (mp, "<for("); pp = mp->param_stack[param_start]; if (pp != NULL) { if (mp_link (pp) == MP_VOID) mp_print_exp (mp, pp, 0); /* we're in a \&{for} loop */ else mp_show_token_list (mp, pp, NULL, 20, mp->tally); } mp_print (mp, ")> "); } @ 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@>= { mp_node pp = mp->param_stack[param_start]; if (pp == NULL) { mp_show_token_list (mp, mp->param_stack[param_start + 1], NULL, 20, mp->tally); } else { mp_node qq = pp; while (mp_link (qq) != NULL) qq = mp_link (qq); mp_link (qq) = mp->param_stack[param_start + 1]; mp_show_token_list (mp, pp, NULL, 20, mp->tally); mp_link (qq) = NULL; } } @ 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...@>= int i; /* index into |buffer| */ integer l; /* length of descriptive information on line 1 */ integer m; /* context information gathered for line 2 */ int n; /* length of line 1 */ integer p; /* starting or ending place in |trick_buf| */ integer q; /* temporary index */ @ The following code tells the print routines to gather the desired information. @d begin_pseudoprint { l=mp->tally; mp->tally=0; mp->selector=pseudo; mp->trick_count=1000000; } @d set_trick_count { mp->first_count=mp->tally; mp->trick_count=mp->tally+1+mp->error_line-mp->half_error_line; if ( mp->trick_count<mp->error_line ) mp->trick_count=mp->error_line; } @ And the following code uses the information after it has been gathered. @<Print two lines using the tricky pseudoprinted information@>= if (mp->trick_count == 1000000) set_trick_count; /* |set_trick_count| must be performed */ if (mp->tally < mp->trick_count) m = mp->tally - mp->first_count; else m = mp->trick_count - mp->first_count; /* context on line 2 */ if (l + mp->first_count <= mp->half_error_line) { p = 0; n = l + mp->first_count; } else { mp_print (mp, "..."); p = l + mp->first_count - mp->half_error_line + 3; n = mp->half_error_line; } for (q = p; q <= mp->first_count - 1; q++) { mp_print_char (mp, mp->trick_buf[q % mp->error_line]); } mp_print_ln (mp); for (q = 1; q <= n; q++) { mp_print_char (mp, xord (' ')); /* print |n| spaces to begin line~2 */ } if (m + n <= mp->error_line) p = mp->first_count + m; else p = mp->first_count + (mp->error_line - n - 3); for (q = mp->first_count; q <= p - 1; q++) { mp_print_char (mp, mp->trick_buf[q % mp->error_line]); } if (m + n > mp->error_line) mp_print (mp, "...") @ 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) { for (i = start; i <= limit - 1; i++) { if (i == loc) set_trick_count; mp_print_char (mp, mp->buffer[i]); } } @ @<Pseudoprint the token list@>= begin_pseudoprint; if (token_type != macro) mp_show_token_list (mp, nstart, nloc, 100000, 0); else mp_show_macro (mp, nstart, nloc, 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 @* 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 { /* enter a new input level, save the old */ if ( mp->input_ptr>mp->max_in_stack ) { mp->max_in_stack=mp->input_ptr; if ( mp->input_ptr==mp->stack_size ) { int l = (mp->stack_size+(mp->stack_size/4)); XREALLOC(mp->input_stack, l, in_state_record); mp->stack_size = l; } } mp->input_stack[mp->input_ptr]=mp->cur_input; /* stack the record */ incr(mp->input_ptr); } @ And of course what goes up must come down. @d pop_input { /* leave an input level, re-enter the old */ decr(mp->input_ptr); mp->cur_input=mp->input_stack[mp->input_ptr]; } @ 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(A) mp_begin_token_list(mp, (A), (quarterword)backed_up) /* backs up a simple token list */ @c static void mp_begin_token_list (MP mp, mp_node p, quarterword t) { push_input; nstart = p; token_type = t; param_start = mp->param_ptr; nloc = p; } @ When a token list has been fully scanned, the following computations should be done as we leave that level of input. @^inner loop@> @c static void mp_end_token_list (MP mp) { /* leave a token-list input level */ mp_node p; /* temporary register */ if (token_type >= backed_up) { /* token list to be deleted */ if (token_type <= inserted) { mp_flush_token_list (mp, nstart); goto DONE; } else { mp_delete_mac_ref (mp, nstart); /* update reference count */ } } while (mp->param_ptr > param_start) { /* parameters must be flushed */ decr (mp->param_ptr); p = mp->param_stack[mp->param_ptr]; if (p != NULL) { if (mp_link (p) == MP_VOID) { /* it's an \&{expr} parameter */ mp_recycle_value (mp, p); mp_free_node (mp, p, value_node_size); } else { mp_flush_token_list (mp, p); /* it's a \&{suffix} or \&{text} parameter */ } } } DONE: pop_input; check_interrupt; } @ The contents of |cur_cmd,cur_mod,cur_sym| are placed into an equivalent token by the |cur_tok| routine. @^inner loop@> @c @<Declare the procedure called |make_exp_copy|@>; static mp_node mp_cur_tok (MP mp) { mp_node p; /* a new token node */ if (mp->cur_sym == NULL && mp->cur_sym_mod == 0) { if (mp->cur_cmd == capsule_token) { mp_value save_exp = mp->cur_exp; /* |cur_exp| to be restored */ mp_make_exp_copy (mp, mp->cur_mod_node); p = mp_stash_cur_exp (mp); mp_link (p) = NULL; mp->cur_exp = save_exp; } else { p = mp_get_token_node (mp); mp_name_type (p) = mp_token; if (mp->cur_cmd == numeric_token) { set_value (p, mp->cur_mod); mp_type (p) = mp_known; } else { set_str_value (p, mp->cur_mod_str); mp_type (p) = mp_string_type; } } } else { p = mp_get_symbolic_node (mp); set_mp_sym_sym (p, mp->cur_sym); mp_name_type (p) = mp->cur_sym_mod; } return p; } @ 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. @<Declarations@>= static void mp_back_input (MP mp); @ @c void mp_back_input (MP mp) { /* undoes one token of input */ mp_node p; /* a token list of length one */ p = mp_cur_tok (mp); while (token_state && (nloc == NULL)) mp_end_token_list (mp); /* conserve stack space */ back_list (p); } @ 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. @ @c static void mp_back_error (MP mp) { /* back up one token and call |error| */ mp->OK_to_interrupt = false; mp_back_input (mp); mp->OK_to_interrupt = true; mp_error (mp); } static void mp_ins_error (MP mp) { /* back up one inserted token and call |error| */ mp->OK_to_interrupt = false; mp_back_input (mp); token_type = (quarterword) inserted; mp->OK_to_interrupt = true; mp_error (mp); } @ 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@> @c void mp_begin_file_reading (MP mp) { if (mp->in_open == (mp->max_in_open-1)) mp_reallocate_input_stack (mp, (mp->max_in_open + mp->max_in_open / 4)); if (mp->first == mp->buf_size) mp_reallocate_buffer (mp, (mp->buf_size + mp->buf_size / 4)); mp->in_open++; push_input; iindex = (quarterword) mp->in_open; if (mp->in_open_max < mp->in_open) mp->in_open_max = mp->in_open; mp->mpx_name[iindex] = absent; start = (halfword) mp->first; name = is_term; /* |terminal_input| is now |true| */ } @ 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. While finishing preloading, it is possible that the file does not actually end with 'dump', so we capture that case here as well. @c static void mp_end_file_reading (MP mp) { if (mp->reading_preload && mp->input_ptr == 0) { mp->cur_sym = mp->frozen_dump; mp_back_input (mp); return; } if (mp->in_open > iindex) { if ((mp->mpx_name[mp->in_open] == absent) || (name <= max_spec_src)) { mp_confusion (mp, "endinput"); @:this can't happen endinput}{\quad endinput@>; } else { (mp->close_file) (mp, mp->input_file[mp->in_open]); /* close an \.{MPX} file */ delete_str_ref (mp->mpx_name[mp->in_open]); decr (mp->in_open); } } mp->first = (size_t) start; if (iindex != mp->in_open) mp_confusion (mp, "endinput"); if (name > max_spec_src) { (mp->close_file) (mp, cur_file); xfree (in_ext); xfree (in_name); xfree (in_area); } pop_input; decr (mp->in_open); } @ 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. @c static boolean mp_begin_mpx_reading (MP mp) { if (mp->in_open != iindex + 1) { return false; } else { if (mp->mpx_name[mp->in_open] <= absent) mp_confusion (mp, "mpx"); @:this can't happen mpx}{\quad mpx@>; if (mp->first == mp->buf_size) mp_reallocate_buffer (mp, (mp->buf_size + (mp->buf_size / 4))); push_input; iindex = (quarterword) mp->in_open; start = (halfword) mp->first; name = mp->mpx_name[mp->in_open]; add_str_ref (name); @<Put an empty line in the input buffer@>; return true; } } @ This procedure temporarily stops reading an \.{MPX} file. @c static void mp_end_mpx_reading (MP mp) { if (mp->in_open != iindex) mp_confusion (mp, "mpx"); @:this can't happen mpx}{\quad mpx@>; if (loc < limit) { @<Complain that we are not at the end of a line in the \.{MPX} file@>; } mp->first = (size_t) start; pop_input; } @ 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@>= { 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."); mp_error (mp); } @ 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. @c void mp_clear_for_error_prompt (MP mp) { while (file_state && terminal_input && (mp->input_ptr > 0) && (loc == limit)) mp_end_file_reading (mp); mp_print_ln (mp); clear_terminal; } @ To get \MP's whole input mechanism going, we perform the following actions. @<Initialize the input routines@>= { mp->input_ptr = 0; mp->max_in_stack = file_bottom; mp->in_open = file_bottom; mp->open_parens = 0; mp->max_buf_stack = 0; mp->param_ptr = 0; mp->max_param_stack = 0; mp->first = 0; start = 0; iindex = file_bottom; line = 0; name = is_term; mp->mpx_name[file_bottom] = absent; mp->force_eof = false; if (!mp_init_terminal (mp)) mp_jump_out (mp); limit = (halfword) mp->last; mp->first = mp->last + 1; /* |init_terminal| has set |loc| and |last| */ } @* 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...@>= integer scanner_status; /* are we scanning at high speed? */ mp_sym warning_info; /* if so, what else do we need to know, in case an error occurs? */ integer warning_info_line; mp_node warning_info_node; @ @<Initialize the input routines@>= mp->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. @c static boolean mp_check_outer_validity (MP mp) { mp_node p; /* points to inserted token list */ if (mp->scanner_status == normal) { return true; } else if (mp->scanner_status == tex_flushing) { @<Check if the file has ended while flushing \TeX\ material and set the result value for |check_outer_validity|@>; } else { mp->deletions_allowed = false; @<Back up an outer symbolic token so that it can be reread@>; if (mp->scanner_status > skipping) { @<Tell the user what has run away and try to recover@>; } else { print_err ("Incomplete if; all text was ignored after line "); @.Incomplete if...@>; mp_print_int (mp, mp->warning_info_line); 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 (mp->cur_sym == NULL) mp->help_line[2] = "The file ended while I was skipping conditional text."; mp->cur_sym = mp->frozen_fi; mp_ins_error (mp); } mp->deletions_allowed = true; return false; } } @ @<Check if the file has ended while flushing \TeX\ material and set...@>= if (mp->cur_sym != NULL) { return true; } else { mp->deletions_allowed = false; print_err ("TeX mode didn't end; all text was ignored after line "); mp_print_int (mp, mp->warning_info_line); help2 ("The file ended while I was looking for the `etex' to", "finish this TeX material. I've inserted `etex' now."); mp->cur_sym = mp->frozen_etex; mp_ins_error (mp); mp->deletions_allowed = true; return false; } @ @<Back up an outer symbolic token so that it can be reread@>= if (mp->cur_sym != NULL) { p = mp_get_symbolic_node (mp); set_mp_sym_sym (p, mp->cur_sym); mp_name_type (p) = mp->cur_sym_mod; back_list (p); /* prepare to read the symbolic token again */ } @ @<Tell the user what has run away...@>= { mp_runaway (mp); /* print the definition-so-far */ if (mp->cur_sym == NULL) { print_err ("File ended"); @.File ended while scanning...@> } else { print_err ("Forbidden token found"); @.Forbidden token found...@> } mp_print (mp, " 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."); switch (mp->scanner_status) { @<Complete the error message, and set |cur_sym| to a token that might help recover from the error@> } /* there are no other cases */ mp_ins_error (mp); } @ 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,...@>= case flushing: mp_print (mp, "to the end of the statement"); mp->help_line[3] = "A previous error seems to have propagated,"; mp->cur_sym = mp->frozen_semicolon; break; case absorbing: mp_print (mp, "a text argument"); mp->help_line[3] = "It seems that a right delimiter was left out,"; if (mp->warning_info == NULL) { mp->cur_sym = mp->frozen_end_group; } else { mp->cur_sym = mp->frozen_right_delimiter; /* the next line makes sure that the inserted delimiter will match the delimiter that already was read. */ equiv_sym (mp->cur_sym) = mp->warning_info; } break; case var_defining: case op_defining: mp_print (mp, "the definition of "); if (mp->scanner_status == op_defining) mp_print_text (mp->warning_info); else mp_print_variable_name (mp, mp->warning_info_node); mp->cur_sym = mp->frozen_end_def; break; case loop_defining: mp_print (mp, "the text of a "); mp_print_text (mp->warning_info); mp_print (mp, " loop"); mp->help_line[3] = "I suspect you have forgotten an `endfor',"; mp->cur_sym = mp->frozen_end_for; break; @ 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. @<Declarations@>= static void mp_runaway (MP mp); @ @c void mp_runaway (MP mp) { if (mp->scanner_status > flushing) { mp_print_nl (mp, "Runaway "); switch (mp->scanner_status) { case absorbing: mp_print (mp, "text?"); break; case var_defining: case op_defining: mp_print (mp, "definition?"); break; case loop_defining: mp_print (mp, "loop?"); break; } /* there are no other cases */ mp_print_ln (mp); mp_show_token_list (mp, mp_link (mp->hold_head), NULL, mp->error_line - 10, 0); } } @ We need to mention a procedure that may be called by |get_next|. @<Declarations@>= static void mp_firm_up_the_line (MP mp); @ 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. @c void mp_get_next (MP mp) { /* sets |cur_cmd|, |cur_mod|, |cur_sym| to next token */ @^inner loop@> /*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 */ int k; /* an index into |buffer| */ ASCII_code c; /* the current character in the buffer */ int class; /* its class number */ integer n, f; /* registers for decimal-to-binary conversion */ RESTART: mp->cur_sym = 0; mp->cur_sym_mod = 0; if (file_state) { @<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@>; } @ 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|...@>= mp->cur_cmd = eq_type (mp->cur_sym); mp->cur_mod = equiv (mp->cur_sym); mp->cur_mod_node = equiv_node (mp->cur_sym); mp->cur_sym2 = equiv_sym (mp->cur_sym); if (mp->cur_cmd >= outer_tag) { if (mp_check_outer_validity (mp)) mp->cur_cmd = mp->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;...@>= { SWITCH: c = mp->buffer[loc]; incr (loc); class = mp->char_class[c]; switch (class) { case digit_class: goto START_NUMERIC_TOKEN; break; case period_class: class = mp->char_class[mp->buffer[loc]]; if (class > period_class) { goto SWITCH; } else if (class < period_class) { /* |class=digit_class| */ n = 0; goto START_DECIMAL_TOKEN; } @:. }{\..\ token@>; break; case space_class: goto SWITCH; break; case percent_class: if (mp->scanner_status == tex_flushing) { if (loc < limit) goto SWITCH; } @<Move to next line of file, or |goto restart| if there is no next line@>; check_interrupt; goto SWITCH; break; case string_class: if (mp->scanner_status == tex_flushing) goto SWITCH; else @<Get a string token and |return|@>; break; case isolated_classes: k = loc - 1; goto FOUND; break; case invalid_class: if (mp->scanner_status == tex_flushing) goto SWITCH; else @<Decry the invalid character and |goto restart|@>; break; default: break; /* letters, etc. */ } k = loc - 1; while (mp->char_class[mp->buffer[loc]] == class) 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: mp->cur_sym = mp_id_lookup (mp, (char *) (mp->buffer + k), (size_t) (loc - k), true); } @ We go to |restart| instead of to |SWITCH|, because we might enter |token_state| after the error has been dealt with (cf.\ |clear_for_error_prompt|). @<Decry the invalid...@>= { 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."); mp->deletions_allowed = false; mp_error (mp); mp->deletions_allowed = true; goto RESTART; } @ @<Get a string token and |return|@>= { if (mp->buffer[loc] == '"') { mp->cur_mod_str = null_str; } else { k = loc; mp->buffer[limit + 1] = xord ('"'); do { incr (loc); } while (mp->buffer[loc] != '"'); if (loc > limit) { @<Decry the missing string delimiter and |goto restart|@>; } str_room ((size_t) (loc - k)); do { append_char (mp->buffer[k]); incr (k); } while (k != loc); mp->cur_mod_str = mp_make_string (mp); } incr (loc); mp->cur_cmd = string_token; return; } @ 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|@>= { 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\"'."); mp->deletions_allowed = false; mp_error (mp); mp->deletions_allowed = true; goto RESTART; } @ @<Get the integer part |n| of a numeric token...@>= n = c - '0'; while (mp->char_class[mp->buffer[loc]] == digit_class) { if (n < 32768) n = 10 * n + mp->buffer[loc] - '0'; incr (loc); } if (mp->buffer[loc] == '.') if (mp->char_class[mp->buffer[loc + 1]] == digit_class) goto DONE; f = 0; goto FIN_NUMERIC_TOKEN; DONE:incr (loc) @ @<Get the fraction part |f| of a numeric token@>= k = 0; do { incr (k); incr (loc); } while (mp->char_class[mp->buffer[loc]] == digit_class); f = mp_round_decimals (mp, (unsigned char *)(mp->buffer+loc-k), (quarterword) k); if (f == unity) { incr (n); f = 0; } @ @<Pack the numeric and fraction parts of a numeric token and |return|@>= if (n < 32768) { @<Set |cur_mod:=n*unity+f| and check if it is uncomfortably large@>; } else if (mp->scanner_status != tex_flushing) { 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."); mp->deletions_allowed = false; mp_error (mp); mp->deletions_allowed = true; mp->cur_mod = EL_GORDO; } mp->cur_cmd = numeric_token; return @ @<Set |cur_mod:=n*unity+f| and check if it is uncomfortably large@>= { mp->cur_mod = n * unity + f; if (mp->cur_mod >= fraction_one) { if ((internal_value (mp_warning_check) > 0) && (mp->scanner_status != tex_flushing)) { print_err ("Number is too large ("); mp_print_scaled (mp, mp->cur_mod); mp_print_char (mp, xord (')')); 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.)"); mp_error (mp); } } } @ Let's consider now what happens when |get_next| is looking at a token list. @^inner loop@> @<Input from token list;...@>= if (nloc != NULL && mp_type (nloc) == mp_symbol_node) { /* symbolic token */ halfword cur_info = mp_sym_info (nloc); mp->cur_sym = mp_sym_sym (nloc); mp->cur_sym_mod = mp_name_type (nloc); nloc = mp_link (nloc); /* move to next */ if (mp->cur_sym_mod == mp_expr_sym) { mp->cur_cmd = capsule_token; mp->cur_mod_node = mp->param_stack[param_start + cur_info]; mp->cur_sym_mod = 0; mp->cur_sym = 0; return; } else if (mp->cur_sym_mod == mp_suffix_sym || mp->cur_sym_mod == mp_text_sym) { mp_begin_token_list (mp, mp->param_stack[param_start + cur_info], (quarterword) parameter); goto RESTART; } } else if (nloc != NULL) { @<Get a stored numeric or string or capsule token and |return|@> } else { /* we are done with this token list */ mp_end_token_list (mp); goto RESTART; /* resume previous level */ } @ @<Get a stored numeric or string or capsule token...@>= { if (mp_name_type (nloc) == mp_token) { if (mp_type (nloc) == mp_known) { mp->cur_mod = value (nloc); mp->cur_cmd = numeric_token; } else { mp->cur_mod_str = str_value (nloc); mp->cur_cmd = string_token; add_str_ref (mp->cur_mod_str); } } else { mp->cur_mod_node = nloc; mp->cur_sym2 = NULL; mp->cur_cmd = capsule_token; } nloc = mp_link (nloc); return; } @ 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) { @<Read next line of file into |buffer|, or |goto restart| if the file has ended@>; } else { if (mp->input_ptr > 0) { /* text was inserted during error recovery or by \&{scantokens} */ mp_end_file_reading (mp); goto RESTART; /* resume previous level */ } if (mp->job_name == NULL && (mp->selector < log_only || mp->selector >= write_file)) mp_open_log_file (mp); if (mp->interaction > mp_nonstop_mode) { if (limit == start) /* previous line was empty */ mp_print_nl (mp, "(Please type a command or say `end')"); @.Please type...@>; mp_print_ln (mp); mp->first = (size_t) start; prompt_input ("*"); /* input on-line into |buffer| */ @.*\relax@>; limit = (halfword) mp->last; mp->buffer[limit] = xord ('%'); mp->first = (size_t) (limit + 1); loc = start; } else { mp_fatal_error (mp, "*** (job aborted, no legal end found)"); @.job aborted@>; /* nonstop mode, which is intended for overnight batch processing, never waits for on-line input */ } } @ The global variable |force_eof| is normally |false|; it is set |true| by an \&{endinput} command. @<Glob...@>= boolean force_eof; /* 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@>= { incr (line); mp->first = (size_t) start; if (!mp->force_eof) { if (mp_input_ln (mp, cur_file)) /* not end of file */ mp_firm_up_the_line (mp); /* this sets |limit| */ else mp->force_eof = true; }; if (mp->force_eof) { mp->force_eof = false; decr (loc); if (mpx_reading) { @<Complain that the \.{MPX} file ended unexpectly; then set |cur_sym:=mp->frozen_mpx_break| and |goto comon_ending|@>; } else { mp_print_char (mp, xord (')')); decr (mp->open_parens); update_terminal; /* show user that file has been read */ mp_end_file_reading (mp); /* resume previous level */ if (mp_check_outer_validity (mp)) goto RESTART; else goto RESTART; } } mp->buffer[limit] = xord ('%'); mp->first = (size_t) (limit + 1); loc = start; /* ready to read */ } @ 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...@>= { mp->mpx_name[iindex] = mpx_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."); mp->deletions_allowed = false; mp_error (mp); mp->deletions_allowed = true; mp->cur_sym = mp->frozen_mpx_break; goto COMMON_ENDING; } @ 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@>= mp->last = mp->first; limit = (halfword) mp->last; /* simulate |input_ln| and |firm_up_the_line| */ mp->buffer[limit] = xord ('%'); mp->first = (size_t) (limit + 1); loc = start @ If the user has set the |mp_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. @c void mp_firm_up_the_line (MP mp) { size_t k; /* an index into |buffer| */ limit = (halfword) mp->last; if ((!mp->noninteractive) && (internal_value (mp_pausing) > 0) && (mp->interaction > mp_nonstop_mode)) { wake_up_terminal; mp_print_ln (mp); if (start < limit) { for (k = (size_t) start; k < (size_t) limit; k++) { mp_print_char (mp, mp->buffer[k]); } } mp->first = (size_t) limit; prompt_input ("=>"); /* wait for user response */ @.=>@>; if (mp->last > mp->first) { for (k = mp->first; k < mp->last; k++) { /* move line down in buffer */ mp->buffer[k + (size_t) start - mp->first] = mp->buffer[k]; } limit = (halfword) ((size_t) start + mp->last - mp->first); } } } @* 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...@>= mp_primitive (mp, "btex", start_tex, btex_code); @:btex_}{\&{btex} primitive@>; mp_primitive (mp, "verbatimtex", start_tex, verbatim_code); @:verbatimtex_}{\&{verbatimtex} primitive@>; mp_primitive (mp, "etex", etex_marker, 0); mp->frozen_etex = mp_frozen_primitive (mp, "etex", etex_marker, 0); @:etex_}{\&{etex} primitive@>; mp_primitive (mp, "mpxbreak", mpx_break, 0); mp->frozen_mpx_break = mp_frozen_primitive (mp, "mpxbreak", mpx_break, 0); @:mpx_break_}{\&{mpxbreak} primitive@> @ @<Cases of |print_cmd...@>= case start_tex: if (m == btex_code) mp_print (mp, "btex"); else mp_print (mp, "verbatimtex"); break; case etex_marker: mp_print (mp, "etex"); break; case mpx_break: mp_print (mp, "mpxbreak"); break; @ Actually, |get_t_next| is a macro that avoids procedure overhead except in the unusual case where \&{btex}, \&{verbatimtex}, \&{etex}, or \&{mpxbreak} is encountered. @c static void get_t_next (MP mp) { mp_get_next (mp); if (mp->cur_cmd <= max_pre_command) mp_t_next (mp); } @ @<Declarations@>= static void get_t_next (MP mp); static void mp_t_next (MP mp); static void mp_start_mpx_input (MP mp); @ @c static void mp_t_next (MP mp) { int old_status; /* saves the |scanner_status| */ integer old_info; /* saves the |warning_info| */ while (mp->cur_cmd <= max_pre_command) { if (mp->cur_cmd == mpx_break) { if (!file_state || (mp->mpx_name[iindex] == absent)) { @<Complain about a misplaced \&{mpxbreak}@>; } else { mp_end_mpx_reading (mp); goto TEX_FLUSH; } } else if (mp->cur_cmd == start_tex) { if (token_state || (name <= max_spec_src)) { @<Complain that we are not reading a file@>; } else if (mpx_reading) { @<Complain that \.{MPX} files cannot contain \TeX\ material@>; } else if ((mp->cur_mod != verbatim_code) && (mp->mpx_name[iindex] != mpx_finished)) { if (!mp_begin_mpx_reading (mp)) mp_start_mpx_input (mp); } else { goto TEX_FLUSH; } } else { @<Complain about a misplaced \&{etex}@>; } goto COMMON_ENDING; TEX_FLUSH: @<Flush the \TeX\ material@>; COMMON_ENDING: mp_get_next (mp); } } @ 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 = mp->scanner_status; old_info = mp->warning_info_line; mp->scanner_status = tex_flushing; mp->warning_info_line = line; do { mp_get_next (mp); } while (mp->cur_cmd != etex_marker); mp->scanner_status = old_status; mp->warning_info_line = old_info @ @<Complain that \.{MPX} files cannot contain \TeX\ material@>= { 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."); mp_error (mp); } @ @<Complain that we are not reading a file@>= { 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`."); mp_error (mp); } @ @<Complain about a misplaced \&{mpxbreak}@>= { print_err ("Misplaced mpxbreak"); help2 ("I'll ignore this preprocessor command because it", "doesn't belong here"); mp_error (mp); } @ @<Complain about a misplaced \&{etex}@>= { print_err ("Extra etex will be ignored"); help1 ("There is no btex or verbatimtex for this to match"); mp_error (mp); } @* 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 start_for 2 /* command modifier for \&{forever} */ @d start_forsuffixes 3 /* command modifier for \&{forever} */ @d end_for 0 /* command modifier for \&{endfor} */ @<Put each...@>= mp_primitive (mp, "def", macro_def, start_def); @:def_}{\&{def} primitive@>; mp_primitive (mp, "vardef", macro_def, var_def); @:var_def_}{\&{vardef} primitive@>; mp_primitive (mp, "primarydef", macro_def, secondary_primary_macro); @:primary_def_}{\&{primarydef} primitive@>; mp_primitive (mp, "secondarydef", macro_def, tertiary_secondary_macro); @:secondary_def_}{\&{secondarydef} primitive@>; mp_primitive (mp, "tertiarydef", macro_def, expression_tertiary_macro); @:tertiary_def_}{\&{tertiarydef} primitive@>; mp_primitive (mp, "enddef", macro_def, end_def); mp->frozen_end_def = mp_frozen_primitive (mp, "enddef", macro_def, end_def); @:end_def_}{\&{enddef} primitive@>; mp_primitive (mp, "for", iteration, start_for); @:for_}{\&{for} primitive@>; mp_primitive (mp, "forsuffixes", iteration, start_forsuffixes); @:for_suffixes_}{\&{forsuffixes} primitive@>; mp_primitive (mp, "forever", iteration, start_forever); @:forever_}{\&{forever} primitive@>; mp_primitive (mp, "endfor", iteration, end_for); mp->frozen_end_for = mp_frozen_primitive (mp, "endfor", iteration, end_for); @:end_for_}{\&{endfor} primitive@> @ @<Cases of |print_cmd...@>= case macro_def: if (m <= var_def) { if (m == start_def) mp_print (mp, "def"); else if (m < start_def) mp_print (mp, "enddef"); else mp_print (mp, "vardef"); } else if (m == secondary_primary_macro) { mp_print (mp, "primarydef"); } else if (m == tertiary_secondary_macro) { mp_print (mp, "secondarydef"); } else { mp_print (mp, "tertiarydef"); } break; case iteration: if (m == start_forever) mp_print (mp, "forever"); else if (m == end_for) mp_print (mp, "endfor"); else if (m == start_for) mp_print (mp, "for"); else mp_print (mp, "forsuffixes"); break; @ 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| or |iteration|). The second parameter, |subst_list|, points to a (possibly empty) list of non-symbolic 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)}. @<Types...@>= typedef struct mp_subst_list_item { quarterword info_mod; quarterword value_mod; mp_sym info; halfword value_data; struct mp_subst_list_item *link; } mp_subst_list_item; @ @c static mp_node mp_scan_toks (MP mp, command_code terminator, mp_subst_list_item * subst_list, mp_node tail_end, quarterword suffix_count) { mp_node p; /* tail of the token list being built */ mp_subst_list_item *q = NULL; /* temporary for link management */ integer balance; /* left delimiters minus right delimiters */ halfword cur_data; quarterword cur_data_mod = 0; p = mp->hold_head; balance = 1; mp_link (mp->hold_head) = NULL; while (1) { get_t_next (mp); cur_data = -1; if (mp->cur_sym != NULL) { @<Substitute for |cur_sym|, if it's on the |subst_list|@>; if (mp->cur_cmd == terminator) { @<Adjust the balance; |break| if it's zero@>; } else if (mp->cur_cmd == macro_special) { /* Handle quoted symbols, \.{\#\AT!}, \.{\AT!}, or \.{\AT!\#} */ if (mp->cur_mod == quote) { get_t_next (mp); } else if (mp->cur_mod <= suffix_count) { cur_data = mp->cur_mod - 1; cur_data_mod = mp_suffix_sym; } } } if (cur_data != -1) { mp_node pp = mp_get_symbolic_node (mp); set_mp_sym_info (pp, cur_data); mp_name_type (pp) = cur_data_mod; mp_link (p) = pp; } else { mp_link (p) = mp_cur_tok (mp); } p = mp_link (p); } mp_link (p) = tail_end; while (subst_list) { q = subst_list->link; xfree (subst_list); subst_list = q; } return mp_link (mp->hold_head); } @ @<Substitute for |cur_sym|...@>= { q = subst_list; while (q != NULL) { if (q->info == mp->cur_sym && q->info_mod == mp->cur_sym_mod) { cur_data = q->value_data; cur_data_mod = q->value_mod; mp->cur_cmd = relax; break; } q = q->link; } } @ @<Adjust the balance; |break| if it's zero@>= if (mp->cur_mod > 0) { incr (balance); } else { decr (balance); if (balance == 0) break; } @ 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...@>= mp_primitive (mp, "quote", macro_special, quote); @:quote_}{\&{quote} primitive@>; mp_primitive (mp, "#@@", macro_special, macro_prefix); @:]]]\#\AT!_}{\.{\#\AT!} primitive@>; mp_primitive (mp, "@@", macro_special, macro_at); @:]]]\AT!_}{\.{\AT!} primitive@>; mp_primitive (mp, "@@#", macro_special, macro_suffix); @:]]]\AT!\#_}{\.{\AT!\#} primitive@> @ @<Cases of |print_cmd...@>= case macro_special: switch (m) { case macro_prefix: mp_print (mp, "#@@"); break; case macro_at: mp_print_char (mp, xord ('@@')); break; case macro_suffix: mp_print (mp, "@@#"); break; default: mp_print (mp, "quote"); break; } break; @ Here is a routine that's used whenever a token will be redefined. If the user's token is unredefinable, the `|mp->frozen_inaccessible|' token is substituted; the latter is redefinable but essentially impossible to use, hence \MP's tables won't get fouled up. @c static void mp_get_symbol (MP mp) { /* sets |cur_sym| to a safe symbol */ RESTART: get_t_next (mp); if ((mp->cur_sym == NULL) || mp_is_frozen(mp, mp->cur_sym)) { 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 (mp->cur_sym != NULL) mp->help_line[2] = "Sorry: You can\'t redefine my error-recovery tokens."; else if (mp->cur_cmd == string_token) delete_str_ref (mp->cur_mod_str); mp->cur_sym = mp->frozen_inaccessible; mp_ins_error (mp); goto RESTART; } } @ 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. @c static void mp_get_clear_symbol (MP mp) { mp_get_symbol (mp); mp_clear_symbol (mp, mp->cur_sym, false); } @ Here's another little subroutine; it checks that an equals sign or assignment sign comes along at the proper place in a macro definition. @c static void mp_check_equals (MP mp) { if (mp->cur_cmd != equals) if (mp->cur_cmd != assignment) { mp_missing_err (mp, "="); @.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."); mp_back_error (mp); } } @ 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}. @c static void mp_make_op_def (MP mp) { command_code m; /* the type of definition */ mp_node q, r; /* for list manipulation */ mp_subst_list_item *qm = NULL, *qn = NULL; m = mp->cur_mod; mp_get_symbol (mp); qm = xmalloc (1, sizeof (mp_subst_list_item)); qm->link = NULL; qm->info = mp->cur_sym; qm->info_mod = mp->cur_sym_mod; qm->value_data = 0; qm->value_mod = mp_expr_sym; mp_get_clear_symbol (mp); mp->warning_info = mp->cur_sym; mp_get_symbol (mp); qn = xmalloc (1, sizeof (mp_subst_list_item)); qn->link = qm; qn->info = mp->cur_sym; qn->info_mod = mp->cur_sym_mod; qn->value_data = 1; qn->value_mod = mp_expr_sym; get_t_next (mp); mp_check_equals (mp); mp->scanner_status = op_defining; q = mp_get_symbolic_node (mp); set_mp_sym_info (q, 0); /* |ref_count(q)=NULL;| */ r = mp_get_symbolic_node (mp); mp_link (q) = r; set_mp_sym_info (r, general_macro); mp_name_type (r) = mp_macro_sym; mp_link (r) = mp_scan_toks (mp, macro_def, qn, NULL, 0); mp->scanner_status = normal; eq_type (mp->warning_info) = m; equiv_node (mp->warning_info) = q; mp_get_x_next (mp); } @ Parameters to macros are introduced by the keywords \&{expr}, \&{suffix}, \&{text}, \&{primary}, \&{secondary}, and \&{tertiary}. @<Put each...@>= mp_primitive (mp, "expr", param_type, expr_param); @:expr_}{\&{expr} primitive@>; mp_primitive (mp, "suffix", param_type, suffix_param); @:suffix_}{\&{suffix} primitive@>; mp_primitive (mp, "text", param_type, text_param); @:text_}{\&{text} primitive@>; mp_primitive (mp, "primary", param_type, primary_macro); @:primary_}{\&{primary} primitive@>; mp_primitive (mp, "secondary", param_type, secondary_macro); @:secondary_}{\&{secondary} primitive@>; mp_primitive (mp, "tertiary", param_type, tertiary_macro); @:tertiary_}{\&{tertiary} primitive@> @ @<Cases of |print_cmd...@>= case param_type: if (m == expr_param) mp_print (mp, "expr"); else if (m == suffix_param) mp_print (mp, "suffix"); else if (m == text_param) mp_print (mp, "text"); else if (m == primary_macro) mp_print (mp, "primary"); else if (m == secondary_macro) mp_print (mp, "secondary"); else mp_print (mp, "tertiary"); break; @ 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|. Note that although the macro scanner allows |def = := enddef| and |def := = enddef|; |def = = enddef| and |def := := enddef| will generate an error because by the time the second of the two identical tokens is seen, its meaning has already become undefined. @c static void mp_scan_def (MP mp) { int m; /* the type of definition */ int n; /* the number of special suffix parameters */ int k; /* the total number of parameters */ int c; /* the kind of macro we're defining */ mp_subst_list_item *r = NULL, *rp = NULL; /* parameter-substitution list */ mp_node q; /* tail of the macro token list */ mp_node p; /* temporary storage */ quarterword sym_type; /* |expr_sym|, |suffix_sym|, or |text_sym| */ mp_sym l_delim, r_delim; /* matching delimiters */ m = mp->cur_mod; c = general_macro; mp_link (mp->hold_head) = NULL; q = mp_get_symbolic_node (mp); set_mp_sym_info (q, 0); /* |ref_count(q)=NULL;| */ r = NULL; @<Scan the token or variable to be defined; set |n|, |scanner_status|, and |warning_info|@>; k = n; if (mp->cur_cmd == left_delimiter) { @<Absorb delimited parameters, putting them into lists |q| and |r|@>; } if (mp->cur_cmd == param_type) { @<Absorb undelimited parameters, putting them into list |r|@>; } mp_check_equals (mp); p = mp_get_symbolic_node (mp); set_mp_sym_info (p, c); mp_name_type (p) = mp_macro_sym; mp_link (q) = p; @<Attach the replacement text to the tail of node |p|@>; mp->scanner_status = normal; mp_get_x_next (mp); } @ We don't put `|mp->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) { mp_link (p) = mp_scan_toks (mp, macro_def, r, NULL, (quarterword) n); } else { mp_node qq = mp_get_symbolic_node (mp); set_mp_sym_sym (qq, mp->bg_loc); mp_link (p) = qq; p = mp_get_symbolic_node (mp); set_mp_sym_sym (p, mp->eg_loc); mp_link (qq) = mp_scan_toks (mp, macro_def, r, p, (quarterword) n); } if (mp->warning_info_node == mp->bad_vardef) mp_flush_token_list (mp, value_node (mp->bad_vardef)) @ @<Glob...@>= mp_sym bg_loc; mp_sym eg_loc; /* hash addresses of `\.{begingroup}' and `\.{endgroup}' */ @ @<Scan the token or variable to be defined;...@>= if (m == start_def) { mp_get_clear_symbol (mp); mp->warning_info = mp->cur_sym; get_t_next (mp); mp->scanner_status = op_defining; n = 0; eq_type (mp->warning_info) = defined_macro; equiv_node (mp->warning_info) = q; } else { p = mp_scan_declared_variable (mp); mp_flush_variable (mp, equiv_node (mp_sym_sym (p)), mp_link (p), true); mp->warning_info_node = mp_find_variable (mp, p); mp_flush_node_list (mp, p); if (mp->warning_info_node == NULL) @<Change to `\.{a bad variable}'@>; mp->scanner_status = var_defining; n = 2; if (mp->cur_cmd == macro_special && mp->cur_mod == macro_suffix) { /* \.{\AT!\#} */ n = 3; get_t_next (mp); } mp_type (mp->warning_info_node) = (quarterword) (mp_unsuffixed_macro - 2 + n); /* |mp_suffixed_macro=mp_unsuffixed_macro+1| */ set_value_node (mp->warning_info_node, q); } @ @<Change to `\.{a bad variable}'@>= { 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."); mp_error (mp); mp->warning_info_node = mp->bad_vardef; } @ @<Initialize table entries@>= mp->bad_vardef = mp_get_value_node (mp); mp_name_type (mp->bad_vardef) = mp_root; value_sym (mp->bad_vardef) = mp->frozen_bad_vardef; @ @<Free table entries@>= mp_free_value_node (mp, mp->bad_vardef); @ @<Absorb delimited parameters, putting them into lists |q| and |r|@>= do { l_delim = mp->cur_sym; r_delim = mp->cur_sym2; get_t_next (mp); if ((mp->cur_cmd == param_type) && (mp->cur_mod == expr_param)) { sym_type = mp_expr_sym; } else if ((mp->cur_cmd == param_type) && (mp->cur_mod == suffix_param)) { sym_type = mp_suffix_sym; } else if ((mp->cur_cmd == param_type) && (mp->cur_mod == text_param)) { sym_type = mp_text_sym; } else { print_err ("Missing parameter type; `expr' will be assumed"); @.Missing parameter type@>; help1 ("You should've had `expr' or `suffix' or `text' here."); mp_back_error (mp); sym_type = mp_expr_sym; } @<Absorb parameter tokens for type |sym_type|@>; mp_check_delimiter (mp, l_delim, r_delim); get_t_next (mp); } while (mp->cur_cmd == left_delimiter) @ @<Absorb parameter tokens for type |sym_type|@>= do { mp_link (q) = mp_get_symbolic_node (mp); q = mp_link (q); mp_name_type (q) = sym_type; set_mp_sym_info (q, k); mp_get_symbol (mp); rp = xmalloc (1, sizeof (mp_subst_list_item)); rp->link = NULL; rp->value_data = k; rp->value_mod = sym_type; rp->info = mp->cur_sym; rp->info_mod = mp->cur_sym_mod; mp_check_param_size (mp, k); incr (k); rp->link = r; r = rp; get_t_next (mp); } while (mp->cur_cmd == comma) @ @<Absorb undelimited parameters, putting them into list |r|@>= { rp = xmalloc (1, sizeof (mp_subst_list_item)); rp->link = NULL; rp->value_data = k; if (mp->cur_mod == expr_param) { rp->value_mod = mp_expr_sym; c = expr_macro; } else if (mp->cur_mod == suffix_param) { rp->value_mod = mp_suffix_sym; c = suffix_macro; } else if (mp->cur_mod == text_param) { rp->value_mod = mp_text_sym; c = text_macro; } else { c = mp->cur_mod; rp->value_mod = mp_expr_sym; } mp_check_param_size (mp, k); incr (k); mp_get_symbol (mp); rp->info = mp->cur_sym; rp->info_mod = mp->cur_sym_mod; rp->link = r; r = rp; get_t_next (mp); if (c == expr_macro) if (mp->cur_cmd == of_token) { c = of_macro; rp = xmalloc (1, sizeof (mp_subst_list_item)); rp->link = NULL; mp_check_param_size (mp, k); rp->value_data = k; rp->value_mod = mp_expr_sym; mp_get_symbol (mp); rp->info = mp->cur_sym; rp->info_mod = mp->cur_sym_mod; rp->link = r; r = rp; get_t_next (mp); } } @* 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|. @<Declarations@>= static void mp_scan_primary (MP mp); static void mp_scan_secondary (MP mp); static void mp_scan_tertiary (MP mp); static void mp_scan_expression (MP mp); static void mp_scan_suffix (MP mp); static void mp_get_boolean (MP mp); static void mp_pass_text (MP mp); static void mp_conditional (MP mp); static void mp_start_input (MP mp); static void mp_begin_iteration (MP mp); static void mp_resume_iteration (MP mp); static void mp_stop_iteration (MP mp); @ A recursion depth counter is used to discover infinite recursions. (Near) infinite recursion is a problem because it translates into C function calls that eat up the available call stack. A better solution would be to depend on signal trapping, but that is problematic when Metapost is used as a library. @<Global...@>= int expand_depth_count; /* current expansion depth */ int expand_depth; /* current expansion depth */ @ The limit is set at |10000|, which should be enough to allow normal usages of metapost while preventing the most obvious crashes on most all operating systems, but the value can be raised if the runtime system allows a larger C stack. @^system dependencies@> @<Set initial...@>= mp->expand_depth = 10000; @ Even better would be if the system allows discovery of the amount of space available on the call stack. @^system dependencies@> @c static void mp_check_expansion_depth (MP mp) { if (mp->expand_depth_count >= mp->expand_depth) { mp_overflow (mp, "expansion depth", mp->expand_depth); } } @ An auxiliary subroutine called |expand| is used by |get_x_next| when it has to do exotic expansion commands. @c static void mp_expand (MP mp) { size_t k; /* something that we hope is |<=buf_size| */ size_t j; /* index into |str_pool| */ mp->expand_depth_count++; mp_check_expansion_depth (mp); if (internal_value (mp_tracing_commands) > unity) if (mp->cur_cmd != defined_macro) show_cur_cmd_mod; switch (mp->cur_cmd) { case if_test: mp_conditional (mp); /* this procedure is discussed in Part 36 below */ break; case fi_or_else: @<Terminate the current conditional and skip to \&{fi}@>; break; case input: @<Initiate or terminate input from a file@>; break; case iteration: if (mp->cur_mod == end_for) { @<Scold the user for having an extra \&{endfor}@>; } else { mp_begin_iteration (mp); /* this procedure is discussed in Part 37 below */ } break; case repeat_loop: @<Repeat a loop@>; break; case exit_test: @<Exit a loop if the proper time has come@>; break; case relax: break; case expand_after: @<Expand the token after the next token@>; break; case scan_tokens: @<Put a string into the input buffer@>; break; case defined_macro: mp_macro_call (mp, mp->cur_mod_node, NULL, mp->cur_sym); break; }; /* there are no other cases */ mp->expand_depth_count--; } @ @<Scold the user...@>= { 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."); mp_error (mp); } @ The processing of \&{input} involves the |start_input| subroutine, which will be declared later; the processing of \&{endinput} is trivial. @<Put each...@>= mp_primitive (mp, "input", input, 0); @:input_}{\&{input} primitive@>; mp_primitive (mp, "endinput", input, 1); @:end_input_}{\&{endinput} primitive@> @ @<Cases of |print_cmd_mod|...@>= case input: if (m == 0) mp_print (mp, "input"); else mp_print (mp, "endinput"); break; @ @<Initiate or terminate input...@>= if (mp->cur_mod > 0) mp->force_eof = true; else mp_start_input (mp) @ 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@>= { while (token_state && (nloc == NULL)) mp_end_token_list (mp); /* conserve stack space */ if (mp->loop_ptr == NULL) { 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."); mp_error (mp); } else { mp_resume_iteration (mp); /* this procedure is in Part 37 below */ } } @ @<Exit a loop if the proper time has come@>= { mp_get_boolean (mp); if (internal_value (mp_tracing_commands) > unity) mp_show_cmd_mod (mp, nullary, cur_exp_value ()); if (cur_exp_value () == true_code) { if (mp->loop_ptr == NULL) { print_err ("No loop is in progress"); @.No loop is in progress@>; help1 ("Why say `exitif' when there's nothing to exit from?"); if (mp->cur_cmd == semicolon) mp_error (mp); else mp_back_error (mp); } else { @<Exit prematurely from an iteration@>; } } else if (mp->cur_cmd != semicolon) { mp_missing_err (mp, ";"); @.Missing `;'@>; help2 ("After `exitif <boolean exp>' I expect to see a semicolon.", "I shall pretend that one was there."); mp_back_error (mp); } } @ Here we use the fact that |forever_text| is the only |token_type| that is less than |loop_text|. @<Exit prematurely...@>= { mp_node p = NULL; do { if (file_state) { mp_end_file_reading (mp); } else { if (token_type <= loop_text) p = nstart; mp_end_token_list (mp); } } while (p == NULL); if (p != mp->loop_ptr->info) mp_fatal_error (mp, "*** (loop confusion)"); @.loop confusion@>; mp_stop_iteration (mp); /* this procedure is in Part 34 below */ } @ @<Expand the token after the next token@>= { mp_node p; get_t_next (mp); p = mp_cur_tok (mp); get_t_next (mp); if (mp->cur_cmd < min_command) mp_expand (mp); else mp_back_input (mp); back_list (p); } @ @<Put a string into the input buffer@>= { mp_get_x_next (mp); mp_scan_primary (mp); if (mp->cur_exp.type != mp_string_type) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_disp_err (mp, NULL, "Not a string"); @.Not a string@>; help2 ("I'm going to flush this expression, since", "scantokens should be followed by a known string."); mp_put_get_flush_error (mp, new_expr); } else { mp_back_input (mp); if (length (cur_exp_str ()) > 0) @<Pretend we're reading a new one-line file@>; } } @ @<Pretend we're reading a new one-line file@>= { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_begin_file_reading (mp); name = is_scantok; k = mp->first + (size_t) length (cur_exp_str ()); if (k >= mp->max_buf_stack) { while (k >= mp->buf_size) { mp_reallocate_buffer (mp, (mp->buf_size + (mp->buf_size / 4))); } mp->max_buf_stack = k + 1; } j = 0; limit = (halfword) k; while (mp->first < (size_t) limit) { mp->buffer[mp->first] = *(cur_exp_str ()->str + j); j++; incr (mp->first); } mp->buffer[limit] = xord ('%'); mp->first = (size_t) (limit + 1); loc = start; mp_flush_cur_exp (mp, new_expr); } @ 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@> @<Declarations@>= static void mp_get_x_next (MP mp); @ @c void mp_get_x_next (MP mp) { mp_node save_exp; /* a capsule to save |cur_type| and |cur_exp| */ get_t_next (mp); if (mp->cur_cmd < min_command) { save_exp = mp_stash_cur_exp (mp); do { if (mp->cur_cmd == defined_macro) mp_macro_call (mp, mp->cur_mod_node, NULL, mp->cur_sym); else mp_expand (mp); get_t_next (mp); } while (mp->cur_cmd < min_command); mp_unstash_cur_exp (mp, save_exp); /* that restores |cur_type| and |cur_exp| */ } } @ 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 symbolic items, whose |info| fields point to the arguments. In other words, if |arg_list=NULL|, no arguments have been scanned yet; otherwise |mp_info(arg_list)| points to the first scanned argument, and |mp_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. @<Declarations@>= static void mp_macro_call (MP mp, mp_node def_ref, mp_node arg_list, mp_sym macro_name); @ @c void mp_macro_call (MP mp, mp_node def_ref, mp_node arg_list, mp_sym macro_name) { /* invokes a user-defined control sequence */ mp_node r; /* current node in the macro's token list */ mp_node p, q; /* for list manipulation */ integer n; /* the number of arguments */ mp_node tail = 0; /* tail of the argument list */ mp_sym l_delim = NULL, r_delim = NULL; /* a delimiter pair */ r = mp_link (def_ref); add_mac_ref (def_ref); if (arg_list == NULL) { n = 0; } else { @<Determine the number |n| of arguments already supplied, and set |tail| to the tail of |arg_list|@>; } if (internal_value (mp_tracing_macros) > 0) { @<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@>; } @ @<Show the text of the macro...@>= mp_begin_diagnostic (mp); mp_print_ln (mp); mp_print_macro_name (mp, arg_list, macro_name); if (n == 3) mp_print (mp, "@@#"); /* indicate a suffixed macro */ mp_show_macro (mp, def_ref, NULL, 100000); if (arg_list != NULL) { n = 0; p = arg_list; do { q = (mp_node)mp_sym_sym (p); mp_print_arg (mp, q, n, 0, 0); incr (n); p = mp_link (p); } while (p != NULL); } mp_end_diagnostic (mp, false) @ @<Declarations@>= static void mp_print_macro_name (MP mp, mp_node a, mp_sym n); @ @c void mp_print_macro_name (MP mp, mp_node a, mp_sym n) { mp_node p, q; /* they traverse the first part of |a| */ if (n != NULL) { mp_print_text (n); } else { p = (mp_node)mp_sym_sym (a); if (p == NULL) { mp_print_text (mp_sym_sym ((mp_node)mp_sym_sym (mp_link (a)))); } else { q = p; while (mp_link (q) != NULL) q = mp_link (q); mp_link (q) = (mp_node)mp_sym_sym (mp_link (a)); mp_show_token_list (mp, p, NULL, 1000, 0); mp_link (q) = NULL; } } } @ @<Declarations@>= static void mp_print_arg (MP mp, mp_node q, integer n, halfword b, quarterword bb); @ @c void mp_print_arg (MP mp, mp_node q, integer n, halfword b, quarterword bb) { if (q && mp_link (q) == MP_VOID) { mp_print_nl (mp, "(EXPR"); } else { if ((bb < mp_text_sym) && (b != text_macro)) mp_print_nl (mp, "(SUFFIX"); else mp_print_nl (mp, "(TEXT"); } mp_print_int (mp, n); mp_print (mp, ")<-"); if (q && mp_link (q) == MP_VOID) mp_print_exp (mp, q, 1); else mp_show_token_list (mp, q, NULL, 1000, 0); } @ @<Determine the number |n| of arguments already supplied...@>= { n = 1; tail = arg_list; while (mp_link (tail) != NULL) { incr (n); tail = mp_link (tail); } } @ @<Scan the remaining arguments, if any; set |r|...@>= mp->cur_cmd = comma + 1; /* anything |<>comma| will do */ while (mp_name_type (r) == mp_expr_sym || mp_name_type (r) == mp_suffix_sym || mp_name_type (r) == mp_text_sym) { @<Scan the delimited argument represented by |mp_sym_info(r)|@>; r = mp_link (r); } if (mp->cur_cmd == comma) { print_err ("Too many arguments to "); @.Too many arguments...@>; mp_print_macro_name (mp, arg_list, macro_name); mp_print_char (mp, xord (';')); mp_print_nl (mp, " Missing `"); mp_print_text (r_delim); @.Missing `)'...@>; mp_print (mp, "' 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."); mp_error (mp); } if (mp_sym_info (r) != general_macro) { @<Scan undelimited argument(s)@>; } r = mp_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 |mp_sym_info(r)|@>= if (mp->cur_cmd != comma) { mp_get_x_next (mp); if (mp->cur_cmd != left_delimiter) { print_err ("Missing argument to "); @.Missing argument...@>; mp_print_macro_name (mp, 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 (mp_name_type (r) == mp_suffix_sym || mp_name_type (r) == mp_text_sym) { set_cur_exp_value (0); /* todo: this was |null| */ mp->cur_exp.type = mp_token_list; } else { set_cur_exp_value (0); mp->cur_exp.type = mp_known; } mp_back_error (mp); mp->cur_cmd = right_delimiter; goto FOUND; } l_delim = mp->cur_sym; r_delim = mp->cur_sym2; } @<Scan the argument represented by |mp_sym_info(r)|@>; if (mp->cur_cmd != comma) @<Check that the proper right delimiter was present@>; FOUND: @<Append the current expression to |arg_list|@> @ @<Check that the proper right delim...@>= if ((mp->cur_cmd != right_delimiter) || (mp->cur_sym2 != l_delim)) { if (mp_name_type (mp_link (r)) == mp_expr_sym || mp_name_type (mp_link (r)) == mp_suffix_sym || mp_name_type (mp_link (r)) == mp_text_sym) { mp_missing_err (mp, ","); @.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."); mp_back_error (mp); mp->cur_cmd = comma; } else { mp_missing_err (mp, mp_str (mp, 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."); mp_back_error (mp); } } @ A \&{suffix} or \&{text} parameter will 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|@>= { p = mp_get_symbolic_node (mp); if (mp->cur_exp.type == mp_token_list) set_mp_sym_sym (p, mp->cur_exp.data.node); else set_mp_sym_sym (p, mp_stash_cur_exp (mp)); if (internal_value (mp_tracing_macros) > 0) { mp_begin_diagnostic (mp); mp_print_arg (mp, (mp_node)mp_sym_sym (p), n, mp_sym_info (r), mp_name_type (r)); mp_end_diagnostic (mp, false); } if (arg_list == NULL) arg_list = p; else mp_link (tail) = p; tail = p; incr (n); } @ @<Scan the argument represented by |mp_sym_info(r)|@>= if (mp_name_type (r) == mp_text_sym) { mp_scan_text_arg (mp, l_delim, r_delim); } else { mp_get_x_next (mp); if (mp_name_type (r) == mp_suffix_sym) mp_scan_suffix (mp); else mp_scan_expression (mp); } @ 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. @<Declarations@>= static void mp_scan_text_arg (MP mp, mp_sym l_delim, mp_sym r_delim); @ @c void mp_scan_text_arg (MP mp, mp_sym l_delim, mp_sym r_delim) { integer balance; /* excess of |l_delim| over |r_delim| */ mp_node p; /* list tail */ mp->warning_info = l_delim; mp->scanner_status = absorbing; p = mp->hold_head; balance = 1; mp_link (mp->hold_head) = NULL; while (1) { get_t_next (mp); if (l_delim == NULL) { @<Adjust the balance for an undelimited argument; |break| if done@>; } else { @<Adjust the balance for a delimited argument; |break| if done@>; } mp_link (p) = mp_cur_tok (mp); p = mp_link (p); } set_cur_exp_node (mp_link (mp->hold_head)); mp->cur_exp.type = mp_token_list; mp->scanner_status = normal; } @ @<Adjust the balance for a delimited argument...@>= if (mp->cur_cmd == right_delimiter) { if (mp->cur_sym2 == l_delim) { decr (balance); if (balance == 0) break; } } else if (mp->cur_cmd == left_delimiter) { if (mp->cur_sym2 == r_delim) incr (balance); } @ @<Adjust the balance for an undelimited...@>= if (end_of_statement) { /* |cur_cmd=semicolon|, |end_group|, or |stop| */ if (balance == 1) { break; } else { if (mp->cur_cmd == end_group) decr (balance); } } else if (mp->cur_cmd == begin_group) { incr (balance); } @ @<Scan undelimited argument(s)@>= { if (mp_sym_info (r) < text_macro) { mp_get_x_next (mp); if (mp_sym_info (r) != suffix_macro) { if ((mp->cur_cmd == equals) || (mp->cur_cmd == assignment)) mp_get_x_next (mp); } } switch (mp_sym_info (r)) { case primary_macro: mp_scan_primary (mp); break; case secondary_macro: mp_scan_secondary (mp); break; case tertiary_macro: mp_scan_tertiary (mp); break; case expr_macro: mp_scan_expression (mp); break; case of_macro: @<Scan an expression followed by `\&{of} $\langle$primary$\rangle$'@>; break; case suffix_macro: @<Scan a suffix with optional delimiters@>; break; case text_macro: mp_scan_text_arg (mp, NULL, NULL); break; } /* there are no other cases */ mp_back_input (mp); @<Append the current expression to |arg_list|@>; } @ @<Scan an expression followed by `\&{of} $\langle$primary$\rangle$'@>= { mp_scan_expression (mp); p = mp_get_symbolic_node (mp); set_mp_sym_sym (p, mp_stash_cur_exp (mp)); if (internal_value (mp_tracing_macros) > 0) { mp_begin_diagnostic (mp); mp_print_arg (mp, (mp_node)mp_sym_sym (p), n, 0, 0); mp_end_diagnostic (mp, false); } if (arg_list == NULL) arg_list = p; else mp_link (tail) = p; tail = p; incr (n); if (mp->cur_cmd != of_token) { mp_missing_err (mp, "of"); mp_print (mp, " for "); @.Missing `of'@>; mp_print_macro_name (mp, arg_list, macro_name); help1 ("I've got the first argument; will look now for the other."); mp_back_error (mp); } mp_get_x_next (mp); mp_scan_primary (mp); } @ @<Scan a suffix with optional delimiters@>= { if (mp->cur_cmd != left_delimiter) { l_delim = NULL; } else { l_delim = mp->cur_sym; r_delim = mp->cur_sym2; mp_get_x_next (mp); } mp_scan_suffix (mp); if (l_delim != NULL) { if ((mp->cur_cmd != right_delimiter) || (mp->cur_sym2 != l_delim)) { mp_missing_err (mp, mp_str (mp, 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."); mp_back_error (mp); } mp_get_x_next (mp); } } @ 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 && (nloc == NULL)) mp_end_token_list (mp); /* conserve stack space */ if (mp->param_ptr + n > mp->max_param_stack) { mp->max_param_stack = mp->param_ptr + n; mp_check_param_size (mp, mp->max_param_stack); @:MetaPost capacity exceeded parameter stack size}{\quad parameter stack size@> } mp_begin_token_list (mp, def_ref, (quarterword) macro); if (macro_name) name = text (macro_name); nloc = r; if (n > 0) { p = arg_list; do { mp->param_stack[mp->param_ptr] = (mp_node)mp_sym_sym (p); incr (mp->param_ptr); p = mp_link (p); } while (p != NULL); mp_flush_node_list (mp, arg_list); } @ It's sometimes necessary to put a single argument onto |param_stack|. The |stack_argument| subroutine does this. @c static void mp_stack_argument (MP mp, mp_node p) { if (mp->param_ptr == mp->max_param_stack) { incr (mp->max_param_stack); mp_check_param_size (mp, mp->max_param_stack); } mp->param_stack[mp->param_ptr] = p; incr (mp->param_ptr); } @* 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 non-symbolic 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_line_field(A) ((mp_if_node)(A))->if_line_field_ @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} */ @(mpmp.h@>= typedef struct mp_if_node_data { NODE_BODY; int if_line_field_; } mp_if_node_data; typedef struct mp_if_node_data *mp_if_node; @ @d if_node_size sizeof(struct mp_if_node_data) /* number of words in stack entry for conditionals */ @c static mp_node mp_get_if_node (MP mp) { mp_if_node p = (mp_if_node) xmalloc (1, if_node_size); add_var_used (if_node_size); memset (p, 0, if_node_size); mp_type (p) = mp_if_node_type; return (mp_node) p; } @ @<Glob...@>= mp_node cond_ptr; /* top of the condition stack */ integer if_limit; /* upper bound on |fi_or_else| codes */ quarterword cur_if; /* type of conditional being worked on */ integer if_line; /* line where that conditional began */ @ @<Set init...@>= mp->cond_ptr = NULL; mp->if_limit = normal; mp->cur_if = 0; mp->if_line = 0; @ @<Put each...@>= mp_primitive (mp, "if", if_test, if_code); @:if_}{\&{if} primitive@>; mp_primitive (mp, "fi", fi_or_else, fi_code); mp->frozen_fi = mp_frozen_primitive (mp, "fi", fi_or_else, fi_code); @:fi_}{\&{fi} primitive@>; mp_primitive (mp, "else", fi_or_else, else_code); @:else_}{\&{else} primitive@>; mp_primitive (mp, "elseif", fi_or_else, else_if_code); @:else_if_}{\&{elseif} primitive@> @ @<Cases of |print_cmd_mod|...@>= case if_test: case fi_or_else: switch (m) { case if_code: mp_print (mp, "if"); break; case fi_code: mp_print (mp, "fi"); break; case else_code: mp_print (mp, "else"); break; default: mp_print (mp, "elseif"); break; } break; @ 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. @c void mp_pass_text (MP mp) { integer l = 0; mp->scanner_status = skipping; mp->warning_info_line = mp_true_line (mp); while (1) { get_t_next (mp); if (mp->cur_cmd <= fi_or_else) { if (mp->cur_cmd < fi_or_else) { incr (l); } else { if (l == 0) break; if (mp->cur_mod == fi_code) decr (l); } } else { @<Decrease the string reference count, if the current token is a string@>; } } mp->scanner_status = normal; } @ @<Decrease the string reference count...@>= if (mp->cur_cmd == string_token) { delete_str_ref (mp->cur_mod_str); } @ 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@>= { p = mp_get_if_node (mp); mp_link (p) = mp->cond_ptr; mp_type (p) = (quarterword) mp->if_limit; mp_name_type (p) = mp->cur_if; if_line_field (p) = mp->if_line; mp->cond_ptr = p; mp->if_limit = if_code; mp->if_line = mp_true_line (mp); mp->cur_if = if_code; } @ @<Pop the condition stack@>= { mp_node p = mp->cond_ptr; mp->if_line = if_line_field (p); mp->cur_if = mp_name_type (p); mp->if_limit = mp_type (p); mp->cond_ptr = mp_link (p); mp_free_node (mp, p, if_node_size); } @ Here's a procedure that changes the |if_limit| code corresponding to a given value of |cond_ptr|. @c static void mp_change_if_limit (MP mp, quarterword l, mp_node p) { mp_node q; if (p == mp->cond_ptr) { mp->if_limit = l; /* that's the easy case */ } else { q = mp->cond_ptr; while (1) { if (q == NULL) mp_confusion (mp, "if"); @:this can't happen if}{\quad if@>; if (mp_link (q) == p) { mp_type (q) = l; return; } q = mp_link (q); } } } @ The user is supposed to put colons into the proper parts of conditional statements. Therefore, \MP\ has to check for their presence. @c static void mp_check_colon (MP mp) { if (mp->cur_cmd != colon) { mp_missing_err (mp, ":"); @.Missing `:'@>; help2 ("There should've been a colon after the condition.", "I shall pretend that one was there."); mp_back_error (mp); } } @ 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@> @c void mp_conditional (MP mp) { mp_node save_cond_ptr; /* |cond_ptr| corresponding to this conditional */ int new_if_limit; /* future value of |if_limit| */ mp_node p; /* temporary register */ @<Push the condition stack@>; save_cond_ptr = mp->cond_ptr; RESWITCH: mp_get_boolean (mp); new_if_limit = else_if_code; if (internal_value (mp_tracing_commands) > unity) { @<Display the boolean value of |cur_exp|@>; } FOUND: mp_check_colon (mp); if (cur_exp_value () == true_code) { mp_change_if_limit (mp, (quarterword) new_if_limit, save_cond_ptr); return; /* wait for \&{elseif}, \&{else}, or \&{fi} */ }; @<Skip to \&{elseif} or \&{else} or \&{fi}, then |goto done|@>; DONE: mp->cur_if = (quarterword) mp->cur_mod; mp->if_line = mp_true_line (mp); if (mp->cur_mod == fi_code) { @<Pop the condition stack@> } else if (mp->cur_mod == else_if_code) { goto RESWITCH; } else { set_cur_exp_value (true_code); new_if_limit = fi_code; mp_get_x_next (mp); goto FOUND; } } @ 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}...@>= while (1) { mp_pass_text (mp); if (mp->cond_ptr == save_cond_ptr) goto DONE; else if (mp->cur_mod == fi_code) @<Pop the condition stack@>; } @ @<Display the boolean value...@>= { mp_begin_diagnostic (mp); if (cur_exp_value () == true_code) mp_print (mp, "{true}"); else mp_print (mp, "{false}"); mp_end_diagnostic (mp, false); } @ 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 (mp->cur_mod > mp->if_limit) { if (mp->if_limit == if_code) { /* condition not yet evaluated */ mp_missing_err (mp, ":"); @.Missing `:'@>; mp_back_input (mp); mp->cur_sym = mp->frozen_colon; mp_ins_error (mp); } else { print_err ("Extra "); mp_print_cmd_mod (mp, fi_or_else, mp->cur_mod); @.Extra else@> @.Extra elseif@> @.Extra fi@>; help1 ("I'm ignoring this; it doesn't match any if."); mp_error (mp); } } else { while (mp->cur_mod != fi_code) mp_pass_text (mp); /* skip to \&{fi} */ @<Pop the condition stack@>; } @* 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 |loop_ptr.info| points to the iterative text of the current (innermost) loop, and |loop_ptr.link| points to the data for any other loops that enclose the current one. A loop-control node also has two other fields, called |type| and |list|, whose contents depend on the type of loop: \yskip\indent|loop_ptr.type=NULL| means that the link of |loop_ptr.list| points to a list of symbolic nodes whose |info| fields point to the remaining argument values of a suffix list and expression list. In this case, an extra field |loop_ptr.start_list| is needed to make sure that |resume_operation| skips ahead. \yskip\indent|loop_ptr.type=MP_VOID| means that the current loop is `\&{forever}'. \yskip\indent|loop_ptr.type=PROGRESSION_FLAG| means that |loop_ptr.value|, |loop_ptr.step_size|, and |loop_ptr.final_value| contain the data for an arithmetic progression. \yskip\indent|loop_ptr.type=p>PROGRESSION_FLAG| means that |p| points to an edge header and |loop_ptr.list| points into the graphical object list for that edge header. @d PROGRESSION_FLAG (mp_node)(2) /* |NULL+2| */ /* |loop_type| value when |loop_list| points to a progression node */ @<Types...@>= typedef struct mp_loop_data { mp_node info; /* iterative text of this loop */ mp_node type; /* the special type of this loop, or a pointer into mem */ mp_node list; /* the remaining list elements */ mp_node list_start; /* head fo the list of elements */ scaled value; /* current arithmetic value */ scaled step_size; /* arithmetic step size */ scaled final_value; /* end arithmetic value */ struct mp_loop_data *link; /* the enclosing loop, if any */ } mp_loop_data; @ @<Glob...@>= mp_loop_data *loop_ptr; /* top of the loop-control-node stack */ @ @<Set init...@>= mp->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. @c static void mp_bad_for (MP mp, const char *s) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_disp_err (mp, NULL, "Improper "); /* show the bad expression above the message */ @.Improper...replaced by 0@>; mp_print (mp, s); mp_print (mp, " 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."); mp_put_get_flush_error (mp, new_expr); } @ 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.) @c void mp_begin_iteration (MP mp) { halfword m; /* |start_for| (\&{for}) or |start_forsuffixes| (\&{forsuffixes}) */ mp_sym n; /* hash address of the current symbol */ mp_loop_data *s; /* the new loop-control node */ mp_subst_list_item *p = NULL; /* substitution list for |scan_toks| */ mp_node q; /* link manipulation register */ m = mp->cur_mod; n = mp->cur_sym; s = xmalloc (1, sizeof (mp_loop_data)); s->type = s->list = s->info = s->list_start = NULL; s->link = NULL; if (m == start_forever) { s->type = MP_VOID; p = NULL; mp_get_x_next (mp); } else { mp_get_symbol (mp); p = xmalloc (1, sizeof (mp_subst_list_item)); p->link = NULL; p->info = mp->cur_sym; p->info_mod = mp->cur_sym_mod; p->value_data = 0; if (m == start_for) { p->value_mod = mp_expr_sym; } else { /* |start_forsuffixes| */ p->value_mod = mp_suffix_sym; } mp_get_x_next (mp); if (mp->cur_cmd == within_token) { @<Set up a picture iteration@>; } else { @<Check for the assignment in a loop header@>; @<Scan the values to be used in the loop@>; } } @<Check for the presence of a colon@>; @<Scan the loop text and put it on the loop control stack@>; mp_resume_iteration (mp); } @ @<Check for the assignment in a loop header@>= if ((mp->cur_cmd != equals) && (mp->cur_cmd != assignment)) { mp_missing_err (mp, "="); @.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."); mp_back_error (mp); } @ @<Check for the presence of a colon@>= if (mp->cur_cmd != colon) { mp_missing_err (mp, ":"); @.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."); mp_back_error (mp); } @ We append a special |mp->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 |mp->frozen_repeat_loop| is an \&{outer} token, so it won't be lost accidentally.) @ @<Scan the loop text...@>= q = mp_get_symbolic_node (mp); set_mp_sym_sym (q, mp->frozen_repeat_loop); mp->scanner_status = loop_defining; mp->warning_info = n; s->info = mp_scan_toks (mp, iteration, p, q, 0); mp->scanner_status = normal; s->link = mp->loop_ptr; mp->loop_ptr = s @ @<Initialize table...@>= mp->frozen_repeat_loop = mp_frozen_primitive (mp, " ENDFOR", repeat_loop + outer_tag, 0); @ The loop text is inserted into \MP's scanning apparatus by the |resume_iteration| routine. @c void mp_resume_iteration (MP mp) { mp_node p, q; /* link registers */ p = mp->loop_ptr->type; if (p == PROGRESSION_FLAG) { set_cur_exp_value (mp->loop_ptr->value); if (@<The arithmetic progression has ended@>) { mp_stop_iteration (mp); return; } mp->cur_exp.type = mp_known; q = mp_stash_cur_exp (mp); /* make |q| an \&{expr} argument */ mp->loop_ptr->value = cur_exp_value () + mp->loop_ptr->step_size; /* set |value(p)| for the next iteration */ /* detect numeric overflow */ if ((mp->loop_ptr->step_size > 0) && (mp->loop_ptr->value < cur_exp_value ())) { if (mp->loop_ptr->final_value > 0) { mp->loop_ptr->value = mp->loop_ptr->final_value; mp->loop_ptr->final_value--; } else { mp->loop_ptr->value = mp->loop_ptr->final_value + 1; } } else if ((mp->loop_ptr->step_size < 0) && (mp->loop_ptr->value > cur_exp_value ())) { if (mp->loop_ptr->final_value < 0) { mp->loop_ptr->value = mp->loop_ptr->final_value; mp->loop_ptr->final_value++; } else { mp->loop_ptr->value = mp->loop_ptr->final_value - 1; } } } else if (p == NULL) { p = mp->loop_ptr->list; if (p != NULL && p == mp->loop_ptr->list_start) { q = p; p = mp_link (p); mp_free_symbolic_node (mp, q); mp->loop_ptr->list = p; } if (p == NULL) { mp_stop_iteration (mp); return; } mp->loop_ptr->list = mp_link (p); q = (mp_node)mp_sym_sym (p); mp_free_symbolic_node (mp, p); } else if (p == MP_VOID) { mp_begin_token_list (mp, mp->loop_ptr->info, (quarterword) forever_text); return; } else { @<Make |q| a capsule containing the next picture component from |loop_list(loop_ptr)| or |goto not_found|@>; } mp_begin_token_list (mp, mp->loop_ptr->info, (quarterword) loop_text); mp_stack_argument (mp, q); if (internal_value (mp_tracing_commands) > unity) { @<Trace the start of a loop@>; } return; NOT_FOUND: mp_stop_iteration (mp); } @ @<The arithmetic progression has ended@>= ((mp->loop_ptr->step_size > 0) && (cur_exp_value () > mp->loop_ptr->final_value)) || ((mp->loop_ptr->step_size < 0) && (cur_exp_value () < mp->loop_ptr->final_value)) @ @<Trace the start of a loop@>= { mp_begin_diagnostic (mp); mp_print_nl (mp, "{loop value="); @.loop value=n@>; if ((q != NULL) && (mp_link (q) == MP_VOID)) mp_print_exp (mp, q, 1); else mp_show_token_list (mp, q, NULL, 50, 0); mp_print_char (mp, xord ('}')); mp_end_diagnostic (mp, false); } @ @<Make |q| a capsule containing the next picture component from...@>= { q = mp->loop_ptr->list; if (q == NULL) goto NOT_FOUND; skip_component (q) goto NOT_FOUND; set_cur_exp_node (mp_copy_objects (mp, mp->loop_ptr->list, q)); mp_init_bbox (mp, cur_exp_node ()); mp->cur_exp.type = mp_picture_type; mp->loop_ptr->list = q; q = mp_stash_cur_exp (mp); } @ 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. @c void mp_stop_iteration (MP mp) { mp_node p, q; /* the usual */ mp_loop_data *tmp; /* for free() */ p = mp->loop_ptr->type; if (p == PROGRESSION_FLAG) { ; } else if (p == NULL) { q = mp->loop_ptr->list; while (q != NULL) { p = (mp_node)mp_sym_sym (q); if (p != NULL) { if (mp_link (p) == MP_VOID) { /* it's an \&{expr} parameter */ mp_recycle_value (mp, p); mp_free_node (mp, p, value_node_size); } else { mp_flush_token_list (mp, p); /* it's a \&{suffix} or \&{text} parameter */ } } p = q; q = mp_link (q); mp_free_symbolic_node (mp, p); } } else if (p > PROGRESSION_FLAG) { delete_edge_ref (p); } tmp = mp->loop_ptr; mp->loop_ptr = tmp->link; mp_flush_token_list (mp, tmp->info); xfree (tmp); } @ 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=start_for|) or a \&{forsuffixes} construction (if |m=start_forsuffixes|). @<Scan the values to be used in the loop@>= s->type = NULL; s->list = mp_get_symbolic_node (mp); s->list_start = s->list; q = s->list; do { mp_get_x_next (mp); if (m != start_for) { mp_scan_suffix (mp); } else { if (mp->cur_cmd >= colon) if (mp->cur_cmd <= comma) goto CONTINUE; mp_scan_expression (mp); if (mp->cur_cmd == step_token) if (q == s->list) { @<Prepare for step-until construction and |break|@>; } set_cur_exp_node (mp_stash_cur_exp (mp)); } mp_link (q) = mp_get_symbolic_node (mp); q = mp_link (q); set_mp_sym_sym (q, mp->cur_exp.data.node); if (m == start_for) mp_name_type (q) = mp_expr_sym; else if (m == start_forsuffixes) mp_name_type (q) = mp_suffix_sym; mp->cur_exp.type = mp_vacuous; CONTINUE: ; } while (mp->cur_cmd == comma) @ @<Prepare for step-until construction and |break|@>= { if (mp->cur_exp.type != mp_known) mp_bad_for (mp, "initial value"); s->value = cur_exp_value (); mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_known) mp_bad_for (mp, "step size"); s->step_size = cur_exp_value (); if (mp->cur_cmd != until_token) { mp_missing_err (mp, "until"); @.Missing `until'@>; help2 ("I assume you meant to say `until' after `step'.", "So I'll look for the final value and colon next."); mp_back_error (mp); } mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_known) mp_bad_for (mp, "final value"); s->final_value = cur_exp_value (); s->type = PROGRESSION_FLAG; break; } @ 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@>= { mp_get_x_next (mp); mp_scan_expression (mp); @<Make sure the current expression is a known picture@>; s->type = mp->cur_exp.data.node; mp->cur_exp.type = mp_vacuous; q = mp_link (dummy_loc (mp->cur_exp.data.node)); if (q != NULL) if (is_start_or_stop (q)) if (mp_skip_1component (mp, q) == NULL) q = mp_link (q); s->list = q; } @ @<Make sure the current expression is a known picture@>= if (mp->cur_exp.type != mp_picture_type) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); new_expr.data.node = mp_get_edge_header_node (mp); mp_disp_err (mp, NULL, "Improper iteration spec has been replaced by nullpicture"); help1 ("When you say `for x in p', p must be a known picture."); mp_put_get_flush_error (mp, new_expr); mp_init_edges (mp, mp->cur_exp.data.node); mp->cur_exp.type = mp_picture_type; } @* 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. 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...@>= char *cur_name; /* name of file just scanned */ char *cur_area; /* file area just scanned, or \.{""} */ char *cur_ext; /* file extension just scanned, or \.{""} */ @ It is easier to maintain reference counts if we assign initial values. @<Set init...@>= mp->cur_name = xstrdup (""); mp->cur_area = xstrdup (""); mp->cur_ext = xstrdup (""); @ @<Dealloc variables@>= xfree (mp->cur_area); xfree (mp->cur_name); xfree (mp->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. @<Glob...@>= integer area_delimiter; /* most recent `\.>' or `\.:' relative to |str_start[str_ptr]| */ integer ext_delimiter; /* the relevant `\..', if any */ boolean quoted_filename; /* whether the filename is wrapped in " markers */ @ Here now is the first of the system-dependent routines for file name scanning. @^system dependencies@> @<Declarations@>= static void mp_begin_name (MP mp); static boolean mp_more_name (MP mp, ASCII_code c); static void mp_end_name (MP mp); @ @c void mp_begin_name (MP mp) { xfree (mp->cur_name); xfree (mp->cur_area); xfree (mp->cur_ext); mp->area_delimiter = -1; mp->ext_delimiter = -1; mp->quoted_filename = false; } @ And here's the second. @^system dependencies@> @c #ifndef IS_DIR_SEP #define IS_DIR_SEP(c) (c=='/' || c=='\\') #endif boolean mp_more_name (MP mp, ASCII_code c) { if (c == '"') { mp->quoted_filename = !mp->quoted_filename; } else if ((c == ' ' || c == '\t') && (mp->quoted_filename == false)) { return false; } else { if (IS_DIR_SEP (c)) { mp->area_delimiter = (integer) mp->cur_length; mp->ext_delimiter = -1; } else if (c == '.') { mp->ext_delimiter = (integer) mp->cur_length; } append_char (c); /* contribute |c| to the current string */ } return true; } @ The third. @^system dependencies@> @d copy_pool_segment(A,B,C) { A = xmalloc(C+1,sizeof(char)); (void)memcpy(A,(char *)(mp->cur_string+B),C); A[C] = 0;} @c void mp_end_name (MP mp) { size_t s = 0; /* length of area, name, and extension */ size_t len; /* "my/w.mp" */ if (mp->area_delimiter < 0) { mp->cur_area = xstrdup (""); } else { len = (size_t) mp->area_delimiter - s + 1; copy_pool_segment (mp->cur_area, s, len); s += len; } if (mp->ext_delimiter < 0) { mp->cur_ext = xstrdup (""); len = (unsigned) (mp->cur_length - s); } else { copy_pool_segment (mp->cur_ext, mp->ext_delimiter, (mp->cur_length - (size_t) mp->ext_delimiter)); len = (size_t) mp->ext_delimiter - s; } copy_pool_segment (mp->cur_name, s, len); reset_cur_string (mp); } @ 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...@>= static void mp_print_file_name (MP mp, char *n, char *a, char *e) { boolean must_quote = false; if (((a != NULL) && (strchr (a, ' ') != NULL)) || ((n != NULL) && (strchr (n, ' ') != NULL)) || ((e != NULL) && (strchr (e, ' ') != NULL))) must_quote = true; if (must_quote) mp_print_char (mp, (ASCII_code) '"'); mp_print (mp, a); mp_print (mp, n); mp_print (mp, e); if (must_quote) mp_print_char (mp, (ASCII_code) '"'); } @ 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(A) { mp->name_of_file[k++]=(char)xchr(xord((ASCII_code)(A))); } @ @c void mp_pack_file_name (MP mp, const char *n, const char *a, const char *e) { integer k; /* number of positions filled in |name_of_file| */ const char *j; /* a character index */ size_t slen; k = 0; assert (n != NULL); xfree (mp->name_of_file); slen = strlen (n) + 1; if (a != NULL) slen += strlen (a); if (e != NULL) slen += strlen (e); mp->name_of_file = xmalloc (slen, 1); if (a != NULL) { for (j = a; *j != '\0'; j++) { append_to_name (*j); } } for (j = n; *j != '\0'; j++) { append_to_name (*j); } if (e != NULL) { for (j = e; *j != '\0'; j++) { append_to_name (*j); } } mp->name_of_file[k] = 0; } @ @<Internal library declarations@>= void mp_pack_file_name (MP mp, const char *n, const char *a, const char *e); @ @<Option variables@>= char *mem_name; /* for commandline */ @ Stripping a |.mem| extension here is for backward compatibility. @<Find and load preload file, if required@>= if (!opt->ini_version) { mp->mem_name = xstrdup (opt->mem_name); if (mp->mem_name) { size_t l = strlen (mp->mem_name); if (l > 4) { char *test = strstr (mp->mem_name, ".mem"); if (test == mp->mem_name + l - 4) { *test = 0; } } } if (mp->mem_name != NULL) { if (!mp_open_mem_file (mp)) { mp->history = mp_fatal_error_stop; mp_jump_out (mp); } } } @ @<Dealloc variables@>= xfree (mp->mem_name); @ This part of the program becomes active when a ``virgin'' \MP\ is trying to get going, just after the preliminary initialization. The buffer contains the first line of input in |buffer[loc..(last-1)]|, where |loc<last| and |buffer[loc]<>""|. @<Declarations@>= static boolean mp_open_mem_name (MP mp); static boolean mp_open_mem_file (MP mp); @ @c boolean mp_open_mem_name (MP mp) { if (mp->mem_name != NULL) { size_t l = strlen (mp->mem_name); char *s = xstrdup (mp->mem_name); if (l > 4) { char *test = strstr (s, ".mp"); if (test == NULL || test != s + l - 4) { s = xrealloc (s, l + 5, 1); strcat (s, ".mp"); } } else { s = xrealloc (s, l + 5, 1); strcat (s, ".mp"); } mp->mem_file = (mp->open_file) (mp, s, "r", mp_filetype_program); xfree (mp->name_of_file); mp->name_of_file = xstrdup (s); free (s); if (mp->mem_file) return true; } return false; } boolean mp_open_mem_file (MP mp) { if (mp->mem_file != NULL) return true; if (mp_open_mem_name (mp)) return true; if (mp_xstrcmp (mp->mem_name, "plain")) { wake_up_terminal; wterm_ln ("Sorry, I can\'t find the '"); wterm (mp->mem_name); wterm ("' preload file; will try 'plain'."); @.Sorry, I can't find...@>; update_terminal; /* now pull out all the stops: try for the system \.{plain} file */ xfree (mp->mem_name); mp->mem_name = xstrdup ("plain"); if (mp_open_mem_name (mp)) return true; } wake_up_terminal; wterm_ln ("I can't find the 'plain' preload file!\n"); @.I can't find PLAIN...@> @.plain@>; return false; } @ 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. @^system dependencies@> @<Declarations@>= #define mp_a_make_name_string(A,B) mp_make_name_string(A) @ @c static str_number mp_make_name_string (MP mp) { int k; /* index into |name_of_file| */ int name_length = (int) strlen (mp->name_of_file); str_room (name_length); for (k = 0; k < name_length; k++) { append_char (xord ((ASCII_code) mp->name_of_file[k])); } return mp_make_string (mp); } @ 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@> @c static void mp_scan_file_name (MP mp) { mp_begin_name (mp); while (mp->buffer[loc] == ' ') incr (loc); while (1) { if ((mp->buffer[loc] == ';') || (mp->buffer[loc] == '%')) break; if (!mp_more_name (mp, mp->buffer[loc])) break; incr (loc); } mp_end_name (mp); } @ Here is another version that takes its input from a string. @<Declare subroutines for parsing file names@>= void mp_str_scan_file (MP mp, str_number s); @ @c void mp_str_scan_file (MP mp, str_number s) { size_t p, q; /* current position and stopping point */ mp_begin_name (mp); p = 0; q = length (s); while (p < q) { if (!mp_more_name (mp, *(s->str + p))) break; incr (p); } mp_end_name (mp); } @ And one that reads from a |char*|. @<Declare subroutines for parsing file names@>= extern void mp_ptr_scan_file (MP mp, char *s); @ @c void mp_ptr_scan_file (MP mp, char *s) { char *p, *q; /* current position and stopping point */ mp_begin_name (mp); p = s; q = p + strlen (s); while (p < q) { if (!mp_more_name (mp, (ASCII_code) (*p))) break; p++; } mp_end_name (mp); } @ The option variable |job_name| contains the file name that was first \&{input} by the user. This name is used to initialize the |job_name| global as well as the |mp_job_name| internal, and is extended by `\.{.log}' and `\.{ps}' and `\.{.mem}' and `\.{.tfm}' in order to make the names of \MP's output files. @<Glob...@>= boolean log_opened; /* has the transcript file been opened? */ char *log_name; /* full name of the log file */ @ @<Option variables@>= char *job_name; /* principal file name */ @ Initially |job_name=NULL|; it becomes nonzero as soon as the true name is known. We have |job_name=NULL| 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. @<Allocate or ...@>= mp->job_name = mp_xstrdup (mp, opt->job_name); if (mp->job_name != NULL) { char *s = mp->job_name + strlen (mp->job_name); while (s > mp->job_name) { if (*s == '.') { *s = '\0'; } s--; } } if (opt->noninteractive) { if (mp->job_name == NULL) mp->job_name = mp_xstrdup (mp, mp->mem_name); } mp->log_opened = false; @ Cannot do this earlier because at the |<Allocate or ...>|, the string pool is not yet initialized. @<Fix up |mp->internal[mp_job_name]|@>= if (mp->job_name != NULL) { if (internal_string (mp_job_name) != 0) delete_str_ref (internal_string (mp_job_name)); internal_string (mp_job_name) = mp_rts (mp, mp->job_name); } @ @<Dealloc variables@>= xfree (mp->job_name); @ 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 mp_pack_file_name(mp, mp->cur_name,mp->cur_area,mp->cur_ext) @<Internal library ...@>= void mp_pack_job_name (MP mp, const char *s); @ @c void mp_pack_job_name (MP mp, const char *s) { /* |s = ".log"|, |".mem"|, |".ps"|, or .\\{nnn} */ xfree (mp->cur_name); mp->cur_name = xstrdup (mp->job_name); xfree (mp->cur_area); mp->cur_area = xstrdup (""); xfree (mp->cur_ext); mp->cur_ext = xstrdup (s); pack_cur_name; } @ 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. @<Internal library ...@>= void mp_prompt_file_name (MP mp, const char *s, const char *e); @ @c void mp_prompt_file_name (MP mp, const char *s, const char *e) { size_t k; /* index into |buffer| */ char *saved_cur_name; if (mp->interaction == mp_scroll_mode) wake_up_terminal; if (strcmp (s, "input file name") == 0) { print_err ("I can\'t open file `"); @.I can't find file x@> } else { print_err ("I can\'t write on file `"); @.I can't write on file x@> } if (strcmp (s, "file name for output") == 0) { mp_print (mp, mp->name_of_file); } else { mp_print_file_name (mp, mp->cur_name, mp->cur_area, mp->cur_ext); } mp_print (mp, "'."); if (strcmp (e, "") == 0) mp_show_context (mp); mp_print_nl (mp, "Please type another "); mp_print (mp, s); @.Please type...@>; if (mp->noninteractive || mp->interaction < mp_scroll_mode) mp_fatal_error (mp, "*** (job aborted, file error in nonstop mode)"); @.job aborted, file error...@>; saved_cur_name = xstrdup (mp->cur_name); clear_terminal; prompt_input (": "); @<Scan file name in the buffer@>; if (strcmp (mp->cur_ext, "") == 0) mp->cur_ext = xstrdup (e); if (strlen (mp->cur_name) == 0) { mp->cur_name = saved_cur_name; } else { xfree (saved_cur_name); } pack_cur_name; } @ @<Scan file name in the buffer@>= { mp_begin_name (mp); k = mp->first; while ((mp->buffer[k] == ' ') && (k < mp->last)) incr (k); while (1) { if (k == mp->last) break; if (!mp_more_name (mp, mp->buffer[k])) break; incr (k); } mp_end_name (mp); } @ 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. @c void mp_open_log_file (MP mp) { unsigned old_setting; /* previous |selector| setting */ int k; /* index into |months| and |buffer| */ int l; /* end of first input line */ integer m; /* the current month */ const char *months = "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC"; /* abbreviations of month names */ if (mp->log_opened) return; old_setting = mp->selector; if (mp->job_name == NULL) { mp->job_name = xstrdup ("mpout"); @<Fix up |mp->internal[mp_job_name]|@>; } mp_pack_job_name (mp, ".log"); while (!mp_a_open_out (mp, &mp->log_file, mp_filetype_log)) { @<Try to get a different log file name@>; } mp->log_name = xstrdup (mp->name_of_file); mp->selector = log_only; mp->log_opened = true; @<Print the banner line, including the date and time@>; mp->input_stack[mp->input_ptr] = mp->cur_input; /* make sure bottom level is in memory */ if (!mp->noninteractive) { mp_print_nl (mp, "**"); @.**@>; l = mp->input_stack[0].limit_field - 1; /* last position of first line */ for (k = 0; k <= l; k++) mp_print_char (mp, mp->buffer[k]); mp_print_ln (mp); /* now the transcript file contains the first line of input */ } mp->selector = old_setting + 2; /* |log_only| or |term_and_log| */ } @ @<Dealloc variables@>= xfree (mp->log_name); @ 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 |mp_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@>= { mp->selector = term_only; mp_prompt_file_name (mp, "transcript file name", ".log"); } @ @<Print the banner...@>= { wlog (mp->banner); mp_print (mp, mp->mem_ident); mp_print (mp, " "); mp_print_int (mp, mp_round_unscaled (mp, internal_value (mp_day))); mp_print_char (mp, xord (' ')); m = mp_round_unscaled (mp, internal_value (mp_month)); for (k = 3 * m - 3; k < 3 * m; k++) { wlog_chr ((unsigned char) months[k]); } mp_print_char (mp, xord (' ')); mp_print_int (mp, mp_round_unscaled (mp, internal_value (mp_year))); mp_print_char (mp, xord (' ')); mp_print_dd (mp, mp_round_unscaled (mp, internal_value (mp_hour))); mp_print_char (mp, xord (':')); mp_print_dd (mp, mp_round_unscaled (mp, internal_value (mp_minute))); } @ 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. @c static boolean mp_try_extension (MP mp, const char *ext) { mp_pack_file_name (mp, mp->cur_name, mp->cur_area, ext); in_name = xstrdup (mp->cur_name); in_area = xstrdup (mp->cur_area); in_ext = xstrdup (ext); if (mp_a_open_in (mp, &cur_file, mp_filetype_program)) { return true; } else { mp_pack_file_name (mp, mp->cur_name, NULL, ext); return mp_a_open_in (mp, &cur_file, mp_filetype_program); } } @ Let's turn now to the procedure that is used to initiate file reading when an `\.{input}' command is being processed. @c void mp_start_input (MP mp) { /* \MP\ will \.{input} something */ char *fname = NULL; @<Put the desired file name in |(cur_name,cur_ext,cur_area)|@>; while (1) { mp_begin_file_reading (mp); /* set up |cur_file| and new level of input */ if (strlen (mp->cur_ext) == 0) { if (mp_try_extension (mp, ".mp")) break; else if (mp_try_extension (mp, "")) break; else if (mp_try_extension (mp, ".mf")) break; } else if (mp_try_extension (mp, mp->cur_ext)) { break; } mp_end_file_reading (mp); /* remove the level that didn't work */ mp_prompt_file_name (mp, "input file name", ""); } name = mp_a_make_name_string (mp, cur_file); fname = xstrdup (mp->name_of_file); if (mp->job_name == NULL) { mp->job_name = xstrdup (mp->cur_name); @<Fix up |mp->internal[mp_job_name]|@>; } if (!mp->log_opened) { mp_open_log_file (mp); } /* |open_log_file| doesn't |show_context|, so |limit| and |loc| needn't be set to meaningful values yet */ if (((int) mp->term_offset + (int) strlen (fname)) > (mp->max_print_line - 2)) mp_print_ln (mp); else if ((mp->term_offset > 0) || (mp->file_offset > 0)) mp_print_char (mp, xord (' ')); mp_print_char (mp, xord ('(')); incr (mp->open_parens); mp_print (mp, fname); xfree (fname); update_terminal; @<Flush |name| and replace it with |cur_name| if it won't be needed@>; @<Read the first line of the new file@>; } @ 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@>= mp_flush_string (mp, name); name = mp_rts (mp, mp->cur_name); xfree (mp->cur_name) @ If the file is empty, it is considered to contain a single blank line, so there is no need to test the return value. @<Read the first line...@>= { line = 1; (void) mp_input_ln (mp, cur_file); mp_firm_up_the_line (mp); mp->buffer[limit] = xord ('%'); mp->first = (size_t) (limit + 1); loc = start; } @ @<Put the desired file name in |(cur_name,cur_ext,cur_area)|@>= while (token_state && (nloc == NULL)) mp_end_token_list (mp); if (token_state) { 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."); mp_error (mp); } if (file_state) { mp_scan_file_name (mp); } else { xfree (mp->cur_name); mp->cur_name = xstrdup (""); xfree (mp->cur_ext); mp->cur_ext = xstrdup (""); xfree (mp->cur_area); mp->cur_area = xstrdup (""); } @ The following simple routine starts reading the \.{MPX} file associated with the current input file. @c void mp_start_mpx_input (MP mp) { char *origname = NULL; /* a copy of nameoffile */ mp_pack_file_name (mp, in_name, "", in_ext); origname = xstrdup (mp->name_of_file); mp_pack_file_name (mp, in_name, "", ".mpx"); if (!(mp->run_make_mpx) (mp, origname, mp->name_of_file)) goto NOT_FOUND; mp_begin_file_reading (mp); if (!mp_a_open_in (mp, &cur_file, mp_filetype_program)) { mp_end_file_reading (mp); goto NOT_FOUND; } name = mp_a_make_name_string (mp, cur_file); mp->mpx_name[iindex] = name; add_str_ref (name); @<Read the first line of the new file@>; xfree (origname); return; NOT_FOUND: @<Explain that the \.{MPX} file can't be read and |succumb|@>; xfree (origname); } @ 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 |origname| 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@> @ @<Exported types@>= typedef int (*mp_makempx_cmd) (MP mp, char *origname, char *mtxname); @ @<Option variables@>= mp_makempx_cmd run_make_mpx; @ @<Allocate or initialize ...@>= set_callback_option (run_make_mpx); @ @<Declarations@>= static int mp_run_make_mpx (MP mp, char *origname, char *mtxname); @ The default does nothing. @c int mp_run_make_mpx (MP mp, char *origname, char *mtxname) { (void) mp; (void) origname; (void) mtxname; return false; } @ @<Explain that the \.{MPX} file can't be read and |succumb|@>= if (mp->interaction == mp_error_stop_mode) wake_up_terminal; mp_print_nl (mp, ">> "); mp_print (mp, origname); mp_print_nl (mp, ">> "); mp_print (mp, mp->name_of_file); mp_print_nl (mp, "! 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"); xfree (origname); 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@>= typedef unsigned int readf_index; /* |0..max_read_files| */ typedef unsigned int write_index; /* |0..max_write_files| */ @ @<Glob...@>= readf_index max_read_files; /* maximum number of simultaneously open \&{readfrom} files */ void **rd_file; /* \&{readfrom} files */ char **rd_fname; /* corresponding file name or 0 if file not open */ readf_index read_files; /* number of valid entries in the above arrays */ write_index max_write_files; /* maximum number of simultaneously open \&{write} */ void **wr_file; /* \&{write} files */ char **wr_fname; /* corresponding file name or 0 if file not open */ write_index write_files; /* number of valid entries in the above arrays */ @ @<Allocate or initialize ...@>= mp->max_read_files = 8; mp->rd_file = xmalloc ((mp->max_read_files + 1), sizeof (void *)); mp->rd_fname = xmalloc ((mp->max_read_files + 1), sizeof (char *)); memset (mp->rd_fname, 0, sizeof (char *) * (mp->max_read_files + 1)); mp->max_write_files = 8; mp->wr_file = xmalloc ((mp->max_write_files + 1), sizeof (void *)); mp->wr_fname = xmalloc ((mp->max_write_files + 1), sizeof (char *)); memset (mp->wr_fname, 0, sizeof (char *) * (mp->max_write_files + 1)); @ 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]|. @c static boolean mp_start_read_input (MP mp, char *s, readf_index n) { mp_ptr_scan_file (mp, s); pack_cur_name; mp_begin_file_reading (mp); if (!mp_a_open_in (mp, &mp->rd_file[n], (int) (mp_filetype_text + n))) goto NOT_FOUND; if (!mp_input_ln (mp, mp->rd_file[n])) { (mp->close_file) (mp, mp->rd_file[n]); goto NOT_FOUND; } mp->rd_fname[n] = xstrdup (s); return true; NOT_FOUND: mp_end_file_reading (mp); return false; } @ Open |wr_file[n]| using file name~|s| and update |wr_fname[n]|. @<Declarations@>= static void mp_open_write_file (MP mp, char *s, readf_index n); @ @c void mp_open_write_file (MP mp, char *s, readf_index n) { mp_ptr_scan_file (mp, s); pack_cur_name; while (!mp_a_open_out (mp, &mp->wr_file[n], (int) (mp_filetype_text + n))) mp_prompt_file_name (mp, "file name for write output", ""); mp->wr_fname[n] = xstrdup (s); } @* 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. @d cur_exp_value() mp->cur_exp.data.val @d cur_exp_node() mp->cur_exp.data.node @d cur_exp_str() mp->cur_exp.data.str @d cur_exp_knot() mp->cur_exp.data.p @d set_cur_exp_value(A) do { if (cur_exp_str()) { delete_str_ref(cur_exp_str()); } cur_exp_value() = (A); cur_exp_node() = NULL; cur_exp_str() = NULL; cur_exp_knot() = NULL; } while (0) @d set_cur_exp_node(A) do { if (cur_exp_str()) { delete_str_ref(cur_exp_str()); } cur_exp_node() = A; cur_exp_str() = NULL; cur_exp_knot() = NULL; cur_exp_value() = 0; } while (0) @d set_cur_exp_str(A) do { if (cur_exp_str()) { delete_str_ref(cur_exp_str()); } cur_exp_str() = A; cur_exp_node() = NULL; cur_exp_knot() = NULL; cur_exp_value() = 0; } while (0) @d set_cur_exp_knot(A) do { if (cur_exp_str()) { delete_str_ref(cur_exp_str()); } cur_exp_knot() = A; cur_exp_node() = NULL; cur_exp_str() = NULL; cur_exp_value() = 0; } while (0) @ @<Glob...@>= mp_value cur_exp; /* the value of the expression just found */ @ @<Set init...@>= memset (&mp->cur_exp.data, 0, sizeof (mp_value)); @ 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=mp_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=mp_boolean_type| means that |cur_exp| is either |true_code| or |false_code|. \smallskip\hang |cur_type=mp_unknown_boolean| means that |cur_exp| points to a capsule node that is in a ring of equivalent booleans whose value has not yet been defined. \smallskip\hang |cur_type=mp_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=mp_unknown_string| means that |cur_exp| points to a capsule node that is in a ring of equivalent strings whose value has not yet been defined. \smallskip\hang |cur_type=mp_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=mp_unknown_pen| means that |cur_exp| points to a capsule node that is in a ring of equivalent pens whose value has not yet been defined. \smallskip\hang |cur_type=mp_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=mp_unknown_path| means that |cur_exp| points to a capsule node that is in a ring of equivalent paths whose value has not yet been defined. \smallskip\hang |cur_type=mp_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=mp_unknown_picture| means that |cur_exp| points to a capsule node that is in a ring of equivalent pictures whose value has not yet been defined. \smallskip\hang |cur_type=mp_transform_type| means that |cur_exp| points to a |mp_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|, |mp_proto_dependent|, or |known|. \smallskip\hang |cur_type=mp_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|, |mp_proto_dependent|, or |known|. \smallskip\hang |cur_type=mp_cmykcolor_type| means that |cur_exp| points to a |mp_cmykcolor_type| capsule node. The |value| part of this capsule points to a color node that contains four numeric values, each of which is |independent|, |dependent|, |mp_proto_dependent|, or |known|. \smallskip\hang |cur_type=mp_pair_type| means that |cur_exp| points to a capsule node whose type is |mp_pair_type|. The |value| part of this capsule points to a pair node that contains two numeric values, each of which is |independent|, |dependent|, |mp_proto_dependent|, or |known|. \smallskip\hang |cur_type=mp_known| means that |cur_exp| is a |scaled| value. \smallskip\hang |cur_type=mp_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=mp_proto_dependent| means that |cur_exp| points to a |mp_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=mp_token_list| means that |cur_exp| points to a linked list of tokens. \smallskip\noindent The possible settings of |cur_type| have been listed here in increasing numerical order. Notice that |cur_type| will never be |mp_numeric_type| or |suffixed_macro| or |mp_unsuffixed_macro|, although variables of those types are allowed. Conversely, \MP\ has no variables of type |mp_vacuous| or |token_list|. @ Capsules are non-symbolic nodes that have a similar meaning to |cur_type| and |cur_exp|. Such nodes have |name_type=capsule|, and their |type| field is one of the possibilities for |cur_type| listed above. Also |link<=void| in capsules that aren't part of a token list. 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 |mp_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, |mp_unknown_boolean| to |mp_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=mp_token_list|. After the operation, |cur_type=mp_vacuous|; hence there is no need to copy path lists or to update reference counts, etc. The special link |MP_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@>= static mp_node mp_stash_cur_exp (MP mp) { mp_node p; /* the capsule that will be returned */ mp_variable_type exp_type = mp->cur_exp.type; switch (exp_type) { case unknown_types: case mp_transform_type: case mp_color_type: case mp_pair_type: case mp_dependent: case mp_proto_dependent: case mp_independent: case mp_cmykcolor_type: p = cur_exp_node (); break; /* |case mp_path_type: case mp_pen_type: case mp_string_type:| */ default: p = mp_get_value_node (mp); mp_name_type (p) = mp_capsule; mp_type (p) = mp->cur_exp.type; set_value (p, cur_exp_value ()); /* this also resets the rest to 0/NULL */ str_value (p) = cur_exp_str (); knot_value (p) = cur_exp_knot (); value_node (p) = cur_exp_node (); break; } mp->cur_exp.type = mp_vacuous; mp_link (p) = MP_VOID; return p; } @ 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=mp_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...@>= static void mp_unstash_cur_exp (MP mp, mp_node p); @ @c void mp_unstash_cur_exp (MP mp, mp_node p) { mp->cur_exp.type = mp_type (p); switch (mp->cur_exp.type) { case unknown_types: case mp_transform_type: case mp_color_type: case mp_pair_type: case mp_dependent: case mp_proto_dependent: case mp_independent: case mp_cmykcolor_type: set_cur_exp_node (p); break; case mp_path_type: case mp_pen_type: set_cur_exp_knot (knot_value (p)); mp_free_node (mp, p, value_node_size); break; case mp_string_type: set_cur_exp_str (str_value (p)); mp_free_node (mp, p, value_node_size); break; case mp_picture_type: set_cur_exp_node (value_node (p)); mp_free_node (mp, p, value_node_size); break; default: set_cur_exp_value (value (p)); mp_free_node (mp, p, value_node_size); break; } } @ 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. @.linearform@> @<Declarations@>= @<Declare the procedure called |print_dp|@>; @<Declare the stashing/unstashing routines@>; static void mp_print_exp (MP mp, mp_node p, quarterword verbosity); @ @c void mp_print_exp (MP mp, mp_node p, quarterword verbosity) { boolean restore_cur_exp; /* should |cur_exp| be restored? */ mp_variable_type t; /* the type of the expression */ integer vv = 0; /* the value of the expression */ mp_node v = NULL; if (p != NULL) { restore_cur_exp = false; } else { p = mp_stash_cur_exp (mp); restore_cur_exp = true; } t = mp_type (p); if (t < mp_dependent) { /* no dep list, could be a capsule */ if (t != mp_vacuous && t != mp_known && value_node (p) != NULL) v = value_node (p); else vv = value (p); } else if (t < mp_independent) { v = (mp_node) dep_list ((mp_value_node) p); } @<Print an abbreviated value of |v| or |vv| with format depending on |t|@>; if (restore_cur_exp) mp_unstash_cur_exp (mp, p); } @ @<Print an abbreviated value of |v| or |vv| with format depending on |t|@>= switch (t) { case mp_vacuous: mp_print (mp, "vacuous"); break; case mp_boolean_type: if (vv == true_code) mp_print (mp, "true"); else mp_print (mp, "false"); break; case unknown_types: case mp_numeric_type: @<Display a variable that's been declared but not defined@>; break; case mp_string_type: mp_print_char (mp, xord ('"')); mp_print_str (mp, str_value (p)); mp_print_char (mp, xord ('"')); break; case mp_pen_type: case mp_path_type: case mp_picture_type: @<Display a complex type@>; break; case mp_transform_type: if (vv == 0 && v == NULL) mp_print_type (mp, t); else @<Display a transform node@>; break; case mp_color_type: if (vv == 0 && v == NULL) mp_print_type (mp, t); else @<Display a color node@>; break; case mp_pair_type: if (vv == 0 && v == NULL) mp_print_type (mp, t); else @<Display a pair node@>; break; case mp_cmykcolor_type: if (vv == 0 && v == NULL) mp_print_type (mp, t); else @<Display a cmykcolor node@>; break; case mp_known: mp_print_scaled (mp, vv); break; case mp_dependent: case mp_proto_dependent: mp_print_dp (mp, t, (mp_value_node) v, verbosity); break; case mp_independent: mp_print_variable_name (mp, p); break; default: mp_confusion (mp, "exp"); break; @:this can't happen exp}{\quad exp@> } @ @<Display big node item |v|@>= { if (mp_type (v) == mp_known) mp_print_scaled (mp, value (v)); else if (mp_type (v) == mp_independent) mp_print_variable_name (mp, v); else mp_print_dp (mp, mp_type (v), (mp_value_node) dep_list ((mp_value_node) v), verbosity); } @ In these cases, |v| starts as the big node. @<Display a pair node@>= { mp_node vvv = v; mp_print_char (mp, xord ('(')); v = x_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = y_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (')')); } @ @<Display a transform node@>= { mp_node vvv = v; mp_print_char (mp, xord ('(')); v = tx_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = ty_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = xx_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = xy_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = yx_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = yy_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (')')); } @ @<Display a color node@>= { mp_node vvv = v; mp_print_char (mp, xord ('(')); v = red_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = green_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = blue_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (')')); } @ @<Display a cmykcolor node@>= { mp_node vvv = v; mp_print_char (mp, xord ('(')); v = cyan_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = magenta_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = yellow_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (',')); v = black_part_loc (vvv); @<Display big node item |v|@>; mp_print_char (mp, xord (')')); } @ 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) { mp_print_type (mp, t); } else { if (mp->selector == term_and_log) if (internal_value (mp_tracing_online) <= 0) { mp->selector = term_only; mp_print_type (mp, t); mp_print (mp, " (see the transcript file)"); mp->selector = term_and_log; }; switch (t) { case mp_pen_type: mp_print_pen (mp, knot_value (p), "", false); break; case mp_path_type: mp_print_path (mp, knot_value (p), "", false); break; case mp_picture_type: mp_print_edges (mp, v, "", false); break; default: break; } } @ @<Declare the procedure called |print_dp|@>= static void mp_print_dp (MP mp, quarterword t, mp_value_node p, quarterword verbosity) { mp_value_node q; /* the node following |p| */ q = (mp_value_node) mp_link (p); if ((dep_info (q) == NULL) || (verbosity > 0)) mp_print_dependency (mp, p, t); else mp_print (mp, "linearform"); } @ 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@>= { mp_print_type (mp, t); if (v != NULL) { mp_print_char (mp, xord (' ')); while ((mp_name_type (v) == mp_capsule) && (v != p)) v = value_node (v); mp_print_variable_name (mp, v); }; } @ 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(A) mp_disp_err(mp, NULL,(A)) /* displays the current expression */ @<Declarations@>= static void mp_disp_err (MP mp, mp_node p, const char *s); @ @c void mp_disp_err (MP mp, mp_node p, const char *s) { if (mp->interaction == mp_error_stop_mode) wake_up_terminal; mp_print_nl (mp, ">> "); @.>>@>; mp_print_exp (mp, p, 1); /* ``medium verbose'' printing of the expression */ if (strlen (s) > 0) { print_err (s); } } @ 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. @ @c static void mp_flush_cur_exp (MP mp, mp_value v) { switch (mp->cur_exp.type) { case unknown_types: case mp_transform_type: case mp_color_type: case mp_pair_type: case mp_dependent: case mp_proto_dependent: case mp_independent: case mp_cmykcolor_type: mp_recycle_value (mp, cur_exp_node ()); mp_free_node (mp, cur_exp_node (), value_node_size); break; case mp_string_type: delete_str_ref (cur_exp_str ()); break; case mp_pen_type: case mp_path_type: mp_toss_knot_list (mp, cur_exp_knot ()); break; case mp_picture_type: delete_edge_ref (cur_exp_node ()); break; default: break; } mp->cur_exp = v; mp->cur_exp.type = mp_known; } @ There's a much more general procedure that is capable of releasing the storage associated with any non-symbolic value packet. @<Declarations@>= static void mp_recycle_value (MP mp, mp_node p); @ @c static void mp_recycle_value (MP mp, mp_node p) { mp_variable_type t; /* a type code */ integer vv; /* another value */ mp_node pp; /* link manipulation register */ integer v = 0; /* a value */ FUNCTION_TRACE2 ("mp_recycle_value(%p)\n", p); t = mp_type (p); if (t < mp_dependent) v = value (p); switch (t) { case mp_vacuous: case mp_boolean_type: case mp_known: case mp_numeric_type: break; case unknown_types: mp_ring_delete (mp, p); break; case mp_string_type: delete_str_ref (str_value (p)); break; case mp_path_type: case mp_pen_type: mp_toss_knot_list (mp, knot_value (p)); break; case mp_picture_type: delete_edge_ref (value_node (p)); break; case mp_cmykcolor_type: if (value_node (p) != NULL) { mp_recycle_value (mp, cyan_part_loc (value_node (p))); mp_recycle_value (mp, magenta_part_loc (value_node (p))); mp_recycle_value (mp, yellow_part_loc (value_node (p))); mp_recycle_value (mp, black_part_loc (value_node (p))); mp_free_node (mp, cyan_part_loc (value_node (p)), value_node_size); mp_free_node (mp, magenta_part_loc (value_node (p)), value_node_size); mp_free_node (mp, black_part_loc (value_node (p)), value_node_size); mp_free_node (mp, yellow_part_loc (value_node (p)), value_node_size); mp_free_node (mp, value_node (p), cmykcolor_node_size); } break; case mp_pair_type: if (value_node (p) != NULL) { mp_recycle_value (mp, x_part_loc (value_node (p))); mp_recycle_value (mp, y_part_loc (value_node (p))); mp_free_node (mp, x_part_loc (value_node (p)), value_node_size); mp_free_node (mp, y_part_loc (value_node (p)), value_node_size); mp_free_node (mp, value_node (p), pair_node_size); } break; case mp_color_type: if (value_node (p) != NULL) { mp_recycle_value (mp, red_part_loc (value_node (p))); mp_recycle_value (mp, green_part_loc (value_node (p))); mp_recycle_value (mp, blue_part_loc (value_node (p))); mp_free_node (mp, red_part_loc (value_node (p)), value_node_size); mp_free_node (mp, green_part_loc (value_node (p)), value_node_size); mp_free_node (mp, blue_part_loc (value_node (p)), value_node_size); mp_free_node (mp, value_node (p), color_node_size); } break; case mp_transform_type: if (value_node (p) != NULL) { mp_recycle_value (mp, tx_part_loc (value_node (p))); mp_recycle_value (mp, ty_part_loc (value_node (p))); mp_recycle_value (mp, xx_part_loc (value_node (p))); mp_recycle_value (mp, xy_part_loc (value_node (p))); mp_recycle_value (mp, yx_part_loc (value_node (p))); mp_recycle_value (mp, yy_part_loc (value_node (p))); mp_free_node (mp, tx_part_loc (value_node (p)), value_node_size); mp_free_node (mp, ty_part_loc (value_node (p)), value_node_size); mp_free_node (mp, xx_part_loc (value_node (p)), value_node_size); mp_free_node (mp, xy_part_loc (value_node (p)), value_node_size); mp_free_node (mp, yx_part_loc (value_node (p)), value_node_size); mp_free_node (mp, yy_part_loc (value_node (p)), value_node_size); mp_free_node (mp, value_node (p), transform_node_size); } break; case mp_dependent: case mp_proto_dependent: @<Recycle a dependency list@>; break; case mp_independent: @<Recycle an independent variable@>; break; case mp_token_list: case mp_structured: mp_confusion (mp, "recycle"); break; @:this can't happen recycle}{\quad recycle@> case mp_unsuffixed_macro: case mp_suffixed_macro: mp_delete_mac_ref (mp, value_node (p)); break; default: /* there are no other valid cases, but please the compiler */ break; } /* there are no other cases */ mp_type (p) = undefined; } @ @<Recycle a dependency list@>= { mp_value_node qq = (mp_value_node) dep_list ((mp_value_node) p); while (dep_info (qq) != NULL) qq = (mp_value_node) mp_link (qq); set_mp_link (prev_dep ((mp_value_node) p), mp_link (qq)); set_prev_dep (mp_link (qq), prev_dep ((mp_value_node) p)); set_mp_link (qq, NULL); mp_flush_node_list (mp, (mp_node) dep_list ((mp_value_node) p)); } @ 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 Andr\'es@> 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@>= { mp_value_node q, r, s; mp->max_c[mp_dependent] = 0; mp->max_c[mp_proto_dependent] = 0; mp->max_link[mp_dependent] = NULL; mp->max_link[mp_proto_dependent] = NULL; q = (mp_value_node) mp_link (mp->dep_head); while (q != mp->dep_head) { s = (mp_value_node) mp->temp_head; set_mp_link (s, dep_list (q)); while (1) { r = (mp_value_node) mp_link (s); if (dep_info (r) == NULL) break; if (dep_info (r) != p) { s = r; } else { t = mp_type (q); if (mp_link (s) == dep_list (q)) { /* reset the |dep_list| */ set_dep_list (q, mp_link (r)); } set_mp_link (s, mp_link (r)); set_dep_info (r, (mp_node) q); if (abs (dep_value (r)) > mp->max_c[t]) { /* Record a new maximum coefficient of type |t| */ if (mp->max_c[t] > 0) { set_mp_link (mp->max_ptr[t], (mp_node) mp->max_link[t]); mp->max_link[t] = mp->max_ptr[t]; } mp->max_c[t] = abs (dep_value (r)); mp->max_ptr[t] = r; } else { set_mp_link (r, (mp_node) mp->max_link[t]); mp->max_link[t] = r; } } } q = (mp_value_node) mp_link (r); } if ((mp->max_c[mp_dependent] > 0) || (mp->max_c[mp_proto_dependent] > 0)) { @<Choose a dependent variable to take the place of the disappearing independent variable, and change all remaining dependencies accordingly@>; } } @ The code for independency removal makes use of three non-symbolic arrays. @<Glob...@>= integer max_c[mp_proto_dependent + 1]; /* max coefficient magnitude */ mp_value_node max_ptr[mp_proto_dependent + 1]; /* where |p| occurs with |max_c| */ mp_value_node max_link[mp_proto_dependent + 1]; /* other occurrences of |p| */ @ @<Choose a dependent...@>= { if ((mp->max_c[mp_dependent] / 010000) >= mp->max_c[mp_proto_dependent]) t = mp_dependent; else t = mp_proto_dependent; @<Determine the dependency list |s| to substitute for the independent variable~|p|@>; t = (quarterword) (mp_dependent + mp_proto_dependent - t); /* complement |t| */ if (mp->max_c[t] > 0) { /* we need to pick up an unchosen dependency */ set_mp_link (mp->max_ptr[t], (mp_node) mp->max_link[t]); mp->max_link[t] = mp->max_ptr[t]; } if (t != mp_dependent) { @<Substitute new dependencies in place of |p|@>; } else { @<Substitute new proto-dependencies in place of |p|@>; } mp_flush_node_list (mp, (mp_node) s); if (mp->fix_needed) mp_fix_dependencies (mp); check_arith; } @ Let |s=max_ptr[t]|. At this point we have $|value|(s)=\pm|max_c|[t]$, and |dep_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 = mp->max_ptr[t]; pp = (mp_node) dep_info (s); /* |debug_printf ("s=%p, pp=%p, r=%p\n",s, pp, dep_list((mp_value_node)pp));| */ v = dep_value (s); if (t == mp_dependent) set_dep_value (s, -fraction_one); else set_dep_value (s, -unity); r = (mp_value_node) dep_list ((mp_value_node) pp); set_mp_link (s, (mp_node) r); while (dep_info (r) != NULL) r = (mp_value_node) mp_link (r); q = (mp_value_node) mp_link (r); set_mp_link (r, NULL); set_prev_dep (q, prev_dep ((mp_value_node) pp)); set_mp_link (prev_dep ((mp_value_node) pp), (mp_node) q); new_indep ((mp_value_node) pp); if (cur_exp_node () == pp && mp->cur_exp.type == t) mp->cur_exp.type = mp_independent; if (internal_value (mp_tracing_equations) > 0) { @<Show the transformed dependency@>; } @ Now $(-v)$ times the formerly independent variable~|p| is being replaced by the dependency list~|s|. @<Show the transformed...@>= if (mp_interesting (mp, p)) { mp_begin_diagnostic (mp); mp_print_nl (mp, "### "); @:]]]\#\#\#_}{\.{\#\#\#}@>; if (v > 0) mp_print_char (mp, xord ('-')); if (t == mp_dependent) vv = mp_round_fraction (mp, mp->max_c[mp_dependent]); else vv = mp->max_c[mp_proto_dependent]; if (vv != unity) mp_print_scaled (mp, vv); mp_print_variable_name (mp, p); while (indep_scale (p) > 0) { mp_print (mp, "*4"); set_indep_scale(p, indep_scale(p)-2); } if (t == mp_dependent) mp_print_char (mp, xord ('=')); else mp_print (mp, " = "); mp_print_dependency (mp, s, t); mp_end_diagnostic (mp, false); } @ Finally, there are dependent and proto-dependent variables whose dependency lists must be brought up to date. @<Substitute new dependencies...@>= for (t = mp_dependent; t <= mp_proto_dependent; t++) { r = mp->max_link[t]; while (r != NULL) { q = (mp_value_node) dep_info (r); set_dep_list (q, (mp_node) mp_p_plus_fq (mp, (mp_value_node) dep_list (q), mp_make_fraction (mp, dep_value (r), -v), s, t, mp_dependent)); if (dep_list (q) == (mp_node) mp->dep_final) mp_make_known (mp, q, mp->dep_final); q = r; r = (mp_value_node) mp_link (r); mp_free_dep_node (mp, q); } } @ @<Substitute new proto...@>= for (t = mp_dependent; t <= mp_proto_dependent; t++) { r = mp->max_link[t]; while (r != NULL) { q = (mp_value_node) dep_info (r); if (t == mp_dependent) { /* for safety's sake, we change |q| to |mp_proto_dependent| */ if (cur_exp_node () == (mp_node) q && mp->cur_exp.type == mp_dependent) mp->cur_exp.type = mp_proto_dependent; set_dep_list (q, (mp_node) mp_p_over_v (mp, (mp_value_node) dep_list (q), unity, mp_dependent, mp_proto_dependent)); mp_type (q) = mp_proto_dependent; set_dep_value (r, mp_round_fraction (mp, dep_value (r))); } set_dep_list (q, (mp_node) mp_p_plus_fq (mp, (mp_value_node) dep_list (q), mp_make_scaled (mp, dep_value (r), -v), s, mp_proto_dependent, mp_proto_dependent)); if (dep_list (q) == (mp_node) mp->dep_final) mp_make_known (mp, q, mp->dep_final); q = r; r = (mp_value_node) mp_link (r); mp_free_dep_node (mp, q); } } @ 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.) @<Declarations@>= static void mp_flush_error (MP mp, mp_value v); static void mp_put_get_error (MP mp); static void mp_put_get_flush_error (MP mp, mp_value v); @ @c void mp_flush_error (MP mp, mp_value v) { mp_error (mp); mp_flush_cur_exp (mp, v); } void mp_put_get_error (MP mp) { mp_back_error (mp); mp_get_x_next (mp); } void mp_put_get_flush_error (MP mp, mp_value v) { mp_put_get_error (mp); mp_flush_cur_exp (mp, v); } @ 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...@>= int var_flag; /* command that wants a variable */ @ @<Set init...@>= mp->var_flag = 0; @* 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@>= void mp_scan_primary (MP mp) { mp_node p, q, r; /* for list manipulation */ quarterword c; /* a primitive operation code */ int my_var_flag; /* initial value of |my_var_flag| */ mp_sym l_delim, r_delim; /* hash addresses of a delimiter pair */ mp_value new_expr; @<Other local variables for |scan_primary|@>; memset(&new_expr,0,sizeof(mp_value)); my_var_flag = mp->var_flag; mp->var_flag = 0; RESTART: check_arith; @<Supply diagnostic information, if requested@>; switch (mp->cur_cmd) { case left_delimiter: @<Scan a delimited primary@>; break; case begin_group: @<Scan a grouped primary@>; break; case string_token: @<Scan a string constant@>; break; case numeric_token: @<Scan a primary that starts with a numeric token@>; break; case nullary: @<Scan a nullary operation@>; break; case unary: case type_name: case cycle: case plus_or_minus: @<Scan a unary operation@>; break; case primary_binary: @<Scan a binary operation with `\&{of}' between its operands@>; break; case str_op: @<Convert a suffix to a string@>; break; case internal_quantity: @<Scan an internal numeric quantity@>; break; case capsule_token: mp_make_exp_copy (mp, mp->cur_mod_node); break; case tag_token: @<Scan a variable primary; |goto restart| if it turns out to be a macro@>; break; default: mp_bad_exp (mp, "A primary"); goto RESTART; break; @.A primary expression...@> } mp_get_x_next (mp); /* the routines |goto done| if they don't want this */ DONE: if (mp->cur_cmd == left_bracket) { if (mp->cur_exp.type >= mp_known) { @<Scan a mediation construction@>; } } } @ Errors at the beginning of expressions are flagged by |bad_exp|. @c static void mp_bad_exp (MP mp, const char *s) { int save_flag; print_err (s); mp_print (mp, " expression can't begin with `"); mp_print_cmd_mod (mp, mp->cur_cmd, mp->cur_mod); mp_print_char (mp, xord ('\'')); 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@>; mp_back_input (mp); mp->cur_sym = 0; mp->cur_cmd = numeric_token; mp->cur_mod = 0; mp_ins_error (mp); save_flag = mp->var_flag; mp->var_flag = 0; mp_get_x_next (mp); mp->var_flag = save_flag; } @ @<Supply diagnostic information, if requested@>= if (mp->interrupt != 0) if (mp->OK_to_interrupt) { mp_back_input (mp); check_interrupt; mp_get_x_next (mp); } @ @<Scan a delimited primary@>= { l_delim = mp->cur_sym; r_delim = mp->cur_sym2; mp_get_x_next (mp); mp_scan_expression (mp); if ((mp->cur_cmd == comma) && (mp->cur_exp.type >= mp_known)) { @<Scan the rest of a delimited set of numerics@>; } else { mp_check_delimiter (mp, l_delim, r_delim); } } @ The |stash_in| subroutine puts the current (numeric) expression into a field within a ``big node.'' @c static void mp_stash_in (MP mp, mp_node p) { mp_value_node q; /* temporary register */ mp_type (p) = mp->cur_exp.type; if (mp->cur_exp.type == mp_known) { set_value (p, cur_exp_value ()); } else { if (mp->cur_exp.type == mp_independent) { @<Stash an independent |cur_exp| into a big node@>; mp_free_node (mp, cur_exp_node (), value_node_size); } else { set_dep_list ((mp_value_node) p, dep_list ((mp_value_node) cur_exp_node ())); set_prev_dep ((mp_value_node) p, prev_dep ((mp_value_node) cur_exp_node ())); set_mp_link (prev_dep ((mp_value_node) p), p); mp_free_dep_node (mp, (mp_value_node) cur_exp_node ()); } } mp->cur_exp.type = mp_vacuous; } @ 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|...@>= { q = mp_single_dependency (mp, cur_exp_node ()); if (q == mp->dep_final) { mp_type (p) = mp_known; set_value (p, 0); mp_free_dep_node (mp, q); } else { mp_new_dep (mp, p, mp_dependent, q); } mp_recycle_value (mp, cur_exp_node ()); } @ 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 delimited set of numerics@>= { p = mp_stash_cur_exp (mp); mp_get_x_next (mp); mp_scan_expression (mp); @<Make sure the second part of a pair or color has a numeric type@>; q = mp_get_value_node (mp); mp_name_type (q) = mp_capsule; if (mp->cur_cmd == comma) { mp_init_color_node (mp, q); r = value_node (q); mp_stash_in (mp, y_part_loc (r)); mp_unstash_cur_exp (mp, p); mp_stash_in (mp, x_part_loc (r)); @<Scan the last of a triplet of numerics@>; if (mp->cur_cmd == comma) { mp_init_cmykcolor_node (mp, q); t = value_node (q); mp_type (cyan_part_loc (t)) = mp_type (red_part_loc (r)); set_value (cyan_part_loc (t), value (red_part_loc (r))); mp_type (magenta_part_loc (t)) = mp_type (green_part_loc (r)); set_value (magenta_part_loc (t), value (green_part_loc (r))); mp_type (yellow_part_loc (t)) = mp_type (blue_part_loc (r)); set_value (yellow_part_loc (t), value (blue_part_loc (r))); mp_recycle_value (mp, r); r = t; @<Scan the last of a quartet of numerics@>; } } else { mp_init_pair_node (mp, q); r = value_node (q); mp_stash_in (mp, y_part_loc (r)); mp_unstash_cur_exp (mp, p); mp_stash_in (mp, x_part_loc (r)); } mp_check_delimiter (mp, l_delim, r_delim); mp->cur_exp.type = mp_type (q); set_cur_exp_node (q); } @ @<Make sure the second part of a pair or color has a numeric type@>= if (mp->cur_exp.type < mp_known) { 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.)"); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } @ @<Scan the last of a triplet of numerics@>= { mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type < mp_known) { exp_err ("Nonnumeric third part has been replaced by 0"); @.Nonnumeric...replaced by 0@>; help3 ("I've just scanned a color `(a,b,c)' or cmykcolor(a,b,c,d); but the `c'", "isn't of numeric type. So I've changed that part to zero.", "(The c that I didn't like appears above the error message.)"); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } mp_stash_in (mp, blue_part_loc (r)); } @ @<Scan the last of a quartet of numerics@>= { mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type < mp_known) { exp_err ("Nonnumeric blackpart has been replaced by 0"); @.Nonnumeric...replaced by 0@>; help3 ("I've just scanned a cmykcolor `(c,m,y,k)'; but the `k' isn't", "of numeric type. So I've changed that part to zero.", "(The k that I didn't like appears above the error message.)"); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } mp_stash_in (mp, black_part_loc (r)); } @ 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|@>= integer group_line; /* where a group began */ @ @<Scan a grouped primary@>= { group_line = mp_true_line (mp); if (internal_value (mp_tracing_commands) > 0) show_cur_cmd_mod; mp_save_boundary (mp); do { mp_do_statement (mp); /* ends with |cur_cmd>=semicolon| */ } while (mp->cur_cmd == semicolon); if (mp->cur_cmd != end_group) { print_err ("A group begun on line "); @.A group...never ended@>; mp_print_int (mp, group_line); mp_print (mp, " never ended"); help2 ("I saw a `begingroup' back there that hasn't been matched", "by `endgroup'. So I've inserted `endgroup' now."); mp_back_error (mp); mp->cur_cmd = end_group; } mp_unsave (mp); /* this might change |cur_type|, if independent variables are recycled */ if (internal_value (mp_tracing_commands) > 0) show_cur_cmd_mod; } @ @<Scan a string constant@>= { mp->cur_exp.type = mp_string_type; set_cur_exp_str (mp->cur_mod_str); } @ 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@>= mp_do_nullary (mp, (quarterword) mp->cur_mod) @ @<Scan a unary operation@>= { c = (quarterword) mp->cur_mod; mp_get_x_next (mp); mp_scan_primary (mp); mp_do_unary (mp, c); goto DONE; } @ 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|@>= scaled num, denom; /* for primaries that are fractions, like `1/2' */ @ @<Scan a primary that starts with a numeric token@>= { set_cur_exp_value (mp->cur_mod); mp->cur_exp.type = mp_known; mp_get_x_next (mp); if (mp->cur_cmd != slash) { num = 0; denom = 0; } else { mp_get_x_next (mp); if (mp->cur_cmd != numeric_token) { mp_back_input (mp); mp->cur_cmd = slash; mp->cur_mod = over; mp->cur_sym = mp->frozen_slash; goto DONE; } num = cur_exp_value (); denom = mp->cur_mod; if (denom == 0) { @<Protest division by zero@>; } else { set_cur_exp_value (mp_make_scaled (mp, num, denom)); } check_arith; mp_get_x_next (mp); } if (mp->cur_cmd >= min_primary_command) { if (mp->cur_cmd < numeric_token) { /* in particular, |cur_cmd<>plus_or_minus| */ p = mp_stash_cur_exp (mp); mp_scan_primary (mp); if ((abs (num) >= abs (denom)) || (mp->cur_exp.type < mp_color_type)) { mp_do_binary (mp, p, times); } else { mp_frac_mult (mp, num, denom); mp_free_node (mp, p, value_node_size); } } } goto DONE; } @ @<Protest division...@>= { print_err ("Division by zero"); @.Division by zero@>; help1 ("I'll pretend that you meant to divide by 1."); mp_error (mp); } @ @<Scan a binary operation with `\&{of}' between its operands@>= { c = (quarterword) mp->cur_mod; mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_cmd != of_token) { mp_missing_err (mp, "of"); mp_print (mp, " for "); mp_print_cmd_mod (mp, primary_binary, c); @.Missing `of'@>; help1 ("I've got the first argument; will look now for the other."); mp_back_error (mp); } p = mp_stash_cur_exp (mp); mp_get_x_next (mp); mp_scan_primary (mp); mp_do_binary (mp, p, c); goto DONE; } @ @<Convert a suffix to a string@>= { mp_get_x_next (mp); mp_scan_suffix (mp); mp->old_setting = mp->selector; mp->selector = new_string; mp_show_token_list (mp, cur_exp_node (), NULL, 100000, 0); mp_flush_token_list (mp, cur_exp_node ()); set_cur_exp_str (mp_make_string (mp)); mp->selector = mp->old_setting; mp->cur_exp.type = mp_string_type; goto DONE; } @ 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, with |name_type| equal to |mp_internal_sym|. (This accords with the conventions of the save stack, as described earlier.) @<Scan an internal...@>= { halfword qq = mp->cur_mod; if (my_var_flag == assignment) { mp_get_x_next (mp); if (mp->cur_cmd == assignment) { set_cur_exp_node (mp_get_symbolic_node (mp)); set_mp_sym_info (cur_exp_node (), qq); mp_name_type (cur_exp_node ()) = mp_internal_sym; mp->cur_exp.type = mp_token_list; goto DONE; } mp_back_input (mp); } if (internal_type (qq) == mp_string_type) { set_cur_exp_str (internal_string (qq)); add_str_ref (cur_exp_str ()); } else { set_cur_exp_value (internal_value (qq)); } mp->cur_exp.type = (mp_variable_type) internal_type (qq); } @ 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>=mp_unsuffixed_macro|, the relation |tt=mp_type(q)| will always hold. If |tt=undefined|, the routine doesn't bother to update its information about type. And if |undefined<tt<mp_unsuffixed_macro|, the precise value of |tt| isn't critical. @ @<Other local variables for |scan_primary|@>= mp_node pre_head, post_head, tail; /* prefix and suffix list variables */ quarterword tt; /* approximation to the type of the variable-so-far */ mp_node t; /* a token */ mp_node macro_ref = 0; /* reference count for a suffixed macro */ @ @<Scan a variable primary...@>= { pre_head = mp_get_symbolic_node (mp); tail = pre_head; post_head = NULL; tt = mp_vacuous; while (1) { t = mp_cur_tok (mp); mp_link (tail) = t; if (tt != undefined) { @<Find the approximate type |tt| and corresponding~|q|@>; if (tt >= mp_unsuffixed_macro) { @<Either begin an unsuffixed macro call or prepare for a suffixed one@>; } } mp_get_x_next (mp); tail = t; if (mp->cur_cmd == left_bracket) { @<Scan for a subscript; replace |cur_cmd| by |numeric_token| if found@>; } if (mp->cur_cmd > max_suffix_token) break; if (mp->cur_cmd < min_suffix_token) break; } /* now |cur_cmd| is |internal_quantity|, |tag_token|, or |numeric_token| */ @<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|@>; } @ @<Either begin an unsuffixed macro call or...@>= { mp_link (tail) = NULL; if (tt > mp_unsuffixed_macro) { /* |tt=mp_suffixed_macro| */ post_head = mp_get_symbolic_node (mp); tail = post_head; mp_link (tail) = t; tt = undefined; macro_ref = value_node (q); add_mac_ref (macro_ref); } else { @<Set up unsuffixed macro call and |goto restart|@>; } } @ @<Scan for a subscript; replace |cur_cmd| by |numeric_token| if found@>= { mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_cmd != right_bracket) { @<Put the left bracket and the expression back to be rescanned@>; } else { if (mp->cur_exp.type != mp_known) mp_bad_subscript (mp); mp->cur_cmd = numeric_token; mp->cur_mod = cur_exp_value (); mp->cur_sym = 0; } } @ 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@>= { mp_back_input (mp); /* that was the token following the current expression */ mp_back_expr (mp); mp->cur_cmd = left_bracket; mp->cur_mod = 0; mp->cur_sym = mp->frozen_left_bracket; } @ Here's a routine that puts the current expression back to be read again. @c static void mp_back_expr (MP mp) { mp_node p; /* capsule token */ p = mp_stash_cur_exp (mp); mp_link (p) = NULL; back_list (p); } @ Unknown subscripts lead to the following error message. @c static void mp_bad_subscript (MP mp) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); 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."); mp_flush_error (mp, new_expr); } @ 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@> { mp_sym qq; p = mp_link (pre_head); qq = mp_sym_sym (p); tt = undefined; if (eq_type (qq) % outer_tag == tag_token) { q = equiv_node (qq); if (q == NULL) goto DONE2; while (1) { p = mp_link (p); if (p == NULL) { tt = mp_type (q); goto DONE2; } if (mp_type (q) != mp_structured) goto DONE2; q = mp_link (attr_head (q)); /* the |collective_subscript| attribute */ if (mp_type (p) == mp_symbol_node) { /* it's not a subscript */ do { q = mp_link (q); } while (!(hashloc (q) >= mp_sym_sym (p))); if (hashloc (q) > mp_sym_sym (p)) goto DONE2; } } } DONE2: ; } @ 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 |mp_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. @c static void mp_obliterated (MP mp, mp_node q) { print_err ("Variable "); mp_show_token_list (mp, q, NULL, 1000, 0); mp_print (mp, " 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."); } @ 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) { @<Set up suffixed macro call and |goto restart|@>; } q = mp_link (pre_head); mp_free_symbolic_node (mp, pre_head); if (mp->cur_cmd == my_var_flag) { mp->cur_exp.type = mp_token_list; set_cur_exp_node (q); goto DONE; } p = mp_find_variable (mp, q); if (p != NULL) { mp_make_exp_copy (mp, p); } else { mp_obliterated (mp, q); mp->help_line[2] = "While I was evaluating the suffix of this variable,"; mp->help_line[1] = "something was redefined, and it's no longer a variable!"; mp->help_line[0] = "In order to get back on my feet, I've inserted `0' instead."; new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } mp_flush_node_list (mp, 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|@>= { p = mp_get_symbolic_node (mp); set_mp_sym_sym (pre_head, mp_link (pre_head)); mp_link (pre_head) = p; set_mp_sym_sym (p, t); mp_macro_call (mp, value_node (q), pre_head, NULL); mp_get_x_next (mp); goto RESTART; } @ 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|@>= { mp_back_input (mp); p = mp_get_symbolic_node (mp); q = mp_link (post_head); set_mp_sym_sym (pre_head, mp_link (pre_head)); mp_link (pre_head) = post_head; set_mp_sym_sym (post_head, q); mp_link (post_head) = p; set_mp_sym_sym (p, mp_link (q)); mp_link (q) = NULL; mp_macro_call (mp, macro_ref, pre_head, NULL); decr_mac_ref (macro_ref); mp_get_x_next (mp); goto RESTART; } @ 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|@>= @<Declare subroutines needed by |make_exp_copy|@>; static void mp_make_exp_copy (MP mp, mp_node p) { mp_node t; /* register(s) for list manipulation */ mp_value_node q; RESTART: mp->cur_exp.type = mp_type (p); switch (mp->cur_exp.type) { case mp_vacuous: case mp_boolean_type: case mp_known: set_cur_exp_value (value (p)); break; case unknown_types: t = mp_new_ring_entry (mp, p); set_cur_exp_node (t); break; case mp_string_type: set_cur_exp_str (str_value (p)); add_str_ref (cur_exp_str ()); break; case mp_picture_type: set_cur_exp_node (value_node (p)); add_edge_ref (cur_exp_node ()); break; case mp_pen_type: set_cur_exp_knot (copy_pen (knot_value (p))); break; case mp_path_type: set_cur_exp_knot (mp_copy_path (mp, knot_value (p))); break; case mp_transform_type: case mp_color_type: case mp_cmykcolor_type: case mp_pair_type: @<Copy the big node |p|@>; break; case mp_dependent: case mp_proto_dependent: mp_encapsulate (mp, mp_copy_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) p))); break; case mp_numeric_type: new_indep (p); goto RESTART; break; case mp_independent: q = mp_single_dependency (mp, p); if (q == mp->dep_final) { mp->cur_exp.type = mp_known; set_cur_exp_value (0); mp_free_dep_node (mp, q); } else { mp->cur_exp.type = mp_dependent; mp_encapsulate (mp, q); } break; default: mp_confusion (mp, "copy"); @:this can't happen copy}{\quad copy@>; break; } } @ The |encapsulate| subroutine assumes that |dep_final| is the tail of dependency list~|p|. @<Declare subroutines needed by |make_exp_copy|@>= static void mp_encapsulate (MP mp, mp_value_node p) { mp_node q = mp_get_value_node (mp); FUNCTION_TRACE2 ("mp_encapsulate(%p)\n", p); mp_name_type (q) = mp_capsule; mp_new_dep (mp, q, mp->cur_exp.type, p); set_cur_exp_node (q); } @ 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|, |mp_proto_dependent|, or |known|. @<Copy the big node |p|@>= { debug_printf("value_node (%p) = %p\n", p, value_node(p)); if (value_node (p) == NULL) { switch (mp_type (p)) { case mp_pair_type: mp_init_pair_node (mp, p); break; case mp_color_type: mp_init_color_node (mp, p); break; case mp_cmykcolor_type: mp_init_cmykcolor_node (mp, p); break; case mp_transform_type: mp_init_transform_node (mp, p); break; default: /* there are no other valid cases, but please the compiler */ break; } } t = mp_get_value_node (mp); mp_name_type (t) = mp_capsule; switch (mp->cur_exp.type) { case mp_pair_type: mp_init_pair_node (mp, t); mp_install (mp, y_part_loc (value_node (t)), y_part_loc (value_node (p))); mp_install (mp, x_part_loc (value_node (t)), x_part_loc (value_node (p))); break; case mp_color_type: mp_init_color_node (mp, t); mp_install (mp, blue_part_loc (value_node (t)), blue_part_loc (value_node (p))); mp_install (mp, green_part_loc (value_node (t)), green_part_loc (value_node (p))); mp_install (mp, red_part_loc (value_node (t)), red_part_loc (value_node (p))); break; case mp_cmykcolor_type: mp_init_cmykcolor_node (mp, t); mp_install (mp, black_part_loc (value_node (t)), black_part_loc (value_node (p))); mp_install (mp, yellow_part_loc (value_node (t)), yellow_part_loc (value_node (p))); mp_install (mp, magenta_part_loc (value_node (t)), magenta_part_loc (value_node (p))); mp_install (mp, cyan_part_loc (value_node (t)), cyan_part_loc (value_node (p))); break; case mp_transform_type: mp_init_transform_node (mp, t); mp_install (mp, yy_part_loc (value_node (t)), yy_part_loc (value_node (p))); mp_install (mp, yx_part_loc (value_node (t)), yx_part_loc (value_node (p))); mp_install (mp, xy_part_loc (value_node (t)), xy_part_loc (value_node (p))); mp_install (mp, xx_part_loc (value_node (t)), xx_part_loc (value_node (p))); mp_install (mp, ty_part_loc (value_node (t)), ty_part_loc (value_node (p))); mp_install (mp, tx_part_loc (value_node (t)), tx_part_loc (value_node (p))); break; default: /* there are no other valid cases, but please the compiler */ break; } set_cur_exp_node (t); } @ 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|@>= static void mp_install (MP mp, mp_node r, mp_node q) { mp_value_node p; /* temporary register */ if (mp_type (q) == mp_known) { set_value (r, value (q)); mp_type (r) = mp_known; } else if (mp_type (q) == mp_independent) { p = mp_single_dependency (mp, q); if (p == mp->dep_final) { mp_type (r) = mp_known; set_value (r, 0); mp_free_dep_node (mp, p); } else { mp_new_dep (mp, r, mp_dependent, p); } } else { mp_new_dep (mp, r, mp_type (q), mp_copy_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) q))); } } @ 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...@>= { p = mp_stash_cur_exp (mp); mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_cmd != comma) { @<Put the left bracket and the expression back...@>; mp_unstash_cur_exp (mp, p); } else { q = mp_stash_cur_exp (mp); mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_cmd != right_bracket) { mp_missing_err (mp, "]"); @.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."); mp_back_error (mp); } r = mp_stash_cur_exp (mp); mp_make_exp_copy (mp, q); mp_do_binary (mp, r, minus); mp_do_binary (mp, p, times); mp_do_binary (mp, q, plus); mp_get_x_next (mp); } } @ Here is a comparatively simple routine that is used to scan the \&{suffix} parameters of a macro. @<Declare the basic parsing subroutines@>= static void mp_scan_suffix (MP mp) { mp_node h, t; /* head and tail of the list being built */ mp_node p; /* temporary register */ h = mp_get_symbolic_node (mp); t = h; while (1) { if (mp->cur_cmd == left_bracket) { @<Scan a bracketed subscript and set |cur_cmd:=numeric_token|@>; } if (mp->cur_cmd == numeric_token) { p = mp_new_num_tok (mp, mp->cur_mod); } else if ((mp->cur_cmd == tag_token) || (mp->cur_cmd == internal_quantity)) { p = mp_get_symbolic_node (mp); set_mp_sym_sym (p, mp->cur_sym); mp_name_type (p) = mp->cur_sym_mod; } else { break; } mp_link (t) = p; t = p; mp_get_x_next (mp); } set_cur_exp_node (mp_link (h)); mp_free_symbolic_node (mp, h); mp->cur_exp.type = mp_token_list; } @ @<Scan a bracketed subscript and set |cur_cmd:=numeric_token|@>= { mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_known) mp_bad_subscript (mp); if (mp->cur_cmd != right_bracket) { mp_missing_err (mp, "]"); @.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."); mp_back_error (mp); } mp->cur_cmd = numeric_token; mp->cur_mod = cur_exp_value (); } @* 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@>= static void mp_scan_secondary (MP mp) { mp_node p; /* for list manipulation */ halfword c, d; /* operation codes or modifiers */ mp_node cc = NULL; mp_sym mac_name = NULL; /* token defined with \&{primarydef} */ RESTART: if ((mp->cur_cmd < min_primary_command) || (mp->cur_cmd > max_primary_command)) mp_bad_exp (mp, "A secondary"); @.A secondary expression...@>; mp_scan_primary (mp); CONTINUE: if (mp->cur_cmd <= max_secondary_command && mp->cur_cmd >= min_secondary_command) { p = mp_stash_cur_exp (mp); d = mp->cur_cmd; c = mp->cur_mod; if (d == secondary_primary_macro) { cc = mp->cur_mod_node; mac_name = mp->cur_sym; add_mac_ref (cc); } mp_get_x_next (mp); mp_scan_primary (mp); if (d != secondary_primary_macro) { mp_do_binary (mp, p, c); } else { mp_back_input (mp); mp_binary_mac (mp, p, cc, mac_name); decr_mac_ref (cc); mp_get_x_next (mp); goto RESTART; } goto CONTINUE; } } @ The following procedure calls a macro that has two parameters, |p| and |cur_exp|. @c static void mp_binary_mac (MP mp, mp_node p, mp_node c, mp_sym n) { mp_node q, r; /* nodes in the parameter list */ q = mp_get_symbolic_node (mp); r = mp_get_symbolic_node (mp); mp_link (q) = r; set_mp_sym_sym (q, p); set_mp_sym_sym (r, mp_stash_cur_exp (mp)); mp_macro_call (mp, c, q, n); } @ The next procedure, |scan_tertiary|, is pretty much the same deal. @<Declare the basic parsing subroutines@>= static void mp_scan_tertiary (MP mp) { mp_node p; /* for list manipulation */ halfword c, d; /* operation codes or modifiers */ mp_node cc = NULL; mp_sym mac_name = NULL; /* token defined with \&{secondarydef} */ RESTART: if ((mp->cur_cmd < min_primary_command) || (mp->cur_cmd > max_primary_command)) mp_bad_exp (mp, "A tertiary"); @.A tertiary expression...@>; mp_scan_secondary (mp); CONTINUE: if (mp->cur_cmd <= max_tertiary_command) { if (mp->cur_cmd >= min_tertiary_command) { p = mp_stash_cur_exp (mp); c = mp->cur_mod; d = mp->cur_cmd; if (d == tertiary_secondary_macro) { cc = mp->cur_mod_node; mac_name = mp->cur_sym; add_mac_ref (cc); } mp_get_x_next (mp); mp_scan_secondary (mp); if (d != tertiary_secondary_macro) { mp_do_binary (mp, p, c); } else { mp_back_input (mp); mp_binary_mac (mp, p, cc, mac_name); decr_mac_ref (cc); mp_get_x_next (mp); goto RESTART; } goto CONTINUE; } } } @ 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@>= static void mp_scan_expression (MP mp) { mp_node p; /* for list manipulation */ mp_knot path_p, path_q, r; mp_knot pp, qq; halfword c, d; /* operation codes or modifiers */ mp_node cc = NULL; int my_var_flag; /* initial value of |var_flag| */ mp_sym mac_name; /* token defined with \&{tertiarydef} */ boolean cycle_hit; /* did a path expression just end with `\&{cycle}'? */ scaled x, y; /* explicit coordinates or tension at a path join */ int t; /* knot type following a path join */ mp_value new_expr; t = 0; y = 0; x = 0; memset(&new_expr,0,sizeof(mp_value)); my_var_flag = mp->var_flag; mac_name = NULL; mp->expand_depth_count++; mp_check_expansion_depth (mp); RESTART: if ((mp->cur_cmd < min_primary_command) || (mp->cur_cmd > max_primary_command)) mp_bad_exp (mp, "An"); @.An expression...@>; mp_scan_tertiary (mp); CONTINUE: if (mp->cur_cmd <= max_expression_command) if (mp->cur_cmd >= min_expression_command) { if ((mp->cur_cmd != equals) || (my_var_flag != assignment)) { p = mp_stash_cur_exp (mp); d = mp->cur_cmd; c = mp->cur_mod; if (d == expression_tertiary_macro) { cc = mp->cur_mod_node; mac_name = mp->cur_sym; add_mac_ref (cc); } if ((d < ampersand) || ((d == ampersand) && ((mp_type (p) == mp_pair_type) || (mp_type (p) == mp_path_type)))) { @<Scan a path construction operation; but |return| if |p| has the wrong type@>; } else { mp_get_x_next (mp); mp_scan_tertiary (mp); if (d != expression_tertiary_macro) { mp_do_binary (mp, p, c); } else { mp_back_input (mp); mp_binary_mac (mp, p, cc, mac_name); decr_mac_ref (cc); mp_get_x_next (mp); goto RESTART; } } goto CONTINUE; } } mp->expand_depth_count--; } @ 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...@>= { 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 (mp->cur_cmd == cycle) { @<Get ready to close a cycle@>; } else { mp_scan_tertiary (mp); @<Convert the right operand, |cur_exp|, into a partial path from |pp| to~|qq|@>; } @<Join the partial paths and reset |p| and |q| to the head and tail of the result@>; if (mp->cur_cmd >= min_expression_command) if (mp->cur_cmd <= ampersand) if (!cycle_hit) goto CONTINUE_PATH; FINISH_PATH: @<Choose control points for the path and put the result into |cur_exp|@>; } @ @<Convert the left operand, |p|, into a partial path ending at~|q|...@>= { mp_unstash_cur_exp (mp, p); if (mp->cur_exp.type == mp_pair_type) path_p = mp_pair_to_knot (mp); else if (mp->cur_exp.type == mp_path_type) path_p = cur_exp_knot (); else return; path_q = path_p; while (mp_next_knot (path_q) != path_p) path_q = mp_next_knot (path_q); if (mp_left_type (path_p) != mp_endpoint) { /* open up a cycle */ r = mp_copy_knot (mp, path_p); mp_next_knot (path_q) = r; path_q = r; } mp_left_type (path_p) = mp_open; mp_right_type (path_q) = mp_open; } @ 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. @c static mp_knot mp_pair_to_knot (MP mp) { /* convert a pair to a knot with two endpoints */ mp_knot q; /* the new node */ q = xmalloc (1, sizeof (struct mp_knot_data)); memset (q, 0, sizeof (struct mp_knot_data)); mp_left_type (q) = mp_endpoint; mp_right_type (q) = mp_endpoint; mp_originator (q) = mp_metapost_user; mp_next_knot (q) = q; mp_known_pair (mp); mp_x_coord (q) = mp->cur_x; mp_y_coord (q) = mp->cur_y; return q; } @ 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. @<Declarations@>= static void mp_known_pair (MP mp); @ @c void mp_known_pair (MP mp) { mp_value new_expr; mp_node p; /* the pair node */ memset(&new_expr,0,sizeof(mp_value)); if (mp->cur_exp.type != mp_pair_type) { 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.)"); mp_put_get_flush_error (mp, new_expr); mp->cur_x = 0; mp->cur_y = 0; } else { p = value_node (cur_exp_node ()); @<Make sure that both |x| and |y| parts of |p| are known; copy them into |cur_x| and |cur_y|@>; mp_flush_cur_exp (mp, new_expr); } } @ @<Make sure that both |x| and |y| parts of |p| are known...@>= if (mp_type (x_part_loc (p)) == mp_known) { mp->cur_x = value (x_part_loc (p)); } else { mp_disp_err (mp, 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.)"); mp_put_get_error (mp); mp_recycle_value (mp, x_part_loc (p)); mp->cur_x = 0; } if (mp_type (y_part_loc (p)) == mp_known) { mp->cur_y = value (y_part_loc (p)); } else { mp_disp_err (mp, 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.)"); mp_put_get_error (mp); mp_recycle_value (mp, y_part_loc (p)); mp->cur_y = 0; } @ At this point |cur_cmd| is either |ampersand|, |left_brace|, or |path_join|. @<Determine the path join parameters...@>= if (mp->cur_cmd == left_brace) { @<Put the pre-join direction information into node |q|@>; } d = mp->cur_cmd; if (d == path_join) { @<Determine the tension and/or control points@>; } else if (d != ampersand) { goto FINISH_PATH; } mp_get_x_next (mp); if (mp->cur_cmd == left_brace) { @<Put the post-join direction information into |x| and |t|@>; } else if (mp_right_type (path_q) != mp_explicit) { t = mp_open; x = 0; } @ 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. @c static quarterword mp_scan_direction (MP mp) { int t; /* the type of information found */ scaled x; /* an |x| coordinate */ mp_get_x_next (mp); if (mp->cur_cmd == curl_command) { @<Scan a curl specification@>; } else { @<Scan a given direction@>; } if (mp->cur_cmd != right_brace) { mp_missing_err (mp, "}"); @.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."); mp_back_error (mp); } mp_get_x_next (mp); return (quarterword) t; } @ @<Scan a curl specification@>= { mp_get_x_next (mp); mp_scan_expression (mp); if ((mp->cur_exp.type != mp_known) || (cur_exp_value () < 0)) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); new_expr.data.val = unity; exp_err ("Improper curl has been replaced by 1"); @.Improper curl@>; help1 ("A curl must be a known, nonnegative number."); mp_put_get_flush_error (mp, new_expr); } t = mp_curl; } @ @<Scan a given direction@>= { mp_scan_expression (mp); if (mp->cur_exp.type > mp_pair_type) { @<Get given directions separated by commas@>; } else { mp_known_pair (mp); } if ((mp->cur_x == 0) && (mp->cur_y == 0)) t = mp_open; else { t = mp_given; set_cur_exp_value (mp_n_arg (mp, mp->cur_x, mp->cur_y)); } } @ @<Get given directions separated by commas@>= { if (mp->cur_exp.type != mp_known) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); new_expr.data.val = 0; 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.)"); mp_put_get_flush_error (mp, new_expr); } x = cur_exp_value (); if (mp->cur_cmd != comma) { mp_missing_err (mp, ","); @.Missing `,'@>; help2 ("I've got the x coordinate of a path direction;", "will look for the y coordinate next."); mp_back_error (mp); } mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_known) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); new_expr.data.val = 0; 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.)"); mp_put_get_flush_error (mp, new_expr); } mp->cur_y = cur_exp_value (); mp->cur_x = x; } @ At this point |mp_right_type(q)| is usually |open|, but it may have been set to some other value by a previous operation. We must maintain the value of |mp_right_type(q)| in cases such as `\.{..\{curl2\}z\{0,0\}..}'. @<Put the pre-join...@>= { t = mp_scan_direction (mp); if (t != mp_open) { mp_right_type (path_q) = (unsigned short) t; right_given (path_q) = cur_exp_value (); if (mp_left_type (path_q) == mp_open) { mp_left_type (path_q) = (unsigned short) t; left_given (path_q) = cur_exp_value (); } /* note that |left_given(q)=left_curl(q)| */ } } @ Since |left_tension| and |mp_left_y| share the same position in knot nodes, and since |left_given| is similarly equivalent to |mp_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...@>= { t = mp_scan_direction (mp); if (mp_right_type (path_q) != mp_explicit) x = cur_exp_value (); else t = mp_explicit; /* the direction information is superfluous */ } @ @<Determine the tension and/or...@>= { mp_get_x_next (mp); if (mp->cur_cmd == tension) { @<Set explicit tensions@>; } else if (mp->cur_cmd == controls) { @<Set explicit control points@>; } else { right_tension (path_q) = unity; y = unity; mp_back_input (mp); /* default tension */ goto DONE; }; if (mp->cur_cmd != path_join) { mp_missing_err (mp, ".."); @.Missing `..'@>; help1 ("A path join command should end with two dots."); mp_back_error (mp); } DONE: ; } @ @<Set explicit tensions@>= { mp_get_x_next (mp); y = mp->cur_cmd; if (mp->cur_cmd == at_least) mp_get_x_next (mp); mp_scan_primary (mp); @<Make sure that the current expression is a valid tension setting@>; if (y == at_least) negate (cur_exp_value ()); right_tension (path_q) = cur_exp_value (); if (mp->cur_cmd == and_command) { mp_get_x_next (mp); y = mp->cur_cmd; if (mp->cur_cmd == at_least) mp_get_x_next (mp); mp_scan_primary (mp); @<Make sure that the current expression is a valid tension setting@>; if (y == at_least) negate (cur_exp_value ()); } y = cur_exp_value (); } @ @d min_tension three_quarter_unit @<Make sure that the current expression is a valid tension setting@>= if ((mp->cur_exp.type != mp_known) || (cur_exp_value () < min_tension)) { exp_err ("Improper tension has been set to 1"); @.Improper tension@>; help1 ("The expression above should have been a number >=3/4."); new_expr.data.val = unity; mp_put_get_flush_error (mp, new_expr); } @ @<Set explicit control points@>= { mp_right_type (path_q) = mp_explicit; t = mp_explicit; mp_get_x_next (mp); mp_scan_primary (mp); mp_known_pair (mp); mp_right_x (path_q) = mp->cur_x; mp_right_y (path_q) = mp->cur_y; if (mp->cur_cmd != and_command) { x = mp_right_x (path_q); y = mp_right_y (path_q); } else { mp_get_x_next (mp); mp_scan_primary (mp); mp_known_pair (mp); x = mp->cur_x; y = mp->cur_y; } } @ @<Convert the right operand, |cur_exp|, into a partial path...@>= { if (mp->cur_exp.type != mp_path_type) pp = mp_pair_to_knot (mp); else pp = cur_exp_knot (); qq = pp; while (mp_next_knot (qq) != pp) qq = mp_next_knot (qq); if (mp_left_type (pp) != mp_endpoint) { /* open up a cycle */ r = mp_copy_knot (mp, pp); mp_next_knot (qq) = r; qq = r; } mp_left_type (pp) = mp_open; mp_right_type (qq) = mp_open; } @ 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@>= { cycle_hit = true; mp_get_x_next (mp); pp = path_p; qq = path_p; if (d == ampersand) if (path_p == path_q) { d = path_join; right_tension (path_q) = unity; y = unity; } } @ @<Join the partial paths and reset |p| and |q|...@>= { if (d == ampersand) { if ((mp_x_coord (path_q) != mp_x_coord (pp)) || (mp_y_coord (path_q) != mp_y_coord (pp))) { 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."); mp_put_get_error (mp); d = path_join; right_tension (path_q) = unity; y = unity; } } @<Plug an opening in |mp_right_type(pp)|, if possible@>; if (d == ampersand) { @<Splice independent paths together@>; } else { @<Plug an opening in |mp_right_type(q)|, if possible@>; mp_next_knot (path_q) = pp; mp_left_y (pp) = y; if (t != mp_open) { mp_left_x (pp) = x; mp_left_type (pp) = (unsigned short) t; }; } path_q = qq; } @ @<Plug an opening in |mp_right_type(q)|...@>= if (mp_right_type (path_q) == mp_open) { if ((mp_left_type (path_q) == mp_curl) || (mp_left_type (path_q) == mp_given)) { mp_right_type (path_q) = mp_left_type (path_q); right_given (path_q) = left_given (path_q); } } @ @<Plug an opening in |mp_right_type(pp)|...@>= if (mp_right_type (pp) == mp_open) { if ((t == mp_curl) || (t == mp_given)) { mp_right_type (pp) = (unsigned short) t; right_given (pp) = x; } } @ @<Splice independent paths together@>= { if (mp_left_type (path_q) == mp_open) if (mp_right_type (path_q) == mp_open) { mp_left_type (path_q) = mp_curl; left_curl (path_q) = unity; } if (mp_right_type (pp) == mp_open) if (t == mp_open) { mp_right_type (pp) = mp_curl; right_curl (pp) = unity; } mp_right_type (path_q) = mp_right_type (pp); mp_next_knot (path_q) = mp_next_knot (pp); mp_right_x (path_q) = mp_right_x (pp); mp_right_y (path_q) = mp_right_y (pp); mp_xfree (pp); if (qq == pp) qq = path_q; } @ @<Choose control points for the path...@>= if (cycle_hit) { if (d == ampersand) path_p = path_q; } else { mp_left_type (path_p) = mp_endpoint; if (mp_right_type (path_p) == mp_open) { mp_right_type (path_p) = mp_curl; right_curl (path_p) = unity; } mp_right_type (path_q) = mp_endpoint; if (mp_left_type (path_q) == mp_open) { mp_left_type (path_q) = mp_curl; left_curl (path_q) = unity; } mp_next_knot (path_q) = path_p; } mp_make_choices (mp, path_p); mp->cur_exp.type = mp_path_type; set_cur_exp_knot (path_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@>= static void mp_get_boolean (MP mp) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_boolean_type) { 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'."); new_expr.data.val = false_code; mp_put_get_flush_error (mp, new_expr); mp->cur_exp.type = mp_boolean_type; } } @* 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...@>= mp_primitive (mp, "true", nullary, true_code); @:true_}{\&{true} primitive@>; mp_primitive (mp, "false", nullary, false_code); @:false_}{\&{false} primitive@>; mp_primitive (mp, "nullpicture", nullary, null_picture_code); @:null_picture_}{\&{nullpicture} primitive@>; mp_primitive (mp, "nullpen", nullary, null_pen_code); @:null_pen_}{\&{nullpen} primitive@>; mp_primitive (mp, "readstring", nullary, read_string_op); @:read_string_}{\&{readstring} primitive@>; mp_primitive (mp, "pencircle", nullary, pen_circle); @:pen_circle_}{\&{pencircle} primitive@>; mp_primitive (mp, "normaldeviate", nullary, normal_deviate); @:normal_deviate_}{\&{normaldeviate} primitive@>; mp_primitive (mp, "readfrom", unary, read_from_op); @:read_from_}{\&{readfrom} primitive@>; mp_primitive (mp, "closefrom", unary, close_from_op); @:close_from_}{\&{closefrom} primitive@>; mp_primitive (mp, "odd", unary, odd_op); @:odd_}{\&{odd} primitive@>; mp_primitive (mp, "known", unary, known_op); @:known_}{\&{known} primitive@>; mp_primitive (mp, "unknown", unary, unknown_op); @:unknown_}{\&{unknown} primitive@>; mp_primitive (mp, "not", unary, not_op); @:not_}{\&{not} primitive@>; mp_primitive (mp, "decimal", unary, decimal); @:decimal_}{\&{decimal} primitive@>; mp_primitive (mp, "reverse", unary, reverse); @:reverse_}{\&{reverse} primitive@>; mp_primitive (mp, "makepath", unary, make_path_op); @:make_path_}{\&{makepath} primitive@>; mp_primitive (mp, "makepen", unary, make_pen_op); @:make_pen_}{\&{makepen} primitive@>; mp_primitive (mp, "oct", unary, oct_op); @:oct_}{\&{oct} primitive@>; mp_primitive (mp, "hex", unary, hex_op); @:hex_}{\&{hex} primitive@>; mp_primitive (mp, "ASCII", unary, ASCII_op); @:ASCII_}{\&{ASCII} primitive@>; mp_primitive (mp, "char", unary, char_op); @:char_}{\&{char} primitive@>; mp_primitive (mp, "length", unary, length_op); @:length_}{\&{length} primitive@>; mp_primitive (mp, "turningnumber", unary, turning_op); @:turning_number_}{\&{turningnumber} primitive@>; mp_primitive (mp, "xpart", unary, x_part); @:x_part_}{\&{xpart} primitive@>; mp_primitive (mp, "ypart", unary, y_part); @:y_part_}{\&{ypart} primitive@>; mp_primitive (mp, "xxpart", unary, xx_part); @:xx_part_}{\&{xxpart} primitive@>; mp_primitive (mp, "xypart", unary, xy_part); @:xy_part_}{\&{xypart} primitive@>; mp_primitive (mp, "yxpart", unary, yx_part); @:yx_part_}{\&{yxpart} primitive@>; mp_primitive (mp, "yypart", unary, yy_part); @:yy_part_}{\&{yypart} primitive@>; mp_primitive (mp, "redpart", unary, red_part); @:red_part_}{\&{redpart} primitive@>; mp_primitive (mp, "greenpart", unary, green_part); @:green_part_}{\&{greenpart} primitive@>; mp_primitive (mp, "bluepart", unary, blue_part); @:blue_part_}{\&{bluepart} primitive@>; mp_primitive (mp, "cyanpart", unary, cyan_part); @:cyan_part_}{\&{cyanpart} primitive@>; mp_primitive (mp, "magentapart", unary, magenta_part); @:magenta_part_}{\&{magentapart} primitive@>; mp_primitive (mp, "yellowpart", unary, yellow_part); @:yellow_part_}{\&{yellowpart} primitive@>; mp_primitive (mp, "blackpart", unary, black_part); @:black_part_}{\&{blackpart} primitive@>; mp_primitive (mp, "greypart", unary, grey_part); @:grey_part_}{\&{greypart} primitive@>; mp_primitive (mp, "colormodel", unary, color_model_part); @:color_model_part_}{\&{colormodel} primitive@>; mp_primitive (mp, "fontpart", unary, font_part); @:font_part_}{\&{fontpart} primitive@>; mp_primitive (mp, "textpart", unary, text_part); @:text_part_}{\&{textpart} primitive@>; mp_primitive (mp, "pathpart", unary, path_part); @:path_part_}{\&{pathpart} primitive@>; mp_primitive (mp, "penpart", unary, pen_part); @:pen_part_}{\&{penpart} primitive@>; mp_primitive (mp, "dashpart", unary, dash_part); @:dash_part_}{\&{dashpart} primitive@>; mp_primitive (mp, "sqrt", unary, sqrt_op); @:sqrt_}{\&{sqrt} primitive@>; mp_primitive (mp, "mexp", unary, mp_m_exp_op); @:m_exp_}{\&{mexp} primitive@>; mp_primitive (mp, "mlog", unary, mp_m_log_op); @:m_log_}{\&{mlog} primitive@>; mp_primitive (mp, "sind", unary, sin_d_op); @:sin_d_}{\&{sind} primitive@>; mp_primitive (mp, "cosd", unary, cos_d_op); @:cos_d_}{\&{cosd} primitive@>; mp_primitive (mp, "floor", unary, floor_op); @:floor_}{\&{floor} primitive@>; mp_primitive (mp, "uniformdeviate", unary, uniform_deviate); @:uniform_deviate_}{\&{uniformdeviate} primitive@>; mp_primitive (mp, "charexists", unary, char_exists_op); @:char_exists_}{\&{charexists} primitive@>; mp_primitive (mp, "fontsize", unary, font_size); @:font_size_}{\&{fontsize} primitive@>; mp_primitive (mp, "llcorner", unary, ll_corner_op); @:ll_corner_}{\&{llcorner} primitive@>; mp_primitive (mp, "lrcorner", unary, lr_corner_op); @:lr_corner_}{\&{lrcorner} primitive@>; mp_primitive (mp, "ulcorner", unary, ul_corner_op); @:ul_corner_}{\&{ulcorner} primitive@>; mp_primitive (mp, "urcorner", unary, ur_corner_op); @:ur_corner_}{\&{urcorner} primitive@>; mp_primitive (mp, "arclength", unary, arc_length); @:arc_length_}{\&{arclength} primitive@>; mp_primitive (mp, "angle", unary, angle_op); @:angle_}{\&{angle} primitive@>; mp_primitive (mp, "cycle", cycle, cycle_op); @:cycle_}{\&{cycle} primitive@>; mp_primitive (mp, "stroked", unary, stroked_op); @:stroked_}{\&{stroked} primitive@>; mp_primitive (mp, "filled", unary, filled_op); @:filled_}{\&{filled} primitive@>; mp_primitive (mp, "textual", unary, textual_op); @:textual_}{\&{textual} primitive@>; mp_primitive (mp, "clipped", unary, clipped_op); @:clipped_}{\&{clipped} primitive@>; mp_primitive (mp, "bounded", unary, bounded_op); @:bounded_}{\&{bounded} primitive@>; mp_primitive (mp, "+", plus_or_minus, plus); @:+ }{\.{+} primitive@>; mp_primitive (mp, "-", plus_or_minus, minus); @:- }{\.{-} primitive@>; mp_primitive (mp, "*", secondary_binary, times); @:* }{\.{*} primitive@>; mp_primitive (mp, "/", slash, over); mp->frozen_slash = mp_frozen_primitive (mp, "/", slash, over); @:/ }{\.{/} primitive@>; mp_primitive (mp, "++", tertiary_binary, pythag_add); @:++_}{\.{++} primitive@>; mp_primitive (mp, "+-+", tertiary_binary, pythag_sub); @:+-+_}{\.{+-+} primitive@>; mp_primitive (mp, "or", tertiary_binary, or_op); @:or_}{\&{or} primitive@>; mp_primitive (mp, "and", and_command, and_op); @:and_}{\&{and} primitive@>; mp_primitive (mp, "<", expression_binary, less_than); @:< }{\.{<} primitive@>; mp_primitive (mp, "<=", expression_binary, less_or_equal); @:<=_}{\.{<=} primitive@>; mp_primitive (mp, ">", expression_binary, greater_than); @:> }{\.{>} primitive@>; mp_primitive (mp, ">=", expression_binary, greater_or_equal); @:>=_}{\.{>=} primitive@>; mp_primitive (mp, "=", equals, equal_to); @:= }{\.{=} primitive@>; mp_primitive (mp, "<>", expression_binary, unequal_to); @:<>_}{\.{<>} primitive@>; mp_primitive (mp, "substring", primary_binary, substring_of); @:substring_}{\&{substring} primitive@>; mp_primitive (mp, "subpath", primary_binary, subpath_of); @:subpath_}{\&{subpath} primitive@>; mp_primitive (mp, "directiontime", primary_binary, direction_time_of); @:direction_time_}{\&{directiontime} primitive@>; mp_primitive (mp, "point", primary_binary, point_of); @:point_}{\&{point} primitive@>; mp_primitive (mp, "precontrol", primary_binary, precontrol_of); @:precontrol_}{\&{precontrol} primitive@>; mp_primitive (mp, "postcontrol", primary_binary, postcontrol_of); @:postcontrol_}{\&{postcontrol} primitive@>; mp_primitive (mp, "penoffset", primary_binary, pen_offset_of); @:pen_offset_}{\&{penoffset} primitive@>; mp_primitive (mp, "arctime", primary_binary, arc_time_of); @:arc_time_of_}{\&{arctime} primitive@>; mp_primitive (mp, "mpversion", nullary, mp_version); @:mp_verison_}{\&{mpversion} primitive@>; mp_primitive (mp, "&", ampersand, concatenate); @:!!!}{\.{\&} primitive@>; mp_primitive (mp, "rotated", secondary_binary, rotated_by); @:rotated_}{\&{rotated} primitive@>; mp_primitive (mp, "slanted", secondary_binary, slanted_by); @:slanted_}{\&{slanted} primitive@>; mp_primitive (mp, "scaled", secondary_binary, scaled_by); @:scaled_}{\&{scaled} primitive@>; mp_primitive (mp, "shifted", secondary_binary, shifted_by); @:shifted_}{\&{shifted} primitive@>; mp_primitive (mp, "transformed", secondary_binary, transformed_by); @:transformed_}{\&{transformed} primitive@>; mp_primitive (mp, "xscaled", secondary_binary, x_scaled); @:x_scaled_}{\&{xscaled} primitive@>; mp_primitive (mp, "yscaled", secondary_binary, y_scaled); @:y_scaled_}{\&{yscaled} primitive@>; mp_primitive (mp, "zscaled", secondary_binary, z_scaled); @:z_scaled_}{\&{zscaled} primitive@>; mp_primitive (mp, "infont", secondary_binary, in_font); @:in_font_}{\&{infont} primitive@>; mp_primitive (mp, "intersectiontimes", tertiary_binary, intersect); @:intersection_times_}{\&{intersectiontimes} primitive@>; mp_primitive (mp, "envelope", primary_binary, envelope_of); @:envelope_}{\&{envelope} primitive@>; mp_primitive (mp, "glyph", primary_binary, glyph_infont); @:glyph_infont_}{\&{envelope} primitive@> @ @<Cases of |print_cmd...@>= case nullary: case unary: case primary_binary: case secondary_binary: case tertiary_binary: case expression_binary: case cycle: case plus_or_minus: case slash: case ampersand: case equals: case and_command: mp_print_op (mp, (quarterword) m); break; @ OK, let's look at the simplest \\{do} procedure first. @c @<Declare nullary action procedure@>; static void mp_do_nullary (MP mp, quarterword c) { check_arith; if (internal_value (mp_tracing_commands) > two) mp_show_cmd_mod (mp, nullary, c); switch (c) { case true_code: case false_code: mp->cur_exp.type = mp_boolean_type; set_cur_exp_value (c); break; case null_picture_code: mp->cur_exp.type = mp_picture_type; set_cur_exp_node (mp_get_edge_header_node (mp)); mp_init_edges (mp, cur_exp_node ()); break; case null_pen_code: mp->cur_exp.type = mp_pen_type; set_cur_exp_knot (mp_get_pen_circle (mp, 0)); break; case normal_deviate: mp->cur_exp.type = mp_known; set_cur_exp_value (mp_norm_rand (mp)); break; case pen_circle: mp->cur_exp.type = mp_pen_type; set_cur_exp_knot (mp_get_pen_circle (mp, unity)); break; case mp_version: mp->cur_exp.type = mp_string_type; set_cur_exp_str (mp_intern (mp, metapost_version)); break; case read_string_op: @<Read a string from the terminal@>; break; } /* there are no other cases */ check_arith; } @ @<Read a string...@>= { if (mp->noninteractive || mp->interaction <= mp_nonstop_mode) mp_fatal_error (mp, "*** (cannot readstring in nonstop modes)"); mp_begin_file_reading (mp); name = is_read; limit = start; prompt_input (""); mp_finish_read (mp); } @ @<Declare nullary action procedure@>= static void mp_finish_read (MP mp) { /* copy |buffer| line to |cur_exp| */ size_t k; str_room (((int) mp->last - (int) start)); for (k = (size_t) start; k < mp->last; k++) { append_char (mp->buffer[k]); } mp_end_file_reading (mp); mp->cur_exp.type = mp_string_type; set_cur_exp_str (mp_make_string (mp)); } @ Things get a bit more interesting when there's an operand. The operand to |do_unary| appears in |cur_type| and |cur_exp|. @c @<Declare unary action procedures@>; static void mp_do_unary (MP mp, quarterword c) { mp_node p, q, r; /* for list manipulation */ integer x; /* a temporary register */ halfword vv; /* a temporary place for |cur_exp_value| */ mp_value new_expr; check_arith; memset(&new_expr,0,sizeof(mp_value)); if (internal_value (mp_tracing_commands) > two) @<Trace the current unary operation@>; switch (c) { case plus: if (mp->cur_exp.type < mp_color_type) mp_bad_unary (mp, plus); break; case minus: @<Negate the current expression@>; break; @<Additional cases of unary operators@>; } /* there are no other cases */ check_arith; } @ The |nice_pair| function returns |true| if both components of a pair are known. @<Declare unary action procedures@>= static boolean mp_nice_pair (MP mp, mp_node p, quarterword t) { (void) mp; if (t == mp_pair_type) { p = value_node (p); if (mp_type (x_part_loc (p)) == mp_known) if (mp_type (y_part_loc (p)) == mp_known) return true; } return false; } @ The |nice_color_or_pair| function is analogous except that it also accepts fully known colors. @<Declare unary action procedures@>= static boolean mp_nice_color_or_pair (MP mp, mp_node p, quarterword t) { mp_node q; (void) mp; switch (t) { case mp_pair_type: q = value_node (p); if (mp_type (x_part_loc (q)) == mp_known) if (mp_type (y_part_loc (q)) == mp_known) return true; break; case mp_color_type: q = value_node (p); if (mp_type (red_part_loc (q)) == mp_known) if (mp_type (green_part_loc (q)) == mp_known) if (mp_type (blue_part_loc (q)) == mp_known) return true; break; case mp_cmykcolor_type: q = value_node (p); if (mp_type (cyan_part_loc (q)) == mp_known) if (mp_type (magenta_part_loc (q)) == mp_known) if (mp_type (yellow_part_loc (q)) == mp_known) if (mp_type (black_part_loc (q)) == mp_known) return true; break; } return false; } @ @<Declare unary action...@>= static void mp_print_known_or_unknown_type (MP mp, quarterword t, mp_node v) { mp_print_char (mp, xord ('(')); if (t > mp_known) mp_print (mp, "unknown numeric"); else { if ((t == mp_pair_type) || (t == mp_color_type) || (t == mp_cmykcolor_type)) if (!mp_nice_color_or_pair (mp, v, t)) mp_print (mp, "unknown "); mp_print_type (mp, t); } mp_print_char (mp, xord (')')); } @ @<Declare unary action...@>= static void mp_bad_unary (MP mp, quarterword c) { exp_err ("Not implemented: "); mp_print_op (mp, c); @.Not implemented...@>; mp_print_known_or_unknown_type (mp, mp->cur_exp.type, cur_exp_node ()); 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."); mp_put_get_error (mp); } @ @<Trace the current unary operation@>= { mp_begin_diagnostic (mp); mp_print_nl (mp, "{"); mp_print_op (mp, c); mp_print_char (mp, xord ('(')); mp_print_exp (mp, NULL, 0); /* show the operand, but not verbosely */ mp_print (mp, ")}"); mp_end_diagnostic (mp, false); } @ 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@>= switch (mp->cur_exp.type) { case mp_color_type: case mp_cmykcolor_type: case mp_pair_type: case mp_independent: q = cur_exp_node (); mp_make_exp_copy (mp, q); if (mp->cur_exp.type == mp_dependent) { mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) cur_exp_node ())); } else if (mp->cur_exp.type <= mp_pair_type) { /* |mp_color_type| |mp_cmykcolor_type|, or |mp_pair_type| */ p = value_node (cur_exp_node ()); switch (mp->cur_exp.type) { case mp_pair_type: r = x_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); r = y_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); break; case mp_color_type: r = red_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); r = green_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); r = blue_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); break; case mp_cmykcolor_type: r = cyan_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); r = magenta_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); r = yellow_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); r = black_part_loc (p); if (mp_type (r) == mp_known) negate (value (r)); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) r)); break; default: /* there are no other valid cases, but please the compiler */ break; } } /* if |cur_type=mp_known| then |cur_exp=0| */ mp_recycle_value (mp, q); mp_free_node (mp, q, value_node_size); break; case mp_dependent: case mp_proto_dependent: mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) cur_exp_node ())); break; case mp_known: negate (cur_exp_value ()); break; default: mp_bad_unary (mp, minus); break; } @ @<Declare unary action...@>= static void mp_negate_dep_list (MP mp, mp_value_node p) { (void) mp; while (1) { set_dep_value (p, -dep_value (p)); if (dep_info (p) == NULL) return; p = (mp_value_node) mp_link (p); } } @ @<Additional cases of unary operators@>= case not_op: if (mp->cur_exp.type != mp_boolean_type) { mp_bad_unary (mp, not_op); } else { halfword bb = true_code + false_code - cur_exp_value (); set_cur_exp_value (bb); } break; @ @d three_sixty_units 23592960 /* that's |360*unity| */ @d boolean_reset(A) if ( (A) ) set_cur_exp_value(true_code); else set_cur_exp_value(false_code) @<Additional cases of unary operators@>= case sqrt_op: case mp_m_exp_op: case mp_m_log_op: case sin_d_op: case cos_d_op: case floor_op: case uniform_deviate: case odd_op: case char_exists_op: if (mp->cur_exp.type != mp_known) { mp_bad_unary (mp, c); } else { switch (c) { case sqrt_op: vv = mp_square_rt (mp, cur_exp_value ()); set_cur_exp_value (vv); break; case mp_m_exp_op: vv = mp_m_exp (mp, cur_exp_value ()); set_cur_exp_value (vv); break; case mp_m_log_op: vv = mp_m_log (mp, cur_exp_value ()); set_cur_exp_value (vv); break; case sin_d_op: case cos_d_op: { fraction n_sin; fraction n_cos; /* results computed by |n_sin_cos| */ mp_n_sin_cos (mp, (cur_exp_value () % three_sixty_units) * 16, &n_cos, &n_sin); if (c == sin_d_op) set_cur_exp_value (mp_round_fraction (mp, n_sin)); else set_cur_exp_value (mp_round_fraction (mp, n_cos)); } break; case floor_op: vv = mp_floor_scaled (mp, cur_exp_value ()); set_cur_exp_value (vv); break; case uniform_deviate: vv = mp_unif_rand (mp, cur_exp_value ()); set_cur_exp_value (vv); break; case odd_op: vv = odd (mp_round_unscaled (mp, cur_exp_value ())); boolean_reset (vv); mp->cur_exp.type = mp_boolean_type; break; case char_exists_op: @<Determine if a character has been shipped out@>; break; } /* there are no other cases */ } break; @ @<Additional cases of unary operators@>= case angle_op: if (mp_nice_pair (mp, cur_exp_node (), mp->cur_exp.type)) { p = value_node (cur_exp_node ()); x = mp_n_arg (mp, value (x_part_loc (p)), value (y_part_loc (p))); if (x >= 0) new_expr.data.val = (x + 8) / 16; else new_expr.data.val = -((-x + 8) / 16); mp_flush_cur_exp (mp, new_expr); } else { mp_bad_unary (mp, angle_op); } break; @ If the current expression is a pair, but the context wants it to be a path, we call |pair_to_path|. @<Declare unary action...@>= static void mp_pair_to_path (MP mp) { set_cur_exp_knot (mp_pair_to_knot (mp)); mp->cur_exp.type = mp_path_type; } @ This complicated if test makes sure that any |bounds| or |clip| picture objects that get passed into \&{within} do not raise an error when queried using the color part primitives (this is needed for backward compatibility) . @d cur_pic_item mp_link(dummy_loc(cur_exp_node())) @d pict_color_type(A) ((cur_pic_item!=NULL) && ((!has_color(cur_pic_item)) || (((mp_color_model(cur_pic_item)==A) || ((mp_color_model(cur_pic_item)==mp_uninitialized_model) && (internal_value(mp_default_color_model)/unity)==(A)))))) @<Additional cases of unary operators@>= case x_part: case y_part: if ((mp->cur_exp.type == mp_pair_type) || (mp->cur_exp.type == mp_transform_type)) mp_take_part (mp, c); else if (mp->cur_exp.type == mp_picture_type) mp_take_pict_part (mp, c); else mp_bad_unary (mp, c); break; case xx_part: case xy_part: case yx_part: case yy_part: if (mp->cur_exp.type == mp_transform_type) mp_take_part (mp, c); else if (mp->cur_exp.type == mp_picture_type) mp_take_pict_part (mp, c); else mp_bad_unary (mp, c); break; case red_part: case green_part: case blue_part: if (mp->cur_exp.type == mp_color_type) mp_take_part (mp, c); else if (mp->cur_exp.type == mp_picture_type) { if pict_color_type (mp_rgb_model) mp_take_pict_part (mp, c); else mp_bad_color_part (mp, c); } else mp_bad_unary (mp, c); break; case cyan_part: case magenta_part: case yellow_part: case black_part: if (mp->cur_exp.type == mp_cmykcolor_type) mp_take_part (mp, c); else if (mp->cur_exp.type == mp_picture_type) { if pict_color_type (mp_cmyk_model) mp_take_pict_part (mp, c); else mp_bad_color_part (mp, c); } else mp_bad_unary (mp, c); break; case grey_part: if (mp->cur_exp.type == mp_known); /* |cur_exp_value()=cur_exp_value()| */ else if (mp->cur_exp.type == mp_picture_type) { if pict_color_type (mp_grey_model) mp_take_pict_part (mp, c); else mp_bad_color_part (mp, c); } else mp_bad_unary (mp, c); break; case color_model_part: if (mp->cur_exp.type == mp_picture_type) mp_take_pict_part (mp, c); else mp_bad_unary (mp, c); break; @ @<Declarations@>= static void mp_bad_color_part (MP mp, quarterword c); @ @c static void mp_bad_color_part (MP mp, quarterword c) { mp_node p; /* the big node */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); p = mp_link (dummy_loc (cur_exp_node ())); exp_err ("Wrong picture color model: "); mp_print_op (mp, c); @.Wrong picture color model...@>; if (mp_color_model (p) == mp_grey_model) mp_print (mp, " of grey object"); else if (mp_color_model (p) == mp_cmyk_model) mp_print (mp, " of cmyk object"); else if (mp_color_model (p) == mp_rgb_model) mp_print (mp, " of rgb object"); else if (mp_color_model (p) == mp_no_model) mp_print (mp, " of marking object"); else mp_print (mp, " of defaulted object"); help3 ("You can only ask for the redpart, greenpart, bluepart of a rgb object,", "the cyanpart, magentapart, yellowpart or blackpart of a cmyk object, ", "or the greypart of a grey object. No mixing and matching, please."); mp_error (mp); if (c == black_part) new_expr.data.val = unity; else new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr); } @ 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...@>= static void mp_take_part (MP mp, quarterword c) { mp_node p; /* the big node */ p = value_node (cur_exp_node ()); set_value_node (mp->temp_val, p); mp_type (mp->temp_val) = mp->cur_exp.type; mp_link (p) = mp->temp_val; mp_free_node (mp, cur_exp_node (), value_node_size); switch (c) { case x_part: if (mp->cur_exp.type == mp_pair_type) mp_make_exp_copy (mp, x_part_loc (p)); else mp_make_exp_copy (mp, tx_part_loc (p)); break; case y_part: if (mp->cur_exp.type == mp_pair_type) mp_make_exp_copy (mp, y_part_loc (p)); else mp_make_exp_copy (mp, ty_part_loc (p)); break; case xx_part: mp_make_exp_copy (mp, xx_part_loc (p)); break; case xy_part: mp_make_exp_copy (mp, xy_part_loc (p)); break; case yx_part: mp_make_exp_copy (mp, yx_part_loc (p)); break; case yy_part: mp_make_exp_copy (mp, yy_part_loc (p)); break; case red_part: mp_make_exp_copy (mp, red_part_loc (p)); break; case green_part: mp_make_exp_copy (mp, green_part_loc (p)); break; case blue_part: mp_make_exp_copy (mp, blue_part_loc (p)); break; case cyan_part: mp_make_exp_copy (mp, cyan_part_loc (p)); break; case magenta_part: mp_make_exp_copy (mp, magenta_part_loc (p)); break; case yellow_part: mp_make_exp_copy (mp, yellow_part_loc (p)); break; case black_part: mp_make_exp_copy (mp, black_part_loc (p)); break; } mp_recycle_value (mp, mp->temp_val); } @ @<Initialize table entries@>= mp->temp_val = mp_get_value_node (mp); mp_name_type (mp->temp_val) = mp_capsule; @ @<Free table entries@>= mp_free_value_node (mp, mp->temp_val); @ @<Additional cases of unary operators@>= case font_part: case text_part: case path_part: case pen_part: case dash_part: if (mp->cur_exp.type == mp_picture_type) mp_take_pict_part (mp, c); else mp_bad_unary (mp, c); break; @ @<Declarations@>= static void mp_scale_edges (MP mp); @ @<Declare unary action...@>= static void mp_take_pict_part (MP mp, quarterword c) { mp_node p; /* first graphical object in |cur_exp| */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); p = mp_link (dummy_loc (cur_exp_node ())); if (p != NULL) { switch (c) { case x_part: case y_part: case xx_part: case xy_part: case yx_part: case yy_part: if (mp_type (p) == mp_text_node_type) { switch (c) { case x_part: new_expr.data.val = tx_val (p); break; case y_part: new_expr.data.val = ty_val (p); break; case xx_part: new_expr.data.val = txx_val (p); break; case xy_part: new_expr.data.val = txy_val (p); break; case yx_part: new_expr.data.val = tyx_val (p); break; case yy_part: new_expr.data.val = tyy_val (p); break; } mp_flush_cur_exp (mp, new_expr); } else goto NOT_FOUND; break; case red_part: case green_part: case blue_part: if (has_color (p)) { switch (c) { case red_part: new_expr.data.val = red_val (p); break; case green_part: new_expr.data.val = green_val (p); break; case blue_part: new_expr.data.val = blue_val (p); break; } mp_flush_cur_exp (mp, new_expr); } else goto NOT_FOUND; break; case cyan_part: case magenta_part: case yellow_part: case black_part: if (has_color (p)) { if (mp_color_model (p) == mp_uninitialized_model && c == black_part) { new_expr.data.val = unity; } else { switch (c) { case cyan_part: new_expr.data.val = cyan_val (p); break; case magenta_part: new_expr.data.val = magenta_val (p); break; case yellow_part: new_expr.data.val = yellow_val (p); break; case black_part: new_expr.data.val = black_val (p); break; } } mp_flush_cur_exp (mp, new_expr); } else goto NOT_FOUND; break; case grey_part: if (has_color (p)) { new_expr.data.val = grey_val (p); mp_flush_cur_exp (mp, new_expr); } else goto NOT_FOUND; break; case color_model_part: if (has_color (p)) { if (mp_color_model (p) == mp_uninitialized_model) new_expr.data.val = internal_value (mp_default_color_model); else new_expr.data.val = mp_color_model (p) * unity; mp_flush_cur_exp (mp, new_expr); } else goto NOT_FOUND; break; @<Handle other cases in |take_pict_part| or |goto not_found|@>; } /* all cases have been enumerated */ return; }; NOT_FOUND: @<Convert the current expression to a NULL value appropriate for |c|@>; } @ @<Handle other cases in |take_pict_part| or |goto not_found|@>= case text_part: if (mp_type (p) != mp_text_node_type) goto NOT_FOUND; else { new_expr.data.str = mp_text_p (p); add_str_ref (new_expr.data.str); mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_string_type; }; break; case font_part: if (mp_type (p) != mp_text_node_type) goto NOT_FOUND; else { new_expr.data.str = mp_rts (mp, mp->font_name[mp_font_n (p)]); add_str_ref (new_expr.data.str); mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_string_type; }; break; case path_part: if (mp_type (p) == mp_text_node_type) goto NOT_FOUND; else if (is_stop (p)) mp_confusion (mp, "pict"); @:this can't happen pict}{\quad pict@> else { new_expr.data.node = NULL; switch (mp_type (p)) { case mp_fill_node_type: new_expr.data.p = mp_copy_path (mp, mp_path_p ((mp_fill_node) p)); break; case mp_stroked_node_type: new_expr.data.p = mp_copy_path (mp, mp_path_p ((mp_stroked_node) p)); break; case mp_start_bounds_node_type: new_expr.data.p = mp_copy_path (mp, mp_path_p ((mp_start_bounds_node) p)); break; case mp_start_clip_node_type: new_expr.data.p = mp_copy_path (mp, mp_path_p ((mp_start_clip_node) p)); break; default: assert (0); break; } mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_path_type; } break; case pen_part: if (!has_pen (p)) goto NOT_FOUND; else { switch (mp_type (p)) { case mp_fill_node_type: if (mp_pen_p ((mp_fill_node) p) == NULL) goto NOT_FOUND; else { new_expr.data.p = copy_pen (mp_pen_p ((mp_fill_node) p)); mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_pen_type; } break; case mp_stroked_node_type: if (mp_pen_p ((mp_stroked_node) p) == NULL) goto NOT_FOUND; else { new_expr.data.p = copy_pen (mp_pen_p ((mp_stroked_node) p)); mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_pen_type; } break; default: assert (0); break; } } break; case dash_part: if (mp_type (p) != mp_stroked_node_type) goto NOT_FOUND; else { if (mp_dash_p (p) == NULL) goto NOT_FOUND; else { add_edge_ref (mp_dash_p (p)); mp->se_sf = dash_scale (p); mp->se_pic = mp_dash_p (p); mp_scale_edges (mp); new_expr.data.node = mp->se_pic; mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_picture_type; } } break; @ 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...@>= mp_node se_pic; /* edge header used and updated by |scale_edges| */ scaled se_sf; /* the scale factor argument to |scale_edges| */ @ @<Convert the current expression to a NULL value appropriate...@>= switch (c) { case text_part: case font_part: new_expr.data.str = null_str; mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_string_type; break; case path_part: new_expr.data.p = mp_new_knot (mp); mp_flush_cur_exp (mp, new_expr); mp_left_type (cur_exp_knot ()) = mp_endpoint; mp_right_type (cur_exp_knot ()) = mp_endpoint; mp_next_knot (cur_exp_knot ()) = cur_exp_knot (); mp_x_coord (cur_exp_knot ()) = 0; mp_y_coord (cur_exp_knot ()) = 0; mp_originator (cur_exp_knot ()) = mp_metapost_user; mp->cur_exp.type = mp_path_type; break; case pen_part: new_expr.data.p = mp_get_pen_circle (mp, 0); mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_pen_type; break; case dash_part: new_expr.data.node = mp_get_edge_header_node (mp); mp_flush_cur_exp (mp, new_expr); mp_init_edges (mp, cur_exp_node ()); mp->cur_exp.type = mp_picture_type; break; default: new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr); break; } @ @<Additional cases of unary...@>= case char_op: if (mp->cur_exp.type != mp_known) { mp_bad_unary (mp, char_op); } else { vv = mp_round_unscaled (mp, cur_exp_value ()) % 256; set_cur_exp_value (vv); mp->cur_exp.type = mp_string_type; if (cur_exp_value () < 0) { vv = cur_exp_value () + 256; set_cur_exp_value (vv); } { unsigned char ss[2]; ss[0] = (unsigned char) cur_exp_value (); ss[1] = '\0'; set_cur_exp_str (mp_rtsl (mp, (char *) ss, 1)); } } break; case decimal: if (mp->cur_exp.type != mp_known) { mp_bad_unary (mp, decimal); } else { mp->old_setting = mp->selector; mp->selector = new_string; mp_print_scaled (mp, cur_exp_value ()); set_cur_exp_str (mp_make_string (mp)); mp->selector = mp->old_setting; mp->cur_exp.type = mp_string_type; } break; case oct_op: case hex_op: case ASCII_op: if (mp->cur_exp.type != mp_string_type) mp_bad_unary (mp, c); else mp_str_to_num (mp, c); break; case font_size: if (mp->cur_exp.type != mp_string_type) mp_bad_unary (mp, font_size); else @<Find the design size of the font whose name is |cur_exp|@>; break; @ @<Declare unary action...@>= static void mp_str_to_num (MP mp, quarterword c) { /* converts a string to a number */ integer n; /* accumulator */ ASCII_code m; /* current character */ unsigned k; /* index into |str_pool| */ int b; /* radix of conversion */ boolean bad_char; /* did the string contain an invalid digit? */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); if (c == ASCII_op) { if (length (cur_exp_str ()) == 0) n = -1; else n = cur_exp_str ()->str[0]; } else { if (c == oct_op) b = 8; else b = 16; n = 0; bad_char = false; for (k = 0; k < length (cur_exp_str ()); k++) { m = (ASCII_code) (*(cur_exp_str ()->str + k)); if ((m >= '0') && (m <= '9')) m = (ASCII_code) (m - '0'); else if ((m >= 'A') && (m <= 'F')) m = (ASCII_code) (m - 'A' + 10); else if ((m >= 'a') && (m <= 'f')) m = (ASCII_code) (m - 'a' + 10); else { bad_char = true; m = 0; }; if ((int) m >= b) { bad_char = true; m = 0; }; if (n < 32768 / b) n = n * b + m; else n = 32767; } @<Give error messages if |bad_char| or |n>=4096|@>; } new_expr.data.val = n * unity; mp_flush_cur_exp (mp, new_expr); } @ @<Give error messages if |bad_char|...@>= if (bad_char) { exp_err ("String contains illegal digits"); @.String contains illegal digits@>; if (c == oct_op) { help1 ("I zeroed out characters that weren't in the range 0..7."); } else { help1 ("I zeroed out characters that weren't hex digits."); } mp_put_get_error (mp); } if ((n > 4095)) { if (internal_value (mp_warning_check) > 0) { print_err ("Number too large ("); mp_print_int (mp, n); mp_print_char (mp, xord (')')); @.Number too large@>; help2 ("I have trouble with numbers greater than 4095; watch out.", "(Set warningcheck:=0 to suppress this message.)"); mp_put_get_error (mp); } } @ The length operation is somewhat unusual in that it applies to a variety of different types of operands. @<Additional cases of unary...@>= case length_op: switch (mp->cur_exp.type) { case mp_string_type: new_expr.data.val = (integer) (length (cur_exp_str ()) * unity); mp_flush_cur_exp (mp, new_expr); break; case mp_path_type: new_expr.data.val = mp_path_length (mp); mp_flush_cur_exp (mp, new_expr); break; case mp_known: vv = abs (cur_exp_value ()); set_cur_exp_value (vv); break; case mp_picture_type: new_expr.data.val = mp_pict_length (mp); mp_flush_cur_exp (mp, new_expr); break; default: if (mp_nice_pair (mp, cur_exp_node (), mp->cur_exp.type)) { new_expr.data.val = mp_pyth_add (mp, value (x_part_loc (value_node (cur_exp_node ()))), value (y_part_loc (value_node (cur_exp_node ())))); mp_flush_cur_exp (mp, new_expr); } else mp_bad_unary (mp, c); break; } break; @ @<Declare unary action...@>= static scaled mp_path_length (MP mp) { /* computes the length of the current path */ scaled n; /* the path length so far */ mp_knot p; /* traverser */ p = cur_exp_knot (); if (mp_left_type (p) == mp_endpoint) n = -unity; else n = 0; do { p = mp_next_knot (p); n = n + unity; } while (p != cur_exp_knot ()); return n; } @ @<Declare unary action...@>= static scaled mp_pict_length (MP mp) { /* counts interior components in picture |cur_exp| */ scaled n; /* the count so far */ mp_node p; /* traverser */ n = 0; p = mp_link (dummy_loc (cur_exp_node ())); if (p != NULL) { if (is_start_or_stop (p)) if (mp_skip_1component (mp, p) == NULL) p = mp_link (p); while (p != NULL) { skip_component (p) return n; n = n + unity; } } return n; } @ Implement |turningnumber| @<Additional cases of unary...@>= case turning_op: if (mp->cur_exp.type == mp_pair_type) { new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr); } else if (mp->cur_exp.type != mp_path_type) { mp_bad_unary (mp, turning_op); } else if (mp_left_type (cur_exp_knot ()) == mp_endpoint) { new_expr.data.p = NULL; mp_flush_cur_exp (mp, new_expr); /* not a cyclic path */ } else { new_expr.data.val = mp_turn_cycles_wrapper (mp, cur_exp_knot ()); mp_flush_cur_exp (mp, new_expr); } break; @ The function |an_angle| returns the value of the |angle| primitive, or $0$ if the argument is |origin|. @<Declare unary action...@>= static angle mp_an_angle (MP mp, scaled xpar, scaled ypar) { if ((!((xpar == 0) && (ypar == 0)))) return mp_n_arg (mp, xpar, ypar); return 0; } @ The actual turning number is (for the moment) computed in a C function that receives eight integers corresponding to the four controlling points, and returns a single angle. Besides those, we have to account for discrete moves at the actual points. @d mp_floor(a) ((a)>=0 ? (int)(a) : -(int)(-(a))) @d bezier_error (720*(256*256*16))+1 @d mp_sign(v) ((v)>0 ? 1 : ((v)<0 ? -1 : 0 )) @d mp_out(A) (double)((A)/(256*256*16)) @d divisor (256.0*256.0) @d double2angle(a) (int)mp_floor(a*256.0*256.0*16.0) @<Declare unary action...@>= static angle mp_bezier_slope (MP mp, integer AX, integer AY, integer BX, integer BY, integer CX, integer CY, integer DX, integer DY); @ @c static angle mp_bezier_slope (MP mp, integer AX, integer AY, integer BX, integer BY, integer CX, integer CY, integer DX, integer DY) { double a, b, c; integer deltax, deltay; double ax, ay, bx, by, cx, cy, dx, dy; angle xi = 0, xo = 0, xm = 0; double res = 0; ax = (double) (AX / divisor); ay = (double) (AY / divisor); bx = (double) (BX / divisor); by = (double) (BY / divisor); cx = (double) (CX / divisor); cy = (double) (CY / divisor); dx = (double) (DX / divisor); dy = (double) (DY / divisor); deltax = (BX - AX); deltay = (BY - AY); if (deltax == 0 && deltay == 0) { deltax = (CX - AX); deltay = (CY - AY); } if (deltax == 0 && deltay == 0) { deltax = (DX - AX); deltay = (DY - AY); } xi = mp_an_angle (mp, deltax, deltay); deltax = (CX - BX); deltay = (CY - BY); xm = mp_an_angle (mp, deltax, deltay); deltax = (DX - CX); deltay = (DY - CY); if (deltax == 0 && deltay == 0) { deltax = (DX - BX); deltay = (DY - BY); } if (deltax == 0 && deltay == 0) { deltax = (DX - AX); deltay = (DY - AY); } xo = mp_an_angle (mp, deltax, deltay); a = (bx - ax) * (cy - by) - (cx - bx) * (by - ay); /* a = (bp-ap)x(cp-bp); */ b = (bx - ax) * (dy - cy) - (by - ay) * (dx - cx);; /* b = (bp-ap)x(dp-cp); */ c = (cx - bx) * (dy - cy) - (dx - cx) * (cy - by); /* c = (cp-bp)x(dp-cp); */ if ((a == 0) && (c == 0)) { res = (b == 0 ? 0 : (mp_out (xo) - mp_out (xi))); } else if ((a == 0) || (c == 0)) { if ((mp_sign (b) == mp_sign (a)) || (mp_sign (b) == mp_sign (c))) { res = mp_out (xo) - mp_out (xi); /* ? */ if (res < -180.0) res += 360.0; else if (res > 180.0) res -= 360.0; } else { res = mp_out (xo) - mp_out (xi); /* ? */ } } else if ((mp_sign (a) * mp_sign (c)) < 0) { res = mp_out (xo) - mp_out (xi); /* ? */ if (res < -180.0) res += 360.0; else if (res > 180.0) res -= 360.0; } else { if (mp_sign (a) == mp_sign (b)) { res = mp_out (xo) - mp_out (xi); /* ? */ if (res < -180.0) res += 360.0; else if (res > 180.0) res -= 360.0; } else { if ((b * b) == (4 * a * c)) { res = (double) bezier_error; } else if ((b * b) < (4 * a * c)) { res = mp_out (xo) - mp_out (xi); /* ? */ if (res <= 0.0 && res > -180.0) res += 360.0; else if (res >= 0.0 && res < 180.0) res -= 360.0; } else { res = mp_out (xo) - mp_out (xi); if (res < -180.0) res += 360.0; else if (res > 180.0) res -= 360.0; } } } return double2angle (res); } @ @d p_nextnext mp_next_knot(mp_next_knot(p)) @d p_next mp_next_knot(p) @d seven_twenty_deg 05500000000 /* $720\cdot2^{20}$, represents $720^\circ$ */ @<Declare unary action...@>= static scaled mp_new_turn_cycles (MP mp, mp_knot c) { angle res, ang; /* the angles of intermediate results */ scaled turns; /* the turn counter */ mp_knot p; /* for running around the path */ integer xp, yp; /* coordinates of next point */ integer x, y; /* helper coordinates */ angle in_angle, out_angle; /* helper angles */ unsigned old_setting; /* saved |selector| setting */ res = 0; turns = 0; p = c; old_setting = mp->selector; mp->selector = term_only; if (internal_value (mp_tracing_commands) > unity) { mp_begin_diagnostic (mp); mp_print_nl (mp, ""); mp_end_diagnostic (mp, false); } do { xp = mp_x_coord (p_next); yp = mp_y_coord (p_next); ang = mp_bezier_slope (mp, mp_x_coord (p), mp_y_coord (p), mp_right_x (p), mp_right_y (p), mp_left_x (p_next), mp_left_y (p_next), xp, yp); if (ang > seven_twenty_deg) { print_err ("Strange path"); mp_error (mp); mp->selector = old_setting; return 0; } res = res + ang; if (res > one_eighty_deg) { res = res - three_sixty_deg; turns = turns + unity; } if (res <= -one_eighty_deg) { res = res + three_sixty_deg; turns = turns - unity; } /* incoming angle at next point */ x = mp_left_x (p_next); y = mp_left_y (p_next); if ((xp == x) && (yp == y)) { x = mp_right_x (p); y = mp_right_y (p); }; if ((xp == x) && (yp == y)) { x = mp_x_coord (p); y = mp_y_coord (p); }; in_angle = mp_an_angle (mp, xp - x, yp - y); /* outgoing angle at next point */ x = mp_right_x (p_next); y = mp_right_y (p_next); if ((xp == x) && (yp == y)) { x = mp_left_x (p_nextnext); y = mp_left_y (p_nextnext); }; if ((xp == x) && (yp == y)) { x = mp_x_coord (p_nextnext); y = mp_y_coord (p_nextnext); }; out_angle = mp_an_angle (mp, x - xp, y - yp); ang = (out_angle - in_angle); reduce_angle (ang); if (ang != 0) { res = res + ang; if (res >= one_eighty_deg) { res = res - three_sixty_deg; turns = turns + unity; }; if (res <= -one_eighty_deg) { res = res + three_sixty_deg; turns = turns - unity; }; }; p = mp_next_knot (p); } while (p != c); mp->selector = old_setting; return turns; } @ 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. @ 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 mp_next_knot(mp_next_knot(p)) @d p_here mp_next_knot(p) @d p_from p @<Declare unary action...@>= static scaled mp_turn_cycles (MP mp, mp_knot c) { angle res, ang; /* the angles of intermediate results */ scaled turns; /* the turn counter */ mp_knot p; /* for running around the path */ res = 0; turns = 0; p = c; do { ang = mp_an_angle (mp, mp_x_coord (p_to) - mp_x_coord (p_here), mp_y_coord (p_to) - mp_y_coord (p_here)) - mp_an_angle (mp, mp_x_coord (p_here) - mp_x_coord (p_from), mp_y_coord (p_here) - mp_y_coord (p_from)); reduce_angle (ang); res = res + ang; if (res >= three_sixty_deg) { res = res - three_sixty_deg; turns = turns + unity; }; if (res <= -three_sixty_deg) { res = res + three_sixty_deg; turns = turns - unity; }; p = mp_next_knot (p); } while (p != c); return turns; } @ @<Declare unary action...@>= static scaled mp_turn_cycles_wrapper (MP mp, mp_knot c) { scaled nval, oval; scaled saved_t_o; /* tracing\_online saved */ if ((mp_next_knot (c) == c) || (mp_next_knot (mp_next_knot (c)) == c)) { if (mp_an_angle (mp, mp_x_coord (c) - mp_right_x (c), mp_y_coord (c) - mp_right_y (c)) > 0) return unity; else return -unity; } else { nval = mp_new_turn_cycles (mp, c); oval = mp_turn_cycles (mp, c); if (nval != oval && internal_value (mp_tracing_choices) > (2 * unity)) { saved_t_o = internal_value (mp_tracing_online); internal_value (mp_tracing_online) = unity; mp_begin_diagnostic (mp); mp_print_nl (mp, "Warning: the turningnumber algorithms do not agree." " The current computed value is "); mp_print_scaled (mp, nval); mp_print (mp, ", but the 'connect-the-dots' algorithm returned "); mp_print_scaled (mp, oval); mp_end_diagnostic (mp, false); internal_value (mp_tracing_online) = saved_t_o; } return nval; } } @ @d type_range(A,B) { if ( (mp->cur_exp.type>=(A)) && (mp->cur_exp.type<=(B)) ) new_expr.data.val = true_code; else new_expr.data.val = false_code; mp_flush_cur_exp(mp, new_expr); mp->cur_exp.type=mp_boolean_type; } @d type_test(A) { if ( mp->cur_exp.type==(mp_variable_type)(A) ) new_expr.data.val = true_code; else new_expr.data.val = false_code; mp_flush_cur_exp(mp, new_expr); mp->cur_exp.type=mp_boolean_type; } @<Additional cases of unary operators@>= case mp_boolean_type: type_range (mp_boolean_type, mp_unknown_boolean); break; case mp_string_type: type_range (mp_string_type, mp_unknown_string); break; case mp_pen_type: type_range (mp_pen_type, mp_unknown_pen); break; case mp_path_type: type_range (mp_path_type, mp_unknown_path); break; case mp_picture_type: type_range (mp_picture_type, mp_unknown_picture); break; case mp_transform_type: case mp_color_type: case mp_cmykcolor_type: case mp_pair_type: type_test (c); break; case mp_numeric_type: type_range (mp_known, mp_independent); break; case known_op: case unknown_op: mp_test_known (mp, c); break; @ @<Declare unary action procedures@>= static void mp_test_known (MP mp, quarterword c) { int b; /* is the current expression known? */ mp_node p; /* location in a big node */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); b = false_code; switch (mp->cur_exp.type) { case mp_vacuous: case mp_boolean_type: case mp_string_type: case mp_pen_type: case mp_path_type: case mp_picture_type: case mp_known: b = true_code; break; case mp_transform_type: p = value_node (cur_exp_node ()); if (mp_type (tx_part_loc (p)) != mp_known) break; if (mp_type (ty_part_loc (p)) != mp_known) break; if (mp_type (xx_part_loc (p)) != mp_known) break; if (mp_type (xy_part_loc (p)) != mp_known) break; if (mp_type (yx_part_loc (p)) != mp_known) break; if (mp_type (yy_part_loc (p)) != mp_known) break; b = true_code; break; case mp_color_type: p = value_node (cur_exp_node ()); if (mp_type (red_part_loc (p)) != mp_known) break; if (mp_type (green_part_loc (p)) != mp_known) break; if (mp_type (blue_part_loc (p)) != mp_known) break; b = true_code; break; case mp_cmykcolor_type: p = value_node (cur_exp_node ()); if (mp_type (cyan_part_loc (p)) != mp_known) break; if (mp_type (magenta_part_loc (p)) != mp_known) break; if (mp_type (yellow_part_loc (p)) != mp_known) break; if (mp_type (black_part_loc (p)) != mp_known) break; b = true_code; break; case mp_pair_type: p = value_node (cur_exp_node ()); if (mp_type (x_part_loc (p)) != mp_known) break; if (mp_type (y_part_loc (p)) != mp_known) break; b = true_code; break; default: break; } if (c == known_op) new_expr.data.val = b; else new_expr.data.val = true_code + false_code - b; mp_flush_cur_exp (mp, new_expr); cur_exp_node () = NULL; mp->cur_exp.type = mp_boolean_type; } @ @<Additional cases of unary operators@>= case cycle_op: if (mp->cur_exp.type != mp_path_type) new_expr.data.val = false_code; else if (mp_left_type (cur_exp_knot ()) != mp_endpoint) new_expr.data.val = true_code; else new_expr.data.val = false_code; mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_boolean_type; break; @ @<Additional cases of unary operators@>= case arc_length: if (mp->cur_exp.type == mp_pair_type) mp_pair_to_path (mp); if (mp->cur_exp.type != mp_path_type) { mp_bad_unary (mp, arc_length); } else { new_expr.data.val = mp_get_arc_length (mp, cur_exp_knot ()); mp_flush_cur_exp (mp, new_expr); } break; @ 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@>= case filled_op: case stroked_op: case textual_op: case clipped_op: case bounded_op: if (mp->cur_exp.type != mp_picture_type) { new_expr.data.val = false_code; } else if (mp_link (dummy_loc (cur_exp_node ())) == NULL) { new_expr.data.val = false_code; } else if (mp_type (mp_link (dummy_loc (cur_exp_node ()))) == (mp_variable_type) (c + mp_fill_node_type - filled_op)) { new_expr.data.val = true_code; } else { new_expr.data.val = false_code; } mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_boolean_type; break; @ @<Additional cases of unary operators@>= case make_pen_op: if (mp->cur_exp.type == mp_pair_type) mp_pair_to_path (mp); if (mp->cur_exp.type != mp_path_type) mp_bad_unary (mp, make_pen_op); else { mp->cur_exp.type = mp_pen_type; set_cur_exp_knot (mp_make_pen (mp, cur_exp_knot (), true)); } break; case make_path_op: if (mp->cur_exp.type != mp_pen_type) { mp_bad_unary (mp, make_path_op); } else { mp->cur_exp.type = mp_path_type; mp_make_path (mp, cur_exp_knot ()); } break; case reverse: if (mp->cur_exp.type == mp_path_type) { mp_knot pk = mp_htap_ypoc (mp, cur_exp_knot ()); if (mp_right_type (pk) == mp_endpoint) pk = mp_next_knot (pk); mp_toss_knot_list (mp, cur_exp_knot ()); set_cur_exp_knot (pk); } else if (mp->cur_exp.type == mp_pair_type) { mp_pair_to_path (mp); } else { mp_bad_unary (mp, reverse); } break; @ The |pair_value| routine changes the current expression to a given ordered pair of values. @<Declare unary action procedures@>= static void mp_pair_value (MP mp, scaled x, scaled y) { mp_node p; /* a pair node */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); p = mp_get_value_node (mp); new_expr.type = mp_type (p); new_expr.data.node = p; mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_pair_type; mp_name_type (p) = mp_capsule; mp_init_pair_node (mp, p); p = value_node (p); mp_type (x_part_loc (p)) = mp_known; set_value (x_part_loc (p), x); mp_type (y_part_loc (p)) = mp_known; set_value (y_part_loc (p), y); } @ @<Additional cases of unary operators@>= case ll_corner_op: if (!mp_get_cur_bbox (mp)) mp_bad_unary (mp, ll_corner_op); else mp_pair_value (mp, mp_minx, mp_miny); break; case lr_corner_op: if (!mp_get_cur_bbox (mp)) mp_bad_unary (mp, lr_corner_op); else mp_pair_value (mp, mp_maxx, mp_miny); break; case ul_corner_op: if (!mp_get_cur_bbox (mp)) mp_bad_unary (mp, ul_corner_op); else mp_pair_value (mp, mp_minx, mp_maxy); break; case ur_corner_op: if (!mp_get_cur_bbox (mp)) mp_bad_unary (mp, ur_corner_op); else mp_pair_value (mp, mp_maxx, mp_maxy); break; @ 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@>= static boolean mp_get_cur_bbox (MP mp) { switch (mp->cur_exp.type) { case mp_picture_type: mp_set_bbox (mp, cur_exp_node (), true); if (minx_val (cur_exp_node ()) > maxx_val (cur_exp_node ())) { mp_minx = 0; mp_maxx = 0; mp_miny = 0; mp_maxy = 0; } else { mp_minx = minx_val (cur_exp_node ()); mp_maxx = maxx_val (cur_exp_node ()); mp_miny = miny_val (cur_exp_node ()); mp_maxy = maxy_val (cur_exp_node ()); } break; case mp_path_type: mp_path_bbox (mp, cur_exp_knot ()); break; case mp_pen_type: mp_pen_bbox (mp, cur_exp_knot ()); break; default: return false; } return true; } @ @<Additional cases of unary operators@>= case read_from_op: case close_from_op: if (mp->cur_exp.type != mp_string_type) mp_bad_unary (mp, c); else mp_do_read_or_close (mp, c); break; @ 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. @<Declare unary action procedures@>= static void mp_do_read_or_close (MP mp, quarterword c) { mp_value new_expr; readf_index n, n0; /* indices for searching |rd_fname| */ memset(&new_expr,0,sizeof(mp_value)); @<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|@>; mp_begin_file_reading (mp); name = is_read; if (mp_input_ln (mp, mp->rd_file[n])) goto FOUND; mp_end_file_reading (mp); NOT_FOUND: @<Record the end of file and set |cur_exp| to a dummy value@>; return; CLOSE_FILE: mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_vacuous; return; FOUND: mp_flush_cur_exp (mp, new_expr); mp_finish_read (mp); } @ Free slots in the |rd_file| and |rd_fname| arrays are marked with NULL's in |rd_fname|. @<Find the |n| where |rd_fname[n]=cur_exp|...@>= { char *fn; n = mp->read_files; n0 = mp->read_files; fn = mp_xstrdup (mp, mp_str (mp, cur_exp_str ())); while (mp_xstrcmp (fn, mp->rd_fname[n]) != 0) { if (n > 0) { decr (n); } else if (c == close_from_op) { goto CLOSE_FILE; } else { if (n0 == mp->read_files) { if (mp->read_files < mp->max_read_files) { incr (mp->read_files); } else { void **rd_file; char **rd_fname; readf_index l, k; l = mp->max_read_files + (mp->max_read_files / 4); rd_file = xmalloc ((l + 1), sizeof (void *)); rd_fname = xmalloc ((l + 1), sizeof (char *)); for (k = 0; k <= l; k++) { if (k <= mp->max_read_files) { rd_file[k] = mp->rd_file[k]; rd_fname[k] = mp->rd_fname[k]; } else { rd_file[k] = 0; rd_fname[k] = NULL; } } xfree (mp->rd_file); xfree (mp->rd_fname); mp->max_read_files = l; mp->rd_file = rd_file; mp->rd_fname = rd_fname; } } n = n0; if (mp_start_read_input (mp, fn, n)) goto FOUND; else goto NOT_FOUND; } if (mp->rd_fname[n] == NULL) { n0 = n; } } if (c == close_from_op) { (mp->close_file) (mp, mp->rd_file[n]); goto NOT_FOUND; } } @ @<Record the end of file and set |cur_exp| to a dummy value@>= xfree (mp->rd_fname[n]); mp->rd_fname[n] = NULL; if (n == mp->read_files - 1) mp->read_files = n; if (c == close_from_op) goto CLOSE_FILE; { new_expr.data.str = mp->eof_line; add_str_ref (new_expr.data.str); mp_flush_cur_exp (mp, new_expr); } mp->cur_exp.type = mp_string_type @ The string denoting end-of-file is a one-byte string at position zero, by definition. I have to cheat a little here because @<Glob...@>= str_number eof_line; @ @<Set init...@>= mp->eof_line = mp_rtsl (mp, "\0", 1); mp->eof_line->refs = MAX_STR_REF; @ Finally, we have the operations that combine a capsule~|p| with the current expression. @d binary_return { mp_finish_binary(mp, old_p, old_exp); return; } @c @<Declare binary action procedures@>; static void mp_finish_binary (MP mp, mp_node old_p, mp_node old_exp) { check_arith; @<Recycle any sidestepped |independent| capsules@>; } static void mp_do_binary (MP mp, mp_node p, integer c) { mp_node q, r, rr; /* for list manipulation */ mp_node old_p, old_exp; /* capsules to recycle */ integer v; /* for numeric manipulation */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); check_arith; if (internal_value (mp_tracing_commands) > two) { @<Trace the current binary operation@>; } @<Sidestep |independent| cases in capsule |p|@>; @<Sidestep |independent| cases in the current expression@>; switch (c) { case plus: case minus: @<Add or subtract the current expression from |p|@>; break; @<Additional cases of binary operators@>; }; /* there are no other cases */ mp_recycle_value (mp, p); mp_free_node (mp, p, value_node_size); /* |return| to avoid this */ mp_finish_binary (mp, old_p, old_exp); } @ @<Declare binary action...@>= static void mp_bad_binary (MP mp, mp_node p, quarterword c) { mp_disp_err (mp, p, ""); exp_err ("Not implemented: "); @.Not implemented...@>; if (c >= min_of) mp_print_op (mp, c); mp_print_known_or_unknown_type (mp, mp_type (p), p); if (c >= min_of) mp_print (mp, "of"); else mp_print_op (mp, c); mp_print_known_or_unknown_type (mp, mp->cur_exp.type, cur_exp_node ()); 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."); mp_put_get_error (mp); } static void mp_bad_envelope_pen (MP mp) { mp_disp_err (mp, NULL, ""); exp_err ("Not implemented: envelope(elliptical pen)of(path)"); @.Not implemented...@>; 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."); mp_put_get_error (mp); } @ @<Trace the current binary operation@>= { mp_begin_diagnostic (mp); mp_print_nl (mp, "{("); mp_print_exp (mp, p, 0); /* show the operand, but not verbosely */ mp_print_char (mp, xord (')')); mp_print_op (mp, (quarterword) c); mp_print_char (mp, xord ('(')); mp_print_exp (mp, NULL, 0); mp_print (mp, ")}"); mp_end_diagnostic (mp, false); } @ 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) { mp_recycle_value (mp, old_p); mp_free_node (mp, old_p, value_node_size); } if (old_exp != NULL) { mp_recycle_value (mp, old_exp); mp_free_node (mp, old_exp, value_node_size); } @ 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|@>= switch (mp_type (p)) { case mp_transform_type: case mp_color_type: case mp_cmykcolor_type: case mp_pair_type: old_p = mp_tarnished (mp, p); break; case mp_independent: old_p = MP_VOID; break; default: old_p = NULL; break; } if (old_p != NULL) { q = mp_stash_cur_exp (mp); old_p = p; mp_make_exp_copy (mp, old_p); p = mp_stash_cur_exp (mp); mp_unstash_cur_exp (mp, q); } @ @<Sidestep |independent| cases in the current expression@>= switch (mp->cur_exp.type) { case mp_transform_type: case mp_color_type: case mp_cmykcolor_type: case mp_pair_type: old_exp = mp_tarnished (mp, cur_exp_node ()); break; case mp_independent: old_exp = MP_VOID; break; default: old_exp = NULL; break; } if (old_exp != NULL) { old_exp = cur_exp_node (); mp_make_exp_copy (mp, old_exp); } @ @<Declare binary action...@>= static mp_node mp_tarnished (MP mp, mp_node p) { mp_node q; /* beginning of the big node */ mp_node r; /* moving value node pointer */ (void) mp; q = value_node (p); switch (mp_type (p)) { case mp_pair_type: r = x_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = y_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; break; case mp_color_type: r = red_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = green_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = blue_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; break; case mp_cmykcolor_type: r = cyan_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = magenta_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = yellow_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = black_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; break; case mp_transform_type: r = tx_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = ty_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = xx_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = xy_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = yx_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; r = yy_part_loc (q); if (mp_type (r) == mp_independent) return MP_VOID; break; default: /* there are no other valid cases, but please the compiler */ break; } return NULL; } @ @<Add or subtract the current expression from |p|@>= if ((mp->cur_exp.type < mp_color_type) || (mp_type (p) < mp_color_type)) { mp_bad_binary (mp, p, (quarterword) c); } else { if ((mp->cur_exp.type > mp_pair_type) && (mp_type (p) > mp_pair_type)) { mp_add_or_subtract (mp, p, NULL, (quarterword) c); } else { if (mp->cur_exp.type != mp_type (p)) { mp_bad_binary (mp, p, (quarterword) c); } else { q = value_node (p); r = value_node (cur_exp_node ()); switch (mp->cur_exp.type) { case mp_pair_type: mp_add_or_subtract (mp, x_part_loc (q), x_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, y_part_loc (q), y_part_loc (r), (quarterword) c); break; case mp_color_type: mp_add_or_subtract (mp, red_part_loc (q), red_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, green_part_loc (q), green_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, blue_part_loc (q), blue_part_loc (r), (quarterword) c); break; case mp_cmykcolor_type: mp_add_or_subtract (mp, cyan_part_loc (q), cyan_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, magenta_part_loc (q), magenta_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, yellow_part_loc (q), yellow_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, black_part_loc (q), black_part_loc (r), (quarterword) c); break; case mp_transform_type: mp_add_or_subtract (mp, tx_part_loc (q), tx_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, ty_part_loc (q), ty_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, xx_part_loc (q), xx_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, xy_part_loc (q), xy_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, yx_part_loc (q), yx_part_loc (r), (quarterword) c); mp_add_or_subtract (mp, yy_part_loc (q), yy_part_loc (r), (quarterword) c); break; default: /* there are no other valid cases, but please the compiler */ break; } } } } @ 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. @^overflow in arithmetic@> @<Declare binary action...@>= @<Declare the procedure called |dep_finish|@>; static void mp_add_or_subtract (MP mp, mp_node p, mp_node q, quarterword c) { mp_variable_type s, t; /* operand types */ mp_value_node r; /* dependency list traverser */ mp_value_node v = NULL; /* second operand value for dep lists */ integer vv = 0; /* second operand value for known values */ if (q == NULL) { t = mp->cur_exp.type; if (t < mp_dependent) vv = cur_exp_value (); else v = (mp_value_node) dep_list ((mp_value_node) cur_exp_node ()); } else { t = mp_type (q); if (t < mp_dependent) vv = value (q); else v = (mp_value_node) dep_list ((mp_value_node) q); } if (t == mp_known) { mp_value_node qq = (mp_value_node) q; if (c == minus) negate (vv); if (mp_type (p) == mp_known) { vv = mp_slow_add (mp, value (p), vv); if (q == NULL) set_cur_exp_value (vv); else set_value (q, vv); return; } /* Add a known value to the constant term of |dep_list(p)| */ r = (mp_value_node) dep_list ((mp_value_node) p); while (dep_info (r) != NULL) r = (mp_value_node) mp_link (r); set_dep_value (r, mp_slow_add (mp, dep_value (r), vv)); if (qq == NULL) { qq = mp_get_dep_node (mp); set_cur_exp_node ((mp_node) qq); mp->cur_exp.type = mp_type (p); mp_name_type (qq) = mp_capsule; q = (mp_node) qq; } set_dep_list (qq, dep_list ((mp_value_node) p)); mp_type (qq) = mp_type (p); set_prev_dep (qq, prev_dep ((mp_value_node) p)); mp_link (prev_dep ((mp_value_node) p)) = (mp_node) qq; mp_type (p) = mp_known; /* this will keep the recycler from collecting non-garbage */ } else { if (c == minus) mp_negate_dep_list (mp, v); @<Add operand |p| to the dependency list |v|@>; } } @ We prefer |dependent| lists to |mp_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 (mp_type (p) == mp_known) { /* Add the known |value(p)| to the constant term of |v| */ while (dep_info (v) != NULL) v = (mp_value_node) mp_link (v); set_dep_value (v, mp_slow_add (mp, value (p), dep_value (v))); } else { s = mp_type (p); r = (mp_value_node) dep_list ((mp_value_node) p); if (t == mp_dependent) { if (s == mp_dependent) { if (mp_max_coef (mp, r) + mp_max_coef (mp, v) < coef_bound) { v = mp_p_plus_q (mp, v, r, mp_dependent); goto DONE; } } /* |fix_needed| will necessarily be false */ t = mp_proto_dependent; v = mp_p_over_v (mp, v, unity, mp_dependent, mp_proto_dependent); } if (s == mp_proto_dependent) v = mp_p_plus_q (mp, v, r, mp_proto_dependent); else v = mp_p_plus_fq (mp, v, unity, r, mp_proto_dependent, mp_dependent); DONE: /* Output the answer, |v| (which might have become |known|) */ if (q != NULL) { mp_dep_finish (mp, v, (mp_value_node) q, t); } else { mp->cur_exp.type = t; mp_dep_finish (mp, v, NULL, t); } } @ 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|@>= static void mp_dep_finish (MP mp, mp_value_node v, mp_value_node q, quarterword t) { mp_value_node p; /* the destination */ scaled vv; /* the value, if it is |known| */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); if (q == NULL) p = (mp_value_node) cur_exp_node (); else p = q; set_dep_list (p, (mp_node) v); mp_type (p) = t; if (dep_info (v) == NULL) { vv = value (v); if (q == NULL) { new_expr.data.val = vv; mp_flush_cur_exp (mp, new_expr); } else { mp_recycle_value (mp, (mp_node) p); mp_type (q) = mp_known; set_value (q, vv); } } else if (q == NULL) { mp->cur_exp.type = t; } if (mp->fix_needed) mp_fix_dependencies (mp); } @ Let's turn now to the six basic relations of comparison. @<Additional cases of binary operators@>= case less_than: case less_or_equal: case greater_than: case greater_or_equal: case equal_to: case unequal_to: check_arith; /* at this point |arith_error| should be |false|? */ if ((mp->cur_exp.type > mp_pair_type) && (mp_type (p) > mp_pair_type)) { mp_add_or_subtract (mp, p, NULL, minus); /* |cur_exp:=(p)-cur_exp| */ } else if (mp->cur_exp.type != mp_type (p)) { mp_bad_binary (mp, p, (quarterword) c); goto DONE; } else if (mp->cur_exp.type == mp_string_type) { new_expr.data.val = mp_str_vs_str (mp, str_value (p), cur_exp_str ()); mp_flush_cur_exp (mp, new_expr); } else if ((mp->cur_exp.type == mp_unknown_string) || (mp->cur_exp.type == mp_unknown_boolean)) { @<Check if unknowns have been equated@>; } else if ((mp->cur_exp.type <= mp_pair_type) && (mp->cur_exp.type >= mp_transform_type)) { @<Reduce comparison of big nodes to comparison of scalars@>; } else if (mp->cur_exp.type == mp_boolean_type) { new_expr.data.val = cur_exp_value () - value (p); mp_flush_cur_exp (mp, new_expr); } else { mp_bad_binary (mp, p, (quarterword) c); goto DONE; } @<Compare the current expression with zero@>; DONE: mp->arith_error = false; /* ignore overflow in comparisons */ break; @ @<Compare the current expression with zero@>= if (mp->cur_exp.type != mp_known) { if (mp->cur_exp.type < mp_known) { mp_disp_err (mp, p, ""); help1 ("The quantities shown above have not been equated.") } 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...@>; new_expr.data.val = false_code; mp_put_get_flush_error (mp, new_expr); } else { switch (c) { case less_than: boolean_reset (cur_exp_value () < 0); break; case less_or_equal: boolean_reset (cur_exp_value () <= 0); break; case greater_than: boolean_reset (cur_exp_value () > 0); break; case greater_or_equal: boolean_reset (cur_exp_value () >= 0); break; case equal_to: boolean_reset (cur_exp_value () == 0); break; case unequal_to: boolean_reset (cur_exp_value () != 0); break; }; /* there are no other cases */ } mp->cur_exp.type = mp_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@>= { q = value_node (cur_exp_node ()); while ((q != cur_exp_node ()) && (q != p)) q = value_node (q); if (q == p) { set_cur_exp_node (NULL); mp_flush_cur_exp (mp, new_expr); } } @ In the following, the |while| loops exist just so that |break| can be used, each loop runs exactly once. @<Reduce comparison of big nodes to comparison of scalars@>= { quarterword part_type; q = value_node (p); r = value_node (cur_exp_node ()); part_type = 0; switch (mp->cur_exp.type) { case mp_pair_type: while (part_type==0) { rr = x_part_loc (r); part_type = x_part; mp_add_or_subtract (mp, x_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = y_part_loc (r); part_type = y_part; mp_add_or_subtract (mp, y_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; } mp_take_part (mp, part_type); break; case mp_color_type: while (part_type==0) { rr = red_part_loc (r); part_type = red_part; mp_add_or_subtract (mp, red_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = green_part_loc (r); part_type = green_part; mp_add_or_subtract (mp, green_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = blue_part_loc (r); part_type = blue_part; mp_add_or_subtract (mp, blue_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; } mp_take_part (mp, part_type); break; case mp_cmykcolor_type: while (part_type==0) { rr = cyan_part_loc (r); part_type = cyan_part; mp_add_or_subtract (mp, cyan_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = magenta_part_loc (r); part_type = magenta_part; mp_add_or_subtract (mp, magenta_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = yellow_part_loc (r); part_type = yellow_part; mp_add_or_subtract (mp, yellow_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = black_part_loc (r); part_type = black_part; mp_add_or_subtract (mp, black_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; } mp_take_part (mp, part_type); break; case mp_transform_type: while (part_type==0) { rr = tx_part_loc (r); part_type = x_part; mp_add_or_subtract (mp, tx_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = ty_part_loc (r); part_type = y_part; mp_add_or_subtract (mp, ty_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = xx_part_loc (r); part_type = xx_part; mp_add_or_subtract (mp, xx_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = xy_part_loc (r); part_type = xy_part; mp_add_or_subtract (mp, xy_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = yx_part_loc (r); part_type = yx_part; mp_add_or_subtract (mp, yx_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; rr = yy_part_loc (r); part_type = yy_part; mp_add_or_subtract (mp, yy_part_loc (q), rr, minus); if (mp_type (rr) != mp_known || value (rr) != 0) break; } mp_take_part (mp, part_type); break; default: assert (0); /* todo: |mp->cur_exp.type>mp_transform_node_type| ? */ break; } } @ Here we use the sneaky fact that |and_op-false_code=or_op-true_code|. @<Additional cases of binary operators@>= case and_op: case or_op: if ((mp_type (p) != mp_boolean_type) || (mp->cur_exp.type != mp_boolean_type)) mp_bad_binary (mp, p, (quarterword) c); else if (value (p) == c + false_code - and_op) set_cur_exp_value (value (p)); break; @ @<Additional cases of binary operators@>= case times: if ((mp->cur_exp.type < mp_color_type) || (mp_type (p) < mp_color_type)) { mp_bad_binary (mp, p, times); } else if ((mp->cur_exp.type == mp_known) || (mp_type (p) == mp_known)) { @<Multiply when at least one operand is known@>; } else if ((mp_nice_color_or_pair (mp, p, mp_type (p)) && (mp->cur_exp.type > mp_pair_type)) || (mp_nice_color_or_pair (mp, cur_exp_node (), mp->cur_exp.type) && (mp_type (p) > mp_pair_type))) { mp_hard_times (mp, p); binary_return; } else { mp_bad_binary (mp, p, times); } break; @ @<Multiply when at least one operand is known@>= { if (mp_type (p) == mp_known) { v = value (p); mp_free_node (mp, p, value_node_size); } else { v = cur_exp_value (); mp_unstash_cur_exp (mp, p); } if (mp->cur_exp.type == mp_known) { set_cur_exp_value (mp_take_scaled (mp, cur_exp_value (), v)); } else if (mp->cur_exp.type == mp_pair_type) { mp_dep_mult (mp, (mp_value_node) x_part_loc (value_node (cur_exp_node ())), v, true); mp_dep_mult (mp, (mp_value_node) y_part_loc (value_node (cur_exp_node ())), v, true); } else if (mp->cur_exp.type == mp_color_type) { mp_dep_mult (mp, (mp_value_node) red_part_loc (value_node (cur_exp_node ())), v, true); mp_dep_mult (mp, (mp_value_node) green_part_loc (value_node (cur_exp_node ())), v, true); mp_dep_mult (mp, (mp_value_node) blue_part_loc (value_node (cur_exp_node ())), v, true); } else if (mp->cur_exp.type == mp_cmykcolor_type) { mp_dep_mult (mp, (mp_value_node) cyan_part_loc (value_node (cur_exp_node ())), v, true); mp_dep_mult (mp, (mp_value_node) magenta_part_loc (value_node (cur_exp_node ())), v, true); mp_dep_mult (mp, (mp_value_node) yellow_part_loc (value_node (cur_exp_node ())), v, true); mp_dep_mult (mp, (mp_value_node) black_part_loc (value_node (cur_exp_node ())), v, true); } else { mp_dep_mult (mp, NULL, v, true); } binary_return; } @ @<Declare binary action...@>= static void mp_dep_mult (MP mp, mp_value_node p, integer v, boolean v_is_scaled) { mp_value_node q; /* the dependency list being multiplied by |v| */ quarterword s, t; /* its type, before and after */ if (p == NULL) { q = (mp_value_node) cur_exp_node (); } else if (mp_type (p) != mp_known) { q = p; } else { if (v_is_scaled) set_dep_value (p, mp_take_scaled (mp, dep_value (p), v)); else set_dep_value (p, mp_take_fraction (mp, dep_value (p), v)); return; }; t = mp_type (q); q = (mp_value_node) dep_list (q); s = t; if (t == mp_dependent) if (v_is_scaled) if (mp_ab_vs_cd (mp, mp_max_coef (mp, q), abs (v), coef_bound - 1, unity) >= 0) t = mp_proto_dependent; q = mp_p_times_v (mp, q, v, s, t, v_is_scaled); mp_dep_finish (mp, q, p, t); } @ 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>=mp_color_type|. @c static void mp_frac_mult (MP mp, scaled n, scaled d) { /* multiplies |cur_exp| by |n/d| */ mp_node old_exp; /* a capsule to recycle */ fraction v; /* |n/d| */ if (internal_value (mp_tracing_commands) > two) { @<Trace the fraction multiplication@>; } switch (mp->cur_exp.type) { case mp_transform_type: case mp_color_type: case mp_cmykcolor_type: case mp_pair_type: old_exp = mp_tarnished (mp, cur_exp_node ()); break; case mp_independent: old_exp = MP_VOID; break; default: old_exp = NULL; break; } if (old_exp != NULL) { old_exp = cur_exp_node (); mp_make_exp_copy (mp, old_exp); } v = mp_make_fraction (mp, n, d); if (mp->cur_exp.type == mp_known) { set_cur_exp_value (mp_take_fraction (mp, cur_exp_value (), v)); } else if (mp->cur_exp.type == mp_pair_type) { mp_dep_mult (mp, (mp_value_node) x_part_loc (value_node (cur_exp_node ())), v, false); mp_dep_mult (mp, (mp_value_node) y_part_loc (value_node (cur_exp_node ())), v, false); } else if (mp->cur_exp.type == mp_color_type) { mp_dep_mult (mp, (mp_value_node) red_part_loc (value_node (cur_exp_node ())), v, false); mp_dep_mult (mp, (mp_value_node) green_part_loc (value_node (cur_exp_node ())), v, false); mp_dep_mult (mp, (mp_value_node) blue_part_loc (value_node (cur_exp_node ())), v, false); } else if (mp->cur_exp.type == mp_cmykcolor_type) { mp_dep_mult (mp, (mp_value_node) cyan_part_loc (value_node (cur_exp_node ())), v, false); mp_dep_mult (mp, (mp_value_node) magenta_part_loc (value_node (cur_exp_node ())), v, false); mp_dep_mult (mp, (mp_value_node) yellow_part_loc (value_node (cur_exp_node ())), v, false); mp_dep_mult (mp, (mp_value_node) black_part_loc (value_node (cur_exp_node ())), v, false); } else { mp_dep_mult (mp, NULL, v, false); } if (old_exp != NULL) { mp_recycle_value (mp, old_exp); mp_free_node (mp, old_exp, value_node_size); } } @ @<Trace the fraction multiplication@>= { mp_begin_diagnostic (mp); mp_print_nl (mp, "{("); mp_print_scaled (mp, n); mp_print_char (mp, xord ('/')); mp_print_scaled (mp, d); mp_print (mp, ")*("); mp_print_exp (mp, NULL, 0); mp_print (mp, ")}"); mp_end_diagnostic (mp, false); } @ The |hard_times| routine multiplies a nice color or pair by a dependency list. @<Declare binary action procedures@>= static void mp_hard_times (MP mp, mp_node p) { mp_value_node q; /* a copy of the dependent variable |p| */ mp_value_node pp; /* for typecasting p */ mp_node r; /* a component of the big node for the nice color or pair */ scaled v; /* the known value for |r| */ if (mp_type (p) <= mp_pair_type) { q = (mp_value_node) mp_stash_cur_exp (mp); mp_unstash_cur_exp (mp, p); p = (mp_node) q; } /* now |cur_type=mp_pair_type| or |cur_type=mp_color_type| or |cur_type=mp_cmykcolor_type| */ pp = (mp_value_node) p; if (mp->cur_exp.type == mp_pair_type) { r = x_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); r = y_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); } else if (mp->cur_exp.type == mp_color_type) { r = red_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); r = green_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); r = blue_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); } else if (mp->cur_exp.type == mp_cmykcolor_type) { r = cyan_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); r = yellow_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); r = magenta_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); r = black_part_loc (value_node (cur_exp_node ())); v = value (r); mp_new_dep (mp, r, mp_type (pp), mp_copy_dep_list (mp, (mp_value_node) dep_list (pp))); mp_dep_mult (mp, (mp_value_node) r, v, true); } } @ @<Additional cases of binary operators@>= case over: if ((mp->cur_exp.type != mp_known) || (mp_type (p) < mp_color_type)) { mp_bad_binary (mp, p, over); } else { v = cur_exp_value (); mp_unstash_cur_exp (mp, p); if (v == 0) { @<Squeal about division by zero@>; } else { if (mp->cur_exp.type == mp_known) { set_cur_exp_value (mp_make_scaled (mp, cur_exp_value (), v)); } else if (mp->cur_exp.type == mp_pair_type) { mp_dep_div (mp, (mp_value_node) x_part_loc (value_node (cur_exp_node ())), v); mp_dep_div (mp, (mp_value_node) y_part_loc (value_node (cur_exp_node ())), v); } else if (mp->cur_exp.type == mp_color_type) { mp_dep_div (mp, (mp_value_node) red_part_loc (value_node (cur_exp_node ())), v); mp_dep_div (mp, (mp_value_node) green_part_loc (value_node (cur_exp_node ())), v); mp_dep_div (mp, (mp_value_node) blue_part_loc (value_node (cur_exp_node ())), v); } else if (mp->cur_exp.type == mp_cmykcolor_type) { mp_dep_div (mp, (mp_value_node) cyan_part_loc (value_node (cur_exp_node ())), v); mp_dep_div (mp, (mp_value_node) magenta_part_loc (value_node (cur_exp_node ())), v); mp_dep_div (mp, (mp_value_node) yellow_part_loc (value_node (cur_exp_node ())), v); mp_dep_div (mp, (mp_value_node) black_part_loc (value_node (cur_exp_node ())), v); } else { mp_dep_div (mp, NULL, v); } } binary_return; } break; @ @<Declare binary action...@>= static void mp_dep_div (MP mp, mp_value_node p, scaled v) { mp_value_node q; /* the dependency list being divided by |v| */ quarterword s, t; /* its type, before and after */ if (p == NULL) q = (mp_value_node) cur_exp_node (); else if (mp_type (p) != mp_known) q = p; else { set_value (p, mp_make_scaled (mp, value (p), v)); return; } t = mp_type (q); q = (mp_value_node) dep_list (q); s = t; if (t == mp_dependent) if (mp_ab_vs_cd (mp, mp_max_coef (mp, q), unity, coef_bound - 1, abs (v)) >= 0) t = mp_proto_dependent; q = mp_p_over_v (mp, q, v, s, t); mp_dep_finish (mp, q, p, t); } @ @<Squeal about division by zero@>= { 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."); mp_put_get_error (mp); } @ @<Additional cases of binary operators@>= case pythag_add: case pythag_sub: if ((mp->cur_exp.type == mp_known) && (mp_type (p) == mp_known)) { if (c == pythag_add) set_cur_exp_value (mp_pyth_add (mp, value (p), cur_exp_value ())); else set_cur_exp_value (mp_pyth_sub (mp, value (p), cur_exp_value ())); } else mp_bad_binary (mp, p, (quarterword) c); break; @ The next few sections of the program deal with affine transformations of coordinate data. @<Additional cases of binary operators@>= case rotated_by: case slanted_by: case scaled_by: case shifted_by: case transformed_by: case x_scaled: case y_scaled: case z_scaled: if (mp_type (p) == mp_path_type) { path_trans ((quarterword) c, p); binary_return; } else if (mp_type (p) == mp_pen_type) { pen_trans ((quarterword) c, p); set_cur_exp_knot (mp_convex_hull (mp, cur_exp_knot ())); /* rounding error could destroy convexity */ binary_return; } else if ((mp_type (p) == mp_pair_type) || (mp_type (p) == mp_transform_type)) { mp_big_trans (mp, p, (quarterword) c); } else if (mp_type (p) == mp_picture_type) { mp_do_edges_trans (mp, p, (quarterword) c); binary_return; } else { mp_bad_binary (mp, p, (quarterword) c); } break; @ 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...@>= static void mp_set_up_trans (MP mp, quarterword c) { mp_node p, q, r; /* list manipulation registers */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); if ((c != transformed_by) || (mp->cur_exp.type != mp_transform_type)) { @<Put the current transform into |cur_exp|@>; } @<If the current transform is entirely known, stash it in global variables; otherwise |return|@>; } @ @<Glob...@>= scaled txx; scaled txy; scaled tyx; scaled tyy; scaled tx; scaled ty; /* current transform coefficients */ @ @<Put the current transform...@>= { p = mp_stash_cur_exp (mp); set_cur_exp_node (mp_id_transform (mp)); mp->cur_exp.type = mp_transform_type; q = value_node (cur_exp_node ()); switch (c) { @<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@>; }; /* there are no other cases */ mp_disp_err (mp, 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."); mp_put_get_error (mp); DONE: mp_recycle_value (mp, p); mp_free_node (mp, p, value_node_size); } @ @<If the current transform is entirely known, ...@>= q = value_node (cur_exp_node ()); if (mp_type (tx_part_loc (q)) != mp_known) return; if (mp_type (ty_part_loc (q)) != mp_known) return; if (mp_type (xx_part_loc (q)) != mp_known) return; if (mp_type (xy_part_loc (q)) != mp_known) return; if (mp_type (yx_part_loc (q)) != mp_known) return; if (mp_type (yy_part_loc (q)) != mp_known) return; mp->txx = value (xx_part_loc (q)); mp->txy = value (xy_part_loc (q)); mp->tyx = value (yx_part_loc (q)); mp->tyy = value (yy_part_loc (q)); mp->tx = value (tx_part_loc (q)); mp->ty = value (ty_part_loc (q)); new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr) @ @<For each of the eight cases...@>= case rotated_by: if (mp_type (p) == mp_known) @<Install sines and cosines, then |goto done|@>; break; case slanted_by: if (mp_type (p) > mp_pair_type) { mp_install (mp, xy_part_loc (q), p); goto DONE; } break; case scaled_by: if (mp_type (p) > mp_pair_type) { mp_install (mp, xx_part_loc (q), p); mp_install (mp, yy_part_loc (q), p); goto DONE; } break; case shifted_by: if (mp_type (p) == mp_pair_type) { r = value_node (p); mp_install (mp, tx_part_loc (q), x_part_loc (r)); mp_install (mp, ty_part_loc (q), y_part_loc (r)); goto DONE; } break; case x_scaled: if (mp_type (p) > mp_pair_type) { mp_install (mp, xx_part_loc (q), p); goto DONE; } break; case y_scaled: if (mp_type (p) > mp_pair_type) { mp_install (mp, yy_part_loc (q), p); goto DONE; } break; case z_scaled: if (mp_type (p) == mp_pair_type) @<Install a complex multiplier, then |goto done|@>; break; case transformed_by: break; @ @<Install sines and cosines, then |goto done|@>= { fraction n_sin; fraction n_cos; mp_n_sin_cos (mp, (value (p) % three_sixty_units) * 16, &n_cos, &n_sin); set_value (xx_part_loc (q), mp_round_fraction (mp, n_cos)); set_value (yx_part_loc (q), mp_round_fraction (mp, n_sin)); set_value (xy_part_loc (q), -value (yx_part_loc (q))); set_value (yy_part_loc (q), value (xx_part_loc (q))); goto DONE; } @ @<Install a complex multiplier, then |goto done|@>= { r = value_node (p); mp_install (mp, xx_part_loc (q), x_part_loc (r)); mp_install (mp, yy_part_loc (q), x_part_loc (r)); mp_install (mp, yx_part_loc (q), y_part_loc (r)); if (mp_type (y_part_loc (r)) == mp_known) negate (value (y_part_loc (r))); else mp_negate_dep_list (mp, (mp_value_node) dep_list ((mp_value_node) y_part_loc (r))); mp_install (mp, xy_part_loc (q), y_part_loc (r)); goto DONE; } @ Procedure |set_up_known_trans| is like |set_up_trans|, but it insists that the transformation be entirely known. @<Declare binary action...@>= static void mp_set_up_known_trans (MP mp, quarterword c) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_set_up_trans (mp, c); if (mp->cur_exp.type != mp_known) { 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."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); mp->txx = unity; mp->txy = 0; mp->tyx = 0; mp->tyy = unity; mp->tx = 0; mp->ty = 0; } } @ Here's a procedure that applies the transform |txx..ty| to a pair of coordinates in locations |p| and~|q|. @<Declare binary action...@>= static void mp_trans (MP mp, scaled * p, scaled * q) { scaled v; /* the new |x| value */ v = mp_take_scaled (mp, *p, mp->txx) + mp_take_scaled (mp, *q, mp->txy) + mp->tx; *q = mp_take_scaled (mp, *p, mp->tyx) + mp_take_scaled (mp, *q, mp->tyy) + mp->ty; *p = v; } @ 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(A,B) { mp_set_up_known_trans(mp, (A)); mp_unstash_cur_exp(mp, (B)); mp_do_path_trans(mp, cur_exp_knot()); } @<Declare binary action...@>= static void mp_do_path_trans (MP mp, mp_knot p) { mp_knot q; /* list traverser */ q = p; do { if (mp_left_type (q) != mp_endpoint) mp_trans (mp, &mp_left_x (q), &mp_left_y (q)); mp_trans (mp, &mp_x_coord (q), &mp_y_coord (q)); if (mp_right_type (q) != mp_endpoint) mp_trans (mp, &mp_right_x (q), &mp_right_y (q)); q = mp_next_knot (q); } while (q != p); } @ Transforming a pen is very similar, except that there are no |mp_left_type| and |mp_right_type| fields. @d pen_trans(A,B) { mp_set_up_known_trans(mp, (A)); mp_unstash_cur_exp(mp, (B)); mp_do_pen_trans(mp, cur_exp_knot()); } @<Declare binary action...@>= static void mp_do_pen_trans (MP mp, mp_knot p) { mp_knot q; /* list traverser */ if (pen_is_elliptical (p)) { mp_trans (mp, &mp_left_x (p), &mp_left_y (p)); mp_trans (mp, &mp_right_x (p), &mp_right_y (p)); } q = p; do { mp_trans (mp, &mp_x_coord (q), &mp_y_coord (q)); q = mp_next_knot (q); } while (q != p); } @ 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...@>= static mp_node mp_edges_trans (MP mp, mp_node h) { mp_node q; /* the object being transformed */ mp_node r, s; /* for list manipulation */ scaled sx, sy; /* saved transformation parameters */ scaled sqdet; /* square root of determinant for |dash_scale| */ integer sgndet; /* sign of the determinant */ scaled v; /* a temporary value */ h = mp_private_edges (mp, h); sqdet = mp_sqrt_det (mp, mp->txx, mp->txy, mp->tyx, mp->tyy); sgndet = mp_ab_vs_cd (mp, mp->txx, mp->tyy, mp->txy, mp->tyx); if (dash_list (h) != mp->null_dash) { @<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 = mp_link (dummy_loc (h)); while (q != NULL) { @<Transform graphical object |q|@>; q = mp_link (q); } return h; } static void mp_do_edges_trans (MP mp, mp_node p, quarterword c) { mp_set_up_known_trans (mp, c); set_value_node (p, mp_edges_trans (mp, value_node (p))); mp_unstash_cur_exp (mp, p); } static void mp_scale_edges (MP mp) { mp->txx = mp->se_sf; mp->tyy = mp->se_sf; mp->txy = 0; mp->tyx = 0; mp->tx = 0; mp->ty = 0; mp->se_pic = mp_edges_trans (mp, mp->se_pic); } @ @<Try to transform the dash list of |h|@>= if ((mp->txy != 0) || (mp->tyx != 0) || (mp->ty != 0) || (abs (mp->txx) != abs (mp->tyy))) { mp_flush_dash_list (mp, h); } else { if (mp->txx < 0) { @<Reverse the dash list of |h|@>; } @<Scale the dash list by |txx| and shift it by |tx|@>; dash_y (h) = mp_take_scaled (mp, dash_y (h), abs (mp->tyy)); } @ @<Reverse the dash list of |h|@>= { r = dash_list (h); dash_list (h) = mp->null_dash; while (r != mp->null_dash) { s = r; r = mp_link (r); v = start_x (s); start_x (s) = stop_x (s); stop_x (s) = v; mp_link (s) = dash_list (h); dash_list (h) = s; } } @ @<Scale the dash list by |txx| and shift it by |tx|@>= r = dash_list (h); while (r != mp->null_dash) { start_x (r) = mp_take_scaled (mp, start_x (r), mp->txx) + mp->tx; stop_x (r) = mp_take_scaled (mp, stop_x (r), mp->txx) + mp->tx; r = mp_link (r); } @ @<Make the bounding box of |h| unknown if it can't be updated properly...@>= if ((mp->txx == 0) && (mp->tyy == 0)) { @<Swap the $x$ and $y$ parameters in the bounding box of |h|@>; } else if ((mp->txy != 0) || (mp->tyx != 0)) { mp_init_bbox (mp, h); goto DONE1; } if (minx_val (h) <= maxx_val (h)) { @<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|@>= { 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; } @ 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...@>= { minx_val (h) = mp_take_scaled (mp, minx_val (h), mp->txx + mp->txy) + mp->tx; maxx_val (h) = mp_take_scaled (mp, maxx_val (h), mp->txx + mp->txy) + mp->tx; miny_val (h) = mp_take_scaled (mp, miny_val (h), mp->tyx + mp->tyy) + mp->ty; maxy_val (h) = mp_take_scaled (mp, maxy_val (h), mp->tyx + mp->tyy) + mp->ty; if (mp->txx + mp->txy < 0) { v = minx_val (h); minx_val (h) = maxx_val (h); maxx_val (h) = v; } if (mp->tyx + mp->tyy < 0) { v = miny_val (h); miny_val (h) = maxy_val (h); maxy_val (h) = v; } } @ Now we ready for the main task of transforming the graphical objects in edge structure~|h|. @<Transform graphical object |q|@>= switch (mp_type (q)) { case mp_fill_node_type: { mp_fill_node qq = (mp_fill_node) q; mp_do_path_trans (mp, mp_path_p (qq)); @<Transform |mp_pen_p(qq)|, making sure polygonal pens stay counter-clockwise@>; } break; case mp_stroked_node_type: { mp_stroked_node qq = (mp_stroked_node) q; mp_do_path_trans (mp, mp_path_p (qq)); @<Transform |mp_pen_p(qq)|, making sure polygonal pens stay counter-clockwise@>; } break; case mp_start_clip_node_type: mp_do_path_trans (mp, mp_path_p ((mp_start_clip_node) q)); break; case mp_start_bounds_node_type: mp_do_path_trans (mp, mp_path_p ((mp_start_bounds_node) q)); break; case mp_text_node_type: @<Transform the compact transformation@>; break; case mp_stop_clip_node_type: case mp_stop_bounds_node_type: break; default: /* there are no other valid cases, but please the compiler */ break; } @ 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 |mp_dash_p(q)| since the \ps\ output procedures will try to compensate for the transformation we are applying to |mp_pen_p(q)|. Since this compensation is based on the square root of the determinant, |sqdet| is the appropriate factor. We pass the mptrap test only if |dash_scale| is not adjusted, nowadays (backend is changed?) @<Transform |mp_pen_p(qq)|, making sure...@>= if (mp_pen_p (qq) != NULL) { sx = mp->tx; sy = mp->ty; mp->tx = 0; mp->ty = 0; mp_do_pen_trans (mp, mp_pen_p (qq)); if (sqdet != 0 && ((mp_type (q) == mp_stroked_node_type) && (mp_dash_p (q) != NULL))) dash_scale (q) = mp_take_scaled (mp, dash_scale (q), sqdet); if (!pen_is_elliptical (mp_pen_p (qq))) if (sgndet < 0) mp_pen_p (qq) = mp_make_pen (mp, mp_copy_path (mp, mp_pen_p (qq)), true); /* this unreverses the pen */ mp->tx = sx; mp->ty = sy; } @ @<Transform the compact transformation@>= mp_trans (mp, &tx_val (q), &ty_val (q)); sx = mp->tx; sy = mp->ty; mp->tx = 0; mp->ty = 0; mp_trans (mp, &txx_val (q), &tyx_val (q)); mp_trans (mp, &txy_val (q), &tyy_val (q)); mp->tx = sx; mp->ty = sy @ The hard cases of transformation occur when big nodes are involved, and when some of their components are unknown. @<Declare binary action...@>= @<Declare subroutines needed by |big_trans|@>; static void mp_big_trans (MP mp, mp_node p, quarterword c) { mp_node q, r, pp, qq; /* list manipulation registers */ q = value_node (p); if (mp_type (q) == mp_pair_node_type) { if (mp_type (x_part_loc (q)) != mp_known || mp_type (y_part_loc (q)) != mp_known) { @<Transform an unknown big node and |return|@>; } } else { /* |mp_transform_type| */ if (mp_type (tx_part_loc (q)) != mp_known || mp_type (ty_part_loc (q)) != mp_known || mp_type (xx_part_loc (q)) != mp_known || mp_type (xy_part_loc (q)) != mp_known || mp_type (yx_part_loc (q)) != mp_known || mp_type (yy_part_loc (q)) != mp_known) { @<Transform an unknown big node and |return|@>; } } @<Transform a known big node@>; } /* node |p| will now be recycled by |do_binary| */ @ @<Transform an unknown big node and |return|@>= { mp_set_up_known_trans (mp, c); mp_make_exp_copy (mp, p); r = value_node (cur_exp_node ()); if (mp->cur_exp.type == mp_transform_type) { mp_bilin1 (mp, yy_part_loc (r), mp->tyy, xy_part_loc (q), mp->tyx, 0); mp_bilin1 (mp, yx_part_loc (r), mp->tyy, xx_part_loc (q), mp->tyx, 0); mp_bilin1 (mp, xy_part_loc (r), mp->txx, yy_part_loc (q), mp->txy, 0); mp_bilin1 (mp, xx_part_loc (r), mp->txx, yx_part_loc (q), mp->txy, 0); } mp_bilin1 (mp, y_part_loc (r), mp->tyy, x_part_loc (q), mp->tyx, mp->ty); mp_bilin1 (mp, x_part_loc (r), mp->txx, y_part_loc (q), mp->txy, mp->tx); return; } @ Let |p| point to a 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|@>= static void mp_bilin1 (MP mp, mp_node p, scaled t, mp_node q, scaled u, scaled delta) { if (t != unity) mp_dep_mult (mp, (mp_value_node) p, t, true); if (u != 0) { if (mp_type (q) == mp_known) { delta += mp_take_scaled (mp, value (q), u); } else { /* Ensure that |type(p)=mp_proto_dependent| */ if (mp_type (p) != mp_proto_dependent) { if (mp_type (p) == mp_known) { mp_new_dep (mp, p, mp_type (p), mp_const_dependency (mp, value (p))); } else { dep_list ((mp_value_node) p) = (mp_node) mp_p_times_v (mp, (mp_value_node) dep_list ((mp_value_node) p), unity, mp_dependent, mp_proto_dependent, true); } mp_type (p) = mp_proto_dependent; } dep_list ((mp_value_node) p) = (mp_node) mp_p_plus_fq (mp, (mp_value_node) dep_list ((mp_value_node) p), u, (mp_value_node) dep_list ((mp_value_node) q), mp_proto_dependent, mp_type (q)); } } if (mp_type (p) == mp_known) { set_value (p, value (p) + delta); } else { mp_value_node r; /* list traverser */ r = (mp_value_node) dep_list ((mp_value_node) p); while (dep_info (r) != NULL) r = (mp_value_node) mp_link (r); delta += value (r); if (r != (mp_value_node) dep_list ((mp_value_node) p)) set_value (r, delta); else { mp_recycle_value (mp, p); mp_type (p) = mp_known; set_value (p, delta); } } if (mp->fix_needed) mp_fix_dependencies (mp); } @ @<Transform a known big node@>= mp_set_up_trans (mp, c); if (mp->cur_exp.type == mp_known) { @<Transform known by known@>; } else { pp = mp_stash_cur_exp (mp); qq = value_node (pp); mp_make_exp_copy (mp, p); r = value_node (cur_exp_node ()); if (mp->cur_exp.type == mp_transform_type) { mp_bilin2 (mp, yy_part_loc (r), yy_part_loc (qq), value (xy_part_loc (q)), yx_part_loc (qq), NULL); mp_bilin2 (mp, yx_part_loc (r), yy_part_loc (qq), value (xx_part_loc (q)), yx_part_loc (qq), NULL); mp_bilin2 (mp, xy_part_loc (r), xx_part_loc (qq), value (yy_part_loc (q)), xy_part_loc (qq), NULL); mp_bilin2 (mp, xx_part_loc (r), xx_part_loc (qq), value (yx_part_loc (q)), xy_part_loc (qq), NULL); } mp_bilin2 (mp, y_part_loc (r), yy_part_loc (qq), value (x_part_loc (q)), yx_part_loc (qq), y_part_loc (qq)); mp_bilin2 (mp, x_part_loc (r), xx_part_loc (qq), value (y_part_loc (q)), xy_part_loc (qq), x_part_loc (qq)); mp_recycle_value (mp, pp); mp_free_node (mp, pp, value_node_size); } @ Let |p| be a |mp_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|@>= static void mp_add_mult_dep (MP mp, mp_value_node p, scaled v, mp_node r) { if (mp_type (r) == mp_known) { set_dep_value (mp->dep_final, dep_value (mp->dep_final) + mp_take_scaled (mp, value (r), v)); } else { dep_list (p) = (mp_node) mp_p_plus_fq (mp, (mp_value_node) dep_list (p), v, (mp_value_node) dep_list ((mp_value_node) r), mp_proto_dependent, mp_type (r)); if (mp->fix_needed) mp_fix_dependencies (mp); } } @ 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)=mp_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|@>= static void mp_bilin2 (MP mp, mp_node p, mp_node t, scaled v, mp_node u, mp_node q) { scaled vv; /* temporary storage for |value(p)| */ vv = value (p); mp_new_dep (mp, p, mp_proto_dependent, mp_const_dependency (mp, 0)); /* this sets |dep_final| */ if (vv != 0) mp_add_mult_dep (mp, (mp_value_node) p, vv, t); /* |dep_final| doesn't change */ if (v != 0) mp_add_mult_dep (mp, (mp_value_node) p, v, u); if (q != NULL) mp_add_mult_dep (mp, (mp_value_node) p, unity, q); if (dep_list ((mp_value_node) p) == (mp_node) mp->dep_final) { vv = dep_value (mp->dep_final); mp_recycle_value (mp, p); mp_type (p) = mp_known; set_value (p, vv); } } @ @<Transform known by known@>= { mp_make_exp_copy (mp, p); r = value_node (cur_exp_node ()); if (mp->cur_exp.type == mp_transform_type) { mp_bilin3 (mp, yy_part_loc (r), mp->tyy, value (xy_part_loc (q)), mp->tyx, 0); mp_bilin3 (mp, yx_part_loc (r), mp->tyy, value (xx_part_loc (q)), mp->tyx, 0); mp_bilin3 (mp, xy_part_loc (r), mp->txx, value (yy_part_loc (q)), mp->txy, 0); mp_bilin3 (mp, xx_part_loc (r), mp->txx, value (yx_part_loc (q)), mp->txy, 0); } mp_bilin3 (mp, y_part_loc (r), mp->tyy, value (x_part_loc (q)), mp->tyx, mp->ty); mp_bilin3 (mp, x_part_loc (r), mp->txx, value (y_part_loc (q)), mp->txy, mp->tx); } @ Finally, in |bilin3| everything is |known|. @<Declare subroutines needed by |big_trans|@>= static void mp_bilin3 (MP mp, mp_node p, scaled t, scaled v, scaled u, scaled delta) { if (t != unity) delta += mp_take_scaled (mp, value (p), t); else delta += value (p); if (u != 0) set_value (p, delta + mp_take_scaled (mp, v, u)); else set_value (p, delta); } @ @<Additional cases of binary operators@>= case concatenate: if ((mp->cur_exp.type == mp_string_type) && (mp_type (p) == mp_string_type)) mp_cat (mp, p); else mp_bad_binary (mp, p, concatenate); break; case substring_of: if (mp_nice_pair (mp, p, mp_type (p)) && (mp->cur_exp.type == mp_string_type)) mp_chop_string (mp, value_node (p)); else mp_bad_binary (mp, p, substring_of); break; case subpath_of: if (mp->cur_exp.type == mp_pair_type) mp_pair_to_path (mp); if (mp_nice_pair (mp, p, mp_type (p)) && (mp->cur_exp.type == mp_path_type)) mp_chop_path (mp, value_node (p)); else mp_bad_binary (mp, p, subpath_of); break; @ @<Declare binary action...@>= static void mp_cat (MP mp, mp_node p) { str_number a, b; /* the strings being concatenated */ size_t needed; size_t saved_cur_length = mp->cur_length; unsigned char *saved_cur_string = mp->cur_string; size_t saved_cur_string_size = mp->cur_string_size; mp->cur_length = 0; mp->cur_string = NULL; mp->cur_string_size = 0; a = str_value (p); b = cur_exp_str (); needed = length (a) + length (b); str_room (needed+1); (void) memcpy (mp->cur_string, a->str, a->len); (void) memcpy (mp->cur_string + a->len, b->str, b->len); mp->cur_length = needed; mp->cur_string[needed] = '\0'; set_cur_exp_str (mp_make_string (mp)); delete_str_ref (b); xfree(mp->cur_string); /* created by |mp_make_string| */ mp->cur_length = saved_cur_length; mp->cur_string = saved_cur_string; mp->cur_string_size = saved_cur_string_size; } @ @<Declare binary action...@>= static void mp_chop_string (MP mp, mp_node p) { integer a, b; /* start and stop points */ integer l; /* length of the original string */ integer k; /* runs from |a| to |b| */ str_number s; /* the original string */ boolean reversed; /* was |a>b|? */ a = mp_round_unscaled (mp, value (x_part_loc (p))); b = mp_round_unscaled (mp, value (y_part_loc (p))); if (a <= b) reversed = false; else { reversed = true; k = a; a = b; b = k; }; s = cur_exp_str (); l = (integer) length (s); if (a < 0) { a = 0; if (b < 0) b = 0; } if (b > l) { b = l; if (a > l) a = l; } str_room ((size_t) (b - a)); if (reversed) { for (k = b - 1; k >= a; k--) { append_char (*(s->str + k)); } } else { for (k = a; k < b; k++) { append_char (*(s->str + k)); } } set_cur_exp_str (mp_make_string (mp)); delete_str_ref (s); } @ @<Declare binary action...@>= static void mp_chop_path (MP mp, mp_node p) { mp_knot q; /* a knot in the original path */ mp_knot pp, qq, rr, ss; /* link variables for copies of path nodes */ scaled a, b, k, l; /* indices for chopping */ boolean reversed; /* was |a>b|? */ l = mp_path_length (mp); a = value (x_part_loc (p)); b = value (y_part_loc (p)); if (a <= b) { reversed = false; } else { reversed = true; k = a; a = b; b = k; } @<Dispense with the cases |a<0| and/or |b>l|@>; q = cur_exp_knot (); while (a >= unity) { q = mp_next_knot (q); a = a - unity; b = b - unity; } if (b == a) { @<Construct a path from |pp| to |qq| of length zero@>; } else { @<Construct a path from |pp| to |qq| of length $\lceil b\rceil$@>; } mp_left_type (pp) = mp_endpoint; mp_right_type (qq) = mp_endpoint; mp_next_knot (qq) = pp; mp_toss_knot_list (mp, cur_exp_knot ()); if (reversed) { set_cur_exp_knot (mp_next_knot (mp_htap_ypoc (mp, pp))); mp_toss_knot_list (mp, pp); } else { set_cur_exp_knot (pp); } } @ @<Dispense with the cases |a<0| and/or |b>l|@>= if (a < 0) { if (mp_left_type (cur_exp_knot ()) == mp_endpoint) { a = 0; if (b < 0) b = 0; } else { do { a = a + l; b = b + l; } while (a < 0); /* a cycle always has length |l>0| */ } } if (b > l) { if (mp_left_type (cur_exp_knot ()) == mp_endpoint) { b = l; if (a > l) a = l; } else { while (a >= l) { a = a - l; b = b - l; } } } @ @<Construct a path from |pp| to |qq| of length $\lceil b\rceil$@>= { pp = mp_copy_knot (mp, q); qq = pp; do { q = mp_next_knot (q); rr = qq; qq = mp_copy_knot (mp, q); mp_next_knot (rr) = qq; b = b - unity; } while (b > 0); if (a > 0) { ss = pp; pp = mp_next_knot (pp); mp_split_cubic (mp, ss, a * 010000); pp = mp_next_knot (ss); mp_xfree (ss); if (rr == ss) { b = mp_make_scaled (mp, b, unity - a); rr = pp; } } if (b < 0) { mp_split_cubic (mp, rr, (b + unity) * 010000); mp_xfree (qq); qq = mp_next_knot (rr); } } @ @<Construct a path from |pp| to |qq| of length zero@>= { if (a > 0) { mp_split_cubic (mp, q, a * 010000); q = mp_next_knot (q); } pp = mp_copy_knot (mp, q); qq = pp; } @ @<Additional cases of binary operators@>= case point_of: case precontrol_of: case postcontrol_of: if (mp->cur_exp.type == mp_pair_type) mp_pair_to_path (mp); if ((mp->cur_exp.type == mp_path_type) && (mp_type (p) == mp_known)) mp_find_point (mp, value (p), (quarterword) c); else mp_bad_binary (mp, p, (quarterword) c); break; case pen_offset_of: if ((mp->cur_exp.type == mp_pen_type) && mp_nice_pair (mp, p, mp_type (p))) mp_set_up_offset (mp, value_node (p)); else mp_bad_binary (mp, p, pen_offset_of); break; case direction_time_of: if (mp->cur_exp.type == mp_pair_type) mp_pair_to_path (mp); if ((mp->cur_exp.type == mp_path_type) && mp_nice_pair (mp, p, mp_type (p))) mp_set_up_direction_time (mp, value_node (p)); else mp_bad_binary (mp, p, direction_time_of); break; case envelope_of: if ((mp_type (p) != mp_pen_type) || (mp->cur_exp.type != mp_path_type)) mp_bad_binary (mp, p, envelope_of); else mp_set_up_envelope (mp, p); break; case glyph_infont: if ((mp_type (p) != mp_string_type && mp_type (p) != mp_known) || (mp->cur_exp.type != mp_string_type)) mp_bad_binary (mp, p, glyph_infont); else mp_set_up_glyph_infont (mp, p); break; break; @ @<Declare binary action...@>= static void mp_set_up_offset (MP mp, mp_node p) { mp_find_offset (mp, value (x_part_loc (p)), value (y_part_loc (p)), cur_exp_knot ()); mp_pair_value (mp, mp->cur_x, mp->cur_y); } static void mp_set_up_direction_time (MP mp, mp_node p) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); new_expr.data.val = mp_find_direction_time (mp, value (x_part_loc (p)), value (y_part_loc (p)), cur_exp_knot ()); mp_flush_cur_exp (mp, new_expr); } static void mp_set_up_envelope (MP mp, mp_node p) { quarterword ljoin, lcap; scaled miterlim; mp_knot q = mp_copy_path (mp, cur_exp_knot ()); /* the original path */ /* TODO: accept elliptical pens for straight paths */ if (pen_is_elliptical (knot_value (p))) { mp_bad_envelope_pen (mp); set_cur_exp_knot (q); mp->cur_exp.type = mp_path_type; return; } if (internal_value (mp_linejoin) > unity) ljoin = 2; else if (internal_value (mp_linejoin) > 0) ljoin = 1; else ljoin = 0; if (internal_value (mp_linecap) > unity) lcap = 2; else if (internal_value (mp_linecap) > 0) lcap = 1; else lcap = 0; if (internal_value (mp_miterlimit) < unity) miterlim = unity; else miterlim = internal_value (mp_miterlimit); set_cur_exp_knot (mp_make_envelope (mp, q, knot_value (p), ljoin, lcap, miterlim)); mp->cur_exp.type = mp_path_type; } @ This is pretty straightfoward. The one silly thing is that the output of |mp_ps_do_font_charstring| has to be un-exported. @<Declare binary action...@>= static void mp_set_up_glyph_infont (MP mp, mp_node p) { mp_edge_object *h = NULL; mp_ps_font *f = NULL; char *n = mp_str (mp, cur_exp_str ()); f = mp_ps_font_parse (mp, (int) mp_find_font (mp, n)); if (f != NULL) { if (mp_type (p) == mp_known) { int v = mp_round_unscaled (mp, value (p)); if (v < 0 || v > 255) { print_err ("glyph index too high ("); mp_print_int (mp, v); mp_print (mp, ")"); mp_error (mp); } else { h = mp_ps_font_charstring (mp, f, v); } } else { n = mp_str (mp, str_value (p)); h = mp_ps_do_font_charstring (mp, f, n); } mp_ps_font_free (mp, f); } if (h != NULL) { set_cur_exp_node (mp_gr_unexport (mp, h)); } else { set_cur_exp_node (mp_get_edge_header_node (mp)); mp_init_edges (mp, cur_exp_node ()); } mp->cur_exp.type = mp_picture_type; } @ @<Declare binary action...@>= static void mp_find_point (MP mp, scaled v, quarterword c) { mp_knot p; /* the path */ scaled n; /* its length */ p = cur_exp_knot (); if (mp_left_type (p) == mp_endpoint) n = -unity; else n = 0; do { p = mp_next_knot (p); n = n + unity; } while (p != cur_exp_knot ()); if (n == 0) { v = 0; } else if (v < 0) { if (mp_left_type (p) == mp_endpoint) v = 0; else v = n - 1 - ((-v - 1) % n); } else if (v > n) { if (mp_left_type (p) == mp_endpoint) v = n; else v = v % n; } p = cur_exp_knot (); while (v >= unity) { p = mp_next_knot (p); v = v - unity; } if (v != 0) { @<Insert a fractional node by splitting the cubic@>; } @<Set the current expression to the desired path coordinates@>; } @ @<Insert a fractional node...@>= { mp_split_cubic (mp, p, v * 010000); p = mp_next_knot (p); } @ @<Set the current expression to the desired path coordinates...@>= switch (c) { case point_of: mp_pair_value (mp, mp_x_coord (p), mp_y_coord (p)); break; case precontrol_of: if (mp_left_type (p) == mp_endpoint) mp_pair_value (mp, mp_x_coord (p), mp_y_coord (p)); else mp_pair_value (mp, mp_left_x (p), mp_left_y (p)); break; case postcontrol_of: if (mp_right_type (p) == mp_endpoint) mp_pair_value (mp, mp_x_coord (p), mp_y_coord (p)); else mp_pair_value (mp, mp_right_x (p), mp_right_y (p)); break; } /* there are no other cases */ @ @<Additional cases of binary operators@>= case arc_time_of: if (mp->cur_exp.type == mp_pair_type) mp_pair_to_path (mp); if ((mp->cur_exp.type == mp_path_type) && (mp_type (p) == mp_known)) { new_expr.data.val = mp_get_arc_time (mp, cur_exp_knot (), value (p)); mp_flush_cur_exp (mp, new_expr); } else { mp_bad_binary (mp, p, (quarterword) c); } break; @ @<Additional cases of bin...@>= case intersect: if (mp_type (p) == mp_pair_type) { q = mp_stash_cur_exp (mp); mp_unstash_cur_exp (mp, p); mp_pair_to_path (mp); p = mp_stash_cur_exp (mp); mp_unstash_cur_exp (mp, q); } if (mp->cur_exp.type == mp_pair_type) mp_pair_to_path (mp); if ((mp->cur_exp.type == mp_path_type) && (mp_type (p) == mp_path_type)) { mp_path_intersection (mp, knot_value (p), cur_exp_knot ()); mp_pair_value (mp, mp->cur_t, mp->cur_tt); } else { mp_bad_binary (mp, p, intersect); } break; @ @<Additional cases of bin...@>= case in_font: if ((mp->cur_exp.type != mp_string_type) || mp_type (p) != mp_string_type) { mp_bad_binary (mp, p, in_font); } else { mp_do_infont (mp, p); binary_return; } break; @ 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...@>= static void mp_do_infont (MP mp, mp_node p) { mp_node q; mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); q = mp_get_edge_header_node (mp); mp_init_edges (mp, q); mp_link (obj_tail (q)) = mp_new_text_node (mp, mp_str (mp, cur_exp_str ()), str_value (p)); obj_tail (q) = mp_link (obj_tail (q)); mp_free_node (mp, p, value_node_size); new_expr.data.node = q; mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_picture_type; } @* 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. @c void mp_do_statement (MP mp) { /* governs \MP's activities */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp->cur_exp.type = mp_vacuous; mp_get_x_next (mp); if (mp->cur_cmd > max_primary_command) { @<Worry about bad statement@>; } else if (mp->cur_cmd > max_statement_command) { @<Do an equation, assignment, title, or `$\langle\,$expression$\,\rangle\,$\&{endgroup}'@>; } else { @<Do a statement that doesn't begin with an expression@>; } if (mp->cur_cmd < semicolon) @<Flush unparsable junk that was found after the statement@>; mp->error_count = 0; } @ @<Declarations@>= @<Declare action procedures for use by |do_statement|@> @ 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@>= { if (mp->cur_cmd < semicolon) { print_err ("A statement can't begin with `"); @.A statement can't begin with x@>; mp_print_cmd_mod (mp, mp->cur_cmd, mp->cur_mod); mp_print_char (mp, xord ('\'')); 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@>; mp_back_error (mp); mp_get_x_next (mp); } } @ 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@>= { 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@>; mp_back_error (mp); mp->scanner_status = flushing; do { get_t_next (mp); @<Decrease the string reference count...@>; } while (!end_of_statement); /* |cur_cmd=semicolon|, |end_group|, or |stop| */ mp->scanner_status = normal; } @ If |do_statement| ends with |cur_cmd=end_group|, we should have |cur_type=mp_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...@>= { if (internal_value (mp_tracing_commands) > 0) show_cur_cmd_mod; switch (mp->cur_cmd) { case type_name: mp_do_type_declaration (mp); break; case macro_def: if (mp->cur_mod > var_def) mp_make_op_def (mp); else if (mp->cur_mod > end_def) mp_scan_def (mp); break; @<Cases of |do_statement| that invoke particular commands@>; } /* there are no other cases */ mp->cur_exp.type = mp_vacuous; } @ The most important statements begin with expressions. @<Do an equation, assignment, title, or...@>= { mp->var_flag = assignment; mp_scan_expression (mp); if (mp->cur_cmd < end_group) { if (mp->cur_cmd == equals) mp_do_equation (mp); else if (mp->cur_cmd == assignment) mp_do_assignment (mp); else if (mp->cur_exp.type == mp_string_type) { @<Do a title@>; } else if (mp->cur_exp.type != mp_vacuous) { 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."); mp_put_get_error (mp); } new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr); mp->cur_exp.type = mp_vacuous; } } @ @<Do a title@>= { if (internal_value (mp_tracing_titles) > 0) { mp_print_nl (mp, ""); mp_print_str (mp, cur_exp_str ()); update_terminal; } } @ 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). @<Declarations@>= @<Declare the procedure called |make_eq|@>; static void mp_do_equation (MP mp); @ @c void mp_do_equation (MP mp) { mp_node lhs; /* capsule for the left-hand side */ mp_node p; /* temporary register */ lhs = mp_stash_cur_exp (mp); mp_get_x_next (mp); mp->var_flag = assignment; mp_scan_expression (mp); if (mp->cur_cmd == equals) mp_do_equation (mp); else if (mp->cur_cmd == assignment) mp_do_assignment (mp); if (internal_value (mp_tracing_commands) > two) @<Trace the current equation@>; if (mp->cur_exp.type == mp_unknown_path) if (mp_type (lhs) == mp_pair_type) { p = mp_stash_cur_exp (mp); mp_unstash_cur_exp (mp, lhs); lhs = p; }; /* in this case |make_eq| will change the pair to a path */ mp_make_eq (mp, lhs); /* equate |lhs| to |(cur_type,cur_exp)| */ } @ And |do_assignment| is similar to |do_equation|: @<Declarations@>= static void mp_do_assignment (MP mp); @ @c void mp_do_assignment (MP mp) { mp_node lhs; /* token list for the left-hand side */ mp_node p; /* where the left-hand value is stored */ mp_node q; /* temporary capsule for the right-hand value */ if (mp->cur_exp.type != mp_token_list) { 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."); mp_error (mp); mp_do_equation (mp); } else { lhs = cur_exp_node (); mp->cur_exp.type = mp_vacuous; mp_get_x_next (mp); mp->var_flag = assignment; mp_scan_expression (mp); if (mp->cur_cmd == equals) mp_do_equation (mp); else if (mp->cur_cmd == assignment) mp_do_assignment (mp); if (internal_value (mp_tracing_commands) > two) @<Trace the current assignment@>; if (mp_name_type (lhs) == mp_internal_sym) { @<Assign the current expression to an internal variable@>; } else { @<Assign the current expression to the variable |lhs|@>; } mp_flush_node_list (mp, lhs); } } @ @<Trace the current equation@>= { mp_begin_diagnostic (mp); mp_print_nl (mp, "{("); mp_print_exp (mp, lhs, 0); mp_print (mp, ")=("); mp_print_exp (mp, NULL, 0); mp_print (mp, ")}"); mp_end_diagnostic (mp, false); } @ @<Trace the current assignment@>= { mp_begin_diagnostic (mp); mp_print_nl (mp, "{"); if (mp_name_type (lhs) == mp_internal_sym) mp_print (mp, internal_name (mp_sym_info (lhs))); else mp_show_token_list (mp, lhs, NULL, 1000, 0); mp_print (mp, ":="); mp_print_exp (mp, NULL, 0); mp_print_char (mp, xord ('}')); mp_end_diagnostic (mp, false); } @ @<Assign the current expression to an internal variable@>= if (mp->cur_exp.type == mp_known || mp->cur_exp.type == mp_string_type) { if (mp->cur_exp.type == mp_string_type) { if (internal_type (mp_sym_info (lhs)) != mp->cur_exp.type) { exp_err ("Internal quantity `"); @.Internal quantity...@>; mp_print (mp, internal_name (mp_sym_info (lhs))); mp_print (mp, "' must receive a known numeric value"); help2 ("I can\'t set this internal quantity to anything but a known", "numeric value, so I'll have to ignore this assignment."); mp_put_get_error (mp); } else { add_str_ref (cur_exp_str ()); internal_string (mp_sym_info (lhs)) = cur_exp_str (); } } else { /* |mp_known| */ if (internal_type (mp_sym_info (lhs)) != mp->cur_exp.type) { exp_err ("Internal quantity `"); @.Internal quantity...@>; mp_print (mp, internal_name (mp_sym_info (lhs))); mp_print (mp, "' must receive a known string"); help2 ("I can\'t set this internal quantity to anything but a known", "string, so I'll have to ignore this assignment."); mp_put_get_error (mp); } else { internal_value (mp_sym_info (lhs)) = cur_exp_value (); } } } else { exp_err ("Internal quantity `"); @.Internal quantity...@>; mp_print (mp, internal_name (mp_sym_info (lhs))); mp_print (mp, "' must receive a known numeric or string"); help2 ("I can\'t set an internal quantity to anything but a known string", "or known numeric value, so I'll have to ignore this assignment."); mp_put_get_error (mp); } @ @<Assign the current expression to the variable |lhs|@>= { p = mp_find_variable (mp, lhs); if (p != NULL) { q = mp_stash_cur_exp (mp); mp->cur_exp.type = mp_und_type (mp, p); mp_recycle_value (mp, p); mp_type (p) = mp->cur_exp.type; set_value (p, 0); /* todo: this was |null| */ mp_make_exp_copy (mp, p); p = mp_stash_cur_exp (mp); mp_unstash_cur_exp (mp, q); mp_make_eq (mp, p); } else { mp_obliterated (mp, lhs); mp_put_get_error (mp); } } @ 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|@>= static void mp_make_eq (MP mp, mp_node lhs); @ @c void mp_make_eq (MP mp, mp_node lhs) { mp_value new_expr; mp_variable_type t; /* type of the left-hand side */ mp_node p; /* pointer inside of big nodes */ integer v = 0; /* value of the left-hand side */ memset(&new_expr,0,sizeof(mp_value)); RESTART: t = mp_type (lhs); if (t <= mp_pair_type) v = value (lhs); switch (t) { @<For each type |t|, make an equation and |goto done| unless |cur_type| is incompatible with~|t|@>; default: /* there are no other valid cases, but please the compiler */ break; } /* all valid cases have been listed */ @<Announce that the equation cannot be performed@>; DONE: check_arith; mp_recycle_value (mp, lhs); mp_free_node (mp, lhs, value_node_size); } @ @<Announce that the equation cannot be performed@>= mp_disp_err (mp, lhs, ""); exp_err ("Equation cannot be performed ("); @.Equation cannot be performed@>; if (mp_type (lhs) <= mp_pair_type) mp_print_type (mp, mp_type (lhs)); else mp_print (mp, "numeric"); mp_print_char (mp, xord ('=')); if (mp->cur_exp.type <= mp_pair_type) mp_print_type (mp, mp->cur_exp.type); else mp_print (mp, "numeric"); mp_print_char (mp, xord (')')); help2 ("I'm sorry, but I don't know how to make such things equal.", "(See the two expressions just above the error message.)"); mp_put_get_error (mp) @ @<For each type |t|, make an equation and |goto done| unless...@>= case mp_boolean_type: case mp_string_type: case mp_pen_type: case mp_path_type: case mp_picture_type: if (mp->cur_exp.type == t + unknown_tag) { new_expr.data.val = v; mp_nonlinear_eq (mp, new_expr, cur_exp_node (), false); mp_unstash_cur_exp (mp, cur_exp_node ()); goto DONE; } else if (mp->cur_exp.type == t) { @<Report redundant or inconsistent equation and |goto done|@>; } break; case unknown_types: if (mp->cur_exp.type == t - unknown_tag) { mp_nonlinear_eq (mp, mp->cur_exp, lhs, true); goto DONE; } else if (mp->cur_exp.type == t) { mp_ring_merge (mp, lhs, cur_exp_node ()); goto DONE; } else if (mp->cur_exp.type == mp_pair_type) { if (t == mp_unknown_path) { mp_pair_to_path (mp); goto RESTART; } } break; case mp_transform_type: case mp_color_type: case mp_cmykcolor_type: case mp_pair_type: if (mp->cur_exp.type == t) { @<Do multiple equations and |goto done|@>; } break; case mp_known: case mp_dependent: case mp_proto_dependent: case mp_independent: if (mp->cur_exp.type >= mp_known) { mp_try_eq (mp, lhs, NULL); goto DONE; } break; case mp_vacuous: break; @ @<Report redundant or inconsistent equation and |goto done|@>= { if (mp->cur_exp.type <= mp_string_type) { if (mp->cur_exp.type == mp_string_type) { if (mp_str_vs_str (mp, str_value (lhs), cur_exp_str ()) != 0) { goto NOT_FOUND; } } else if (v != cur_exp_value ()) { goto NOT_FOUND; } @<Exclaim about a redundant equation@>; goto DONE; } 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."); mp_put_get_error (mp); 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."); mp_put_get_error (mp); goto DONE; } @ @<Do multiple equations and |goto done|@>= { p = value_node (lhs); switch (t) { case mp_transform_type: mp_try_eq (mp, yy_part_loc (p), yy_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, yx_part_loc (p), yx_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, xy_part_loc (p), xy_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, xx_part_loc (p), xx_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, ty_part_loc (p), ty_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, tx_part_loc (p), tx_part_loc (value_node (cur_exp_node ()))); break; case mp_color_type: mp_try_eq (mp, blue_part_loc (p), blue_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, green_part_loc (p), green_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, red_part_loc (p), red_part_loc (value_node (cur_exp_node ()))); break; case mp_cmykcolor_type: mp_try_eq (mp, black_part_loc (p), black_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, yellow_part_loc (p), yellow_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, magenta_part_loc (p), magenta_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, cyan_part_loc (p), cyan_part_loc (value_node (cur_exp_node ()))); break; case mp_pair_type: mp_try_eq (mp, y_part_loc (p), y_part_loc (value_node (cur_exp_node ()))); mp_try_eq (mp, x_part_loc (p), x_part_loc (value_node (cur_exp_node ()))); break; default: /* there are no other valid cases, but please the compiler */ break; } goto DONE; } @ 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. @<Declarations@>= static void mp_try_eq (MP mp, mp_node l, mp_node r); @ @c void mp_try_eq (MP mp, mp_node l, mp_node r) { mp_value_node p; /* dependency list for right operand minus left operand */ mp_variable_type t; /* the type of list |p| */ mp_value_node q; /* the constant term of |p| is here */ mp_value_node pp; /* dependency list for right operand */ mp_variable_type tt; /* the type of list |pp| */ boolean copied; /* have we copied a list that ought to be recycled? */ @<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 (dep_info (p) == NULL) { @<Deal with redundant or inconsistent equation@>; } else { mp_linear_eq (mp, p, (quarterword) t); if (r == NULL && mp->cur_exp.type != mp_known) { if (mp_type (cur_exp_node ()) == mp_known) { mp_node pp = cur_exp_node (); set_cur_exp_value (value (pp)); mp->cur_exp.type = mp_known; mp_free_node (mp, pp, value_node_size); } } } } @ @<Remove the left operand from its container, negate it, and...@>= t = mp_type (l); if (t == mp_known) { t = mp_dependent; p = mp_const_dependency (mp, -value (l)); q = p; } else if (t == mp_independent) { t = mp_dependent; p = mp_single_dependency (mp, l); set_dep_value (p, -dep_value (p)); q = mp->dep_final; } else { mp_value_node ll = (mp_value_node) l; p = (mp_value_node) dep_list (ll); q = p; while (1) { set_dep_value (q, -dep_value (q)); if (dep_info (q) == NULL) break; q = (mp_value_node) mp_link (q); } mp_link (prev_dep (ll)) = mp_link (q); set_prev_dep ((mp_value_node) mp_link (q), prev_dep (ll)); mp_type (ll) = mp_known; } @ @<Deal with redundant or inconsistent equation@>= { if (abs (value (p)) > 64) { /* off by .001 or more */ print_err ("Inconsistent equation"); @.Inconsistent equation@>; mp_print (mp, " (off by "); mp_print_scaled (mp, value (p)); mp_print_char (mp, xord (')')); help2 ("The equation I just read contradicts what was said before.", "But don't worry; continue and I'll just ignore it."); mp_put_get_error (mp); } else if (r == NULL) { @<Exclaim about a redundant equation@>; } mp_free_dep_node (mp, p); } @ @<Add the right operand to list |p|@>= if (r == NULL) { if (mp->cur_exp.type == mp_known) { set_value (q, value (q) + cur_exp_value ()); goto DONE1; } else { tt = mp->cur_exp.type; if (tt == mp_independent) pp = mp_single_dependency (mp, cur_exp_node ()); else pp = (mp_value_node) dep_list ((mp_value_node) cur_exp_node ()); } } else { if (mp_type (r) == mp_known) { set_dep_value (q, dep_value (q) + value (r)); goto DONE1; } else { tt = mp_type (r); if (tt == mp_independent) pp = mp_single_dependency (mp, r); else pp = (mp_value_node) dep_list ((mp_value_node) r); } } if (tt != mp_independent) { copied = false; } else { copied = true; tt = mp_dependent; } @<Add dependency list |pp| of type |tt| to dependency list~|p| of type~|t|@>; if (copied) mp_flush_node_list (mp, (mp_node) pp); DONE1: @ @<Add dependency list |pp| of type |tt| to dependency list~|p| of type~|t|@>= mp->watch_coefs = false; if (t == tt) { p = mp_p_plus_q (mp, p, pp, (quarterword) t); } else if (t == mp_proto_dependent) { p = mp_p_plus_fq (mp, p, unity, pp, mp_proto_dependent, mp_dependent); } else { q = p; while (dep_info (q) != NULL) { set_dep_value (q, mp_round_fraction (mp, dep_value (q))); q = (mp_value_node) mp_link (q); } t = mp_proto_dependent; p = mp_p_plus_q (mp, p, pp, (quarterword) t); } mp->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|. @<Declarations@>= static mp_node mp_scan_declared_variable (MP mp); @ @c mp_node mp_scan_declared_variable (MP mp) { mp_sym x; /* hash address of the variable's root */ mp_node h, t; /* head and tail of the token list to be returned */ mp_get_symbol (mp); x = mp->cur_sym; if (mp->cur_cmd != tag_token) mp_clear_symbol (mp, x, false); h = mp_get_symbolic_node (mp); set_mp_sym_sym (h, x); t = h; while (1) { mp_get_x_next (mp); if (mp->cur_sym == NULL) break; if (mp->cur_cmd != tag_token) if (mp->cur_cmd != internal_quantity) { if (mp->cur_cmd == left_bracket) { @<Descend past a collective subscript@>; } else { break; } } mp_link (t) = mp_get_symbolic_node (mp); t = mp_link (t); set_mp_sym_sym (t, mp->cur_sym); mp_name_type (t) = mp->cur_sym_mod; } if ((eq_type (x) % outer_tag) != tag_token) mp_clear_symbol (mp, x, false); if (equiv_node (x) == NULL) mp_new_root (mp, x); return h; } @ If the subscript isn't collective, we don't accept it as part of the declared variable. @<Descend past a collective subscript@>= { mp_sym ll = mp->cur_sym; /* hash address of left bracket */ mp_get_x_next (mp); if (mp->cur_cmd != right_bracket) { mp_back_input (mp); mp->cur_sym = ll; mp->cur_cmd = left_bracket; break; } else { mp->cur_sym = collective_subscript; } } @ Type declarations are introduced by the following primitive operations. @<Put each...@>= mp_primitive (mp, "numeric", type_name, mp_numeric_type); @:numeric_}{\&{numeric} primitive@>; mp_primitive (mp, "string", type_name, mp_string_type); @:string_}{\&{string} primitive@>; mp_primitive (mp, "boolean", type_name, mp_boolean_type); @:boolean_}{\&{boolean} primitive@>; mp_primitive (mp, "path", type_name, mp_path_type); @:path_}{\&{path} primitive@>; mp_primitive (mp, "pen", type_name, mp_pen_type); @:pen_}{\&{pen} primitive@>; mp_primitive (mp, "picture", type_name, mp_picture_type); @:picture_}{\&{picture} primitive@>; mp_primitive (mp, "transform", type_name, mp_transform_type); @:transform_}{\&{transform} primitive@>; mp_primitive (mp, "color", type_name, mp_color_type); @:color_}{\&{color} primitive@>; mp_primitive (mp, "rgbcolor", type_name, mp_color_type); @:color_}{\&{rgbcolor} primitive@>; mp_primitive (mp, "cmykcolor", type_name, mp_cmykcolor_type); @:color_}{\&{cmykcolor} primitive@>; mp_primitive (mp, "pair", type_name, mp_pair_type); @:pair_}{\&{pair} primitive@> @ @<Cases of |print_cmd...@>= case type_name: mp_print_type (mp, (quarterword) m); break; @ 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|@>= static void mp_do_type_declaration (MP mp); @ @c void mp_do_type_declaration (MP mp) { quarterword t; /* the type being declared */ mp_node p; /* token list for a declared variable */ mp_node q; /* value node for the variable */ if (mp->cur_mod >= mp_transform_type) t = (quarterword) mp->cur_mod; else t = (quarterword) (mp->cur_mod + unknown_tag); do { p = mp_scan_declared_variable (mp); mp_flush_variable (mp, equiv_node (mp_sym_sym (p)), mp_link (p), false); q = mp_find_variable (mp, p); if (q != NULL) { mp_type (q) = t; set_value (q, 0); /* todo: this was |null| */ } else { 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."); mp_put_get_error (mp); } mp_flush_node_list (mp, p); if (mp->cur_cmd < comma) { @<Flush spurious symbols after the declared variable@>; } } while (!end_of_statement); } @ @<Flush spurious symbols after the declared variable@>= { 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 (mp->cur_cmd == numeric_token) mp->help_line[2] = "Explicit subscripts like `x15a' aren't permitted."; mp_put_get_error (mp); mp->scanner_status = flushing; do { get_t_next (mp); @<Decrease the string reference count...@>; } while (mp->cur_cmd < comma); /* either |end_of_statement| or |cur_cmd=comma| */ mp->scanner_status = normal; } @ \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|. @c static void mp_main_control (MP mp) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); do { mp_do_statement (mp); if (mp->cur_cmd == end_group) { print_err ("Extra `endgroup'"); @.Extra `endgroup'@>; help2 ("I'm not currently working on a `begingroup',", "so I had better not try to end anything."); mp_flush_error (mp, new_expr); } } while (mp->cur_cmd != stop); } int mp_run (MP mp) { if (mp->history < mp_fatal_error_stop) { xfree (mp->jump_buf); mp->jump_buf = malloc (sizeof (jmp_buf)); if (mp->jump_buf == NULL || setjmp (*(mp->jump_buf)) != 0) return mp->history; mp_main_control (mp); /* come to life */ mp_final_cleanup (mp); /* prepare for death */ mp_close_files_and_terminate (mp); } return mp->history; } @ This function allows setting of internals from an external source (like the command line or a controlling application). It accepts two |char *|'s, even for numeric assignments when it calls |atoi| to get an integer from the start of the string. @c void mp_set_internal (MP mp, char *n, char *v, int isstring) { size_t l = strlen (n); char err[256]; const char *errid = NULL; if (l > 0) { mp_sym p = mp_id_lookup (mp, n, l, false); if (p == NULL) { errid = "variable does not exist"; } else { if (eq_type (p) == internal_quantity) { if ((internal_type (equiv (p)) == mp_string_type) && (isstring)) { internal_string (equiv (p)) = mp_rts (mp, v); } else if ((internal_type (equiv (p)) == mp_known) && (!isstring)) { scaled test = (scaled) atoi (v); if (test > 16383) { errid = "value is too large"; } else if (test < -16383) { errid = "value is too small"; } else { internal_value (equiv (p)) = test * unity; } } else { errid = "value has the wrong type"; } } else { errid = "variable is not an internal"; } } } if (errid != NULL) { if (isstring) { mp_snprintf (err, 256, "%s=\"%s\": %s, assignment ignored.", n, v, errid); } else { mp_snprintf (err, 256, "%s=%d: %s, assignment ignored.", n, atoi (v), errid); } mp_warn (mp, err); } } @ @<Exported function headers@>= void mp_set_internal (MP mp, char *n, char *v, int isstring); @ For |mp_execute|, we need to define a structure to store the redirected input and output. This structure holds the five relevant streams: the three informational output streams, the PostScript generation stream, and the input stream. These streams have many things in common, so it makes sense to give them their own structure definition. \item{fptr} is a virtual file pointer \item{data} is the data this stream holds \item{cur} is a cursor pointing into |data| \item{size} is the allocated length of the data stream \item{used} is the actual length of the data stream There are small differences between input and output: |term_in| never uses |used|, whereas the other four never use |cur|. @<Exported types@>= typedef struct { void *fptr; char *data; char *cur; size_t size; size_t used; } mp_stream; typedef struct { mp_stream term_out; mp_stream error_out; mp_stream log_out; mp_stream ps_out; mp_stream term_in; struct mp_edge_object *edges; } mp_run_data; @ We need a function to clear an output stream, this is called at the beginning of |mp_execute|. We also need one for destroying an output stream, this is called just before a stream is (re)opened. @c static void mp_reset_stream (mp_stream * str) { xfree (str->data); str->cur = NULL; str->size = 0; str->used = 0; } static void mp_free_stream (mp_stream * str) { xfree (str->fptr); mp_reset_stream (str); } @ @<Declarations@>= static void mp_reset_stream (mp_stream * str); static void mp_free_stream (mp_stream * str); @ The global instance contains a pointer instead of the actual structure even though it is essentially static, because that makes it is easier to move the object around. @<Global ...@>= mp_run_data run_data; @ Another type is needed: the indirection will overload some of the file pointer objects in the instance (but not all). For clarity, an indirect object is used that wraps a |FILE *|. @<Types ... @>= typedef struct File { FILE *f; } File; @ Here are all of the functions that need to be overloaded for |mp_execute|. @<Declarations@>= static void *mplib_open_file (MP mp, const char *fname, const char *fmode, int ftype); static int mplib_get_char (void *f, mp_run_data * mplib_data); static void mplib_unget_char (void *f, mp_run_data * mplib_data, int c); static char *mplib_read_ascii_file (MP mp, void *ff, size_t * size); static void mplib_write_ascii_file (MP mp, void *ff, const char *s); static void mplib_read_binary_file (MP mp, void *ff, void **data, size_t * size); static void mplib_write_binary_file (MP mp, void *ff, void *s, size_t size); static void mplib_close_file (MP mp, void *ff); static int mplib_eof_file (MP mp, void *ff); static void mplib_flush_file (MP mp, void *ff); static void mplib_shipout_backend (MP mp, void *h); @ The |xmalloc(1,1)| calls make sure the stored indirection values are unique. @d reset_stream(a) do { mp_reset_stream(&(a)); if (!ff->f) { ff->f = xmalloc(1,1); (a).fptr = ff->f; } } while (0) @c static void *mplib_open_file (MP mp, const char *fname, const char *fmode, int ftype) { File *ff = xmalloc (1, sizeof (File)); mp_run_data *run = mp_rundata (mp); ff->f = NULL; if (ftype == mp_filetype_terminal) { if (fmode[0] == 'r') { if (!ff->f) { ff->f = xmalloc (1, 1); run->term_in.fptr = ff->f; } } else { reset_stream (run->term_out); } } else if (ftype == mp_filetype_error) { reset_stream (run->error_out); } else if (ftype == mp_filetype_log) { reset_stream (run->log_out); } else if (ftype == mp_filetype_postscript) { mp_free_stream (&(run->ps_out)); ff->f = xmalloc (1, 1); run->ps_out.fptr = ff->f; } else { char realmode[3]; char *f = (mp->find_file) (mp, fname, fmode, ftype); if (f == NULL) return NULL; realmode[0] = *fmode; realmode[1] = 'b'; realmode[2] = 0; ff->f = fopen (f, realmode); free (f); if ((fmode[0] == 'r') && (ff->f == NULL)) { free (ff); return NULL; } } return ff; } static int mplib_get_char (void *f, mp_run_data * run) { int c; if (f == run->term_in.fptr && run->term_in.data != NULL) { if (run->term_in.size == 0) { if (run->term_in.cur != NULL) { run->term_in.cur = NULL; } else { xfree (run->term_in.data); } c = EOF; } else { run->term_in.size--; c = *(run->term_in.cur)++; } } else { c = fgetc (f); } return c; } static void mplib_unget_char (void *f, mp_run_data * run, int c) { if (f == run->term_in.fptr && run->term_in.cur != NULL) { run->term_in.size++; run->term_in.cur--; } else { ungetc (c, f); } } static char *mplib_read_ascii_file (MP mp, void *ff, size_t * size) { char *s = NULL; if (ff != NULL) { int c; size_t len = 0, lim = 128; mp_run_data *run = mp_rundata (mp); FILE *f = ((File *) ff)->f; if (f == NULL) return NULL; *size = 0; c = mplib_get_char (f, run); if (c == EOF) return NULL; s = malloc (lim); if (s == NULL) return NULL; while (c != EOF && c != '\n' && c != '\r') { if (len >= (lim - 1)) { s = xrealloc (s, (lim + (lim >> 2)), 1); if (s == NULL) return NULL; lim += (lim >> 2); } s[len++] = (char) c; c = mplib_get_char (f, run); } if (c == '\r') { c = mplib_get_char (f, run); if (c != EOF && c != '\n') mplib_unget_char (f, run, c); } s[len] = 0; *size = len; } return s; } static void mp_append_string (MP mp, mp_stream * a, const char *b) { size_t l = strlen (b); if ((a->used + l) >= a->size) { a->size += 256 + (a->size) / 5 + l; a->data = xrealloc (a->data, a->size, 1); } memcpy (a->data + a->used, b, l); a->used += l; } static void mplib_write_ascii_file (MP mp, void *ff, const char *s) { if (ff != NULL) { void *f = ((File *) ff)->f; mp_run_data *run = mp_rundata (mp); if (f != NULL) { if (f == run->term_out.fptr) { mp_append_string (mp, &(run->term_out), s); } else if (f == run->error_out.fptr) { mp_append_string (mp, &(run->error_out), s); } else if (f == run->log_out.fptr) { mp_append_string (mp, &(run->log_out), s); } else if (f == run->ps_out.fptr) { mp_append_string (mp, &(run->ps_out), s); } else { fprintf ((FILE *) f, "%s", s); } } } } static void mplib_read_binary_file (MP mp, void *ff, void **data, size_t * size) { (void) mp; if (ff != NULL) { size_t len = 0; FILE *f = ((File *) ff)->f; if (f != NULL) len = fread (*data, 1, *size, f); *size = len; } } static void mplib_write_binary_file (MP mp, void *ff, void *s, size_t size) { (void) mp; if (ff != NULL) { FILE *f = ((File *) ff)->f; if (f != NULL) (void) fwrite (s, size, 1, f); } } static void mplib_close_file (MP mp, void *ff) { if (ff != NULL) { mp_run_data *run = mp_rundata (mp); void *f = ((File *) ff)->f; if (f != NULL) { if (f != run->term_out.fptr && f != run->error_out.fptr && f != run->log_out.fptr && f != run->ps_out.fptr && f != run->term_in.fptr) { fclose (f); } } free (ff); } } static int mplib_eof_file (MP mp, void *ff) { if (ff != NULL) { mp_run_data *run = mp_rundata (mp); FILE *f = ((File *) ff)->f; if (f == NULL) return 1; if (f == run->term_in.fptr && run->term_in.data != NULL) { return (run->term_in.size == 0); } return feof (f); } return 1; } static void mplib_flush_file (MP mp, void *ff) { (void) mp; (void) ff; return; } static void mplib_shipout_backend (MP mp, void *voidh) { mp_node h = (mp_node) voidh; mp_edge_object *hh = mp_gr_export (mp, h); if (hh) { mp_run_data *run = mp_rundata (mp); if (run->edges == NULL) { run->edges = hh; } else { mp_edge_object *p = run->edges; while (p->next != NULL) { p = p->next; } p->next = hh; } } } @ This is where we fill them all in. @<Prepare function pointers for non-interactive use@>= { mp->open_file = mplib_open_file; mp->close_file = mplib_close_file; mp->eof_file = mplib_eof_file; mp->flush_file = mplib_flush_file; mp->write_ascii_file = mplib_write_ascii_file; mp->read_ascii_file = mplib_read_ascii_file; mp->write_binary_file = mplib_write_binary_file; mp->read_binary_file = mplib_read_binary_file; mp->shipout_backend = mplib_shipout_backend; } @ Perhaps this is the most important API function in the library. @<Exported function ...@>= extern mp_run_data *mp_rundata (MP mp); @ @c mp_run_data *mp_rundata (MP mp) { return &(mp->run_data); } @ @<Dealloc ...@>= mp_free_stream (&(mp->run_data.term_in)); mp_free_stream (&(mp->run_data.term_out)); mp_free_stream (&(mp->run_data.log_out)); mp_free_stream (&(mp->run_data.error_out)); mp_free_stream (&(mp->run_data.ps_out)); @ @<Finish non-interactive use@>= xfree (mp->term_out); xfree (mp->term_in); xfree (mp->err_out); @ @<Start non-interactive work@>= @<Initialize the output routines@>; mp->input_ptr = 0; mp->max_in_stack = file_bottom; mp->in_open = file_bottom; mp->open_parens = 0; mp->max_buf_stack = 0; mp->param_ptr = 0; mp->max_param_stack = 0; start = loc = 0; iindex = file_bottom; nloc = nstart = NULL; mp->first = 0; line = 0; name = is_term; mp->mpx_name[file_bottom] = absent; mp->force_eof = false; t_open_in; mp->scanner_status = normal; if (mp->mem_ident == NULL && !mp->ini_version) { if (!mp_load_preload_file (mp)) { mp->history = mp_fatal_error_stop; return mp->history; } } mp_fix_date_and_time (mp); if (mp->random_seed == 0) mp->random_seed = (internal_value (mp_time) / unity) + internal_value (mp_day); mp_init_randoms (mp, mp->random_seed); @<Initialize the print |selector|...@>; mp_open_log_file (mp); mp_set_job_id (mp); mp_init_map_file (mp, mp->troff_mode); mp->history = mp_spotless; /* ready to go! */ if (mp->troff_mode) { internal_value (mp_gtroffmode) = unity; internal_value (mp_prologues) = unity; } @<Fix up |mp->internal[mp_job_name]|@>; if (mp->start_sym != NULL) { /* insert the `\&{everyjob}' symbol */ mp->cur_sym = mp->start_sym; mp_back_input (mp); } @ @c int mp_execute (MP mp, char *s, size_t l) { mp_reset_stream (&(mp->run_data.term_out)); mp_reset_stream (&(mp->run_data.log_out)); mp_reset_stream (&(mp->run_data.error_out)); mp_reset_stream (&(mp->run_data.ps_out)); if (mp->finished) { return mp->history; } else if (!mp->noninteractive) { mp->history = mp_fatal_error_stop; return mp->history; } if (mp->history < mp_fatal_error_stop) { xfree (mp->jump_buf); mp->jump_buf = malloc (sizeof (jmp_buf)); if (mp->jump_buf == NULL || setjmp (*(mp->jump_buf)) != 0) { return mp->history; } if (s == NULL) { /* this signals EOF */ mp_final_cleanup (mp); /* prepare for death */ mp_close_files_and_terminate (mp); return mp->history; } mp->tally = 0; mp->term_offset = 0; mp->file_offset = 0; /* Perhaps some sort of warning here when |data| is not * yet exhausted would be nice ... this happens after errors */ if (mp->run_data.term_in.data) xfree (mp->run_data.term_in.data); mp->run_data.term_in.data = xstrdup (s); mp->run_data.term_in.cur = mp->run_data.term_in.data; mp->run_data.term_in.size = l; if (mp->run_state == 0) { mp->selector = term_only; @<Start non-interactive work@>; } mp->run_state = 1; (void) mp_input_ln (mp, mp->term_in); mp_firm_up_the_line (mp); mp->buffer[limit] = xord ('%'); mp->first = (size_t) (limit + 1); loc = start; do { mp_do_statement (mp); } while (mp->cur_cmd != stop); mp_final_cleanup (mp); mp_close_files_and_terminate (mp); } return mp->history; } @ This function cleans up @c int mp_finish (MP mp) { int history = 0; if (mp->finished || mp->history >= mp_fatal_error_stop) { history = mp->history; mp_free (mp); return history; } xfree (mp->jump_buf); mp->jump_buf = malloc (sizeof (jmp_buf)); if (mp->jump_buf == NULL || setjmp (*(mp->jump_buf)) != 0) { history = mp->history; } else { history = mp->history; mp_final_cleanup (mp); /* prepare for death */ } mp_close_files_and_terminate (mp); mp_free (mp); return history; } @ People may want to know the library version @c char *mp_metapost_version (void) { return mp_strdup (metapost_version); } @ @<Exported function headers@>= int mp_run (MP mp); int mp_execute (MP mp, char *s, size_t l); int mp_finish (MP mp); char *mp_metapost_version (void); @ @<Put each...@>= mp_primitive (mp, "end", stop, 0); @:end_}{\&{end} primitive@>; mp_primitive (mp, "dump", stop, 1); mp->frozen_dump = mp_frozen_primitive (mp, "dump", stop, 1); @:dump_}{\&{dump} primitive@> @ @<Cases of |print_cmd...@>= case stop: if (mp->cur_mod == 0) mp_print (mp, "end"); else mp_print (mp, "dump"); break; @* 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|...@>= case mp_random_seed: mp_do_random_seed (mp); break; @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_random_seed (MP mp); @ @c void mp_do_random_seed (MP mp) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_get_x_next (mp); if (mp->cur_cmd != assignment) { mp_missing_err (mp, ":="); @.Missing `:='@>; help1 ("Always say `randomseed:=<numeric expression>'."); mp_back_error (mp); }; mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_known) { 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."); mp_put_get_flush_error (mp, new_expr); } else { @<Initialize the random seed to |cur_exp|@>; } } @ @<Initialize the random seed to |cur_exp|@>= { mp_init_randoms (mp, cur_exp_value ()); if (mp->selector >= log_only && mp->selector < write_file) { mp->old_setting = mp->selector; mp->selector = log_only; mp_print_nl (mp, "{randomseed:="); mp_print_scaled (mp, cur_exp_value ()); mp_print_char (mp, xord ('}')); mp_print_nl (mp, ""); mp->selector = mp->old_setting; } } @ And here's another simple one (somewhat different in flavor): @<Cases of |do_statement|...@>= case mode_command: mp_print_ln (mp); mp->interaction = mp->cur_mod; @<Initialize the print |selector| based on |interaction|@>; if (mp->log_opened) mp->selector = mp->selector + 2; mp_get_x_next (mp); break; @ @<Put each...@>= mp_primitive (mp, "batchmode", mode_command, mp_batch_mode); @:mp_batch_mode_}{\&{batchmode} primitive@>; mp_primitive (mp, "nonstopmode", mode_command, mp_nonstop_mode); @:mp_nonstop_mode_}{\&{nonstopmode} primitive@>; mp_primitive (mp, "scrollmode", mode_command, mp_scroll_mode); @:mp_scroll_mode_}{\&{scrollmode} primitive@>; mp_primitive (mp, "errorstopmode", mode_command, mp_error_stop_mode); @:mp_error_stop_mode_}{\&{errorstopmode} primitive@> @ @<Cases of |print_cmd_mod|...@>= case mode_command: switch (m) { case mp_batch_mode: mp_print (mp, "batchmode"); break; case mp_nonstop_mode: mp_print (mp, "nonstopmode"); break; case mp_scroll_mode: mp_print (mp, "scrollmode"); break; default: mp_print (mp, "errorstopmode"); break; } break; @ The `\&{inner}' and `\&{outer}' commands are only slightly harder. @<Cases of |do_statement|...@>= case protection_command: mp_do_protection (mp); break; @ @<Put each...@>= mp_primitive (mp, "inner", protection_command, 0); @:inner_}{\&{inner} primitive@>; mp_primitive (mp, "outer", protection_command, 1); @:outer_}{\&{outer} primitive@> @ @<Cases of |print_cmd...@>= case protection_command: if (m == 0) mp_print (mp, "inner"); else mp_print (mp, "outer"); break; @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_protection (MP mp); @ @c void mp_do_protection (MP mp) { int m; /* 0 to unprotect, 1 to protect */ halfword t; /* the |eq_type| before we change it */ m = mp->cur_mod; do { mp_get_symbol (mp); t = eq_type (mp->cur_sym); if (m == 0) { if (t >= outer_tag) eq_type (mp->cur_sym) = t - outer_tag; } else if (t < outer_tag) { eq_type (mp->cur_sym) = t + outer_tag; } mp_get_x_next (mp); } while (mp->cur_cmd == comma); } @ \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|...@>= case delimiters: mp_def_delims (mp); break; @ @<Declare action procedures for use by |do_statement|@>= static void mp_def_delims (MP mp); @ @c void mp_def_delims (MP mp) { mp_sym l_delim, r_delim; /* the new delimiter pair */ mp_get_clear_symbol (mp); l_delim = mp->cur_sym; mp_get_clear_symbol (mp); r_delim = mp->cur_sym; eq_type (l_delim) = left_delimiter; equiv_sym (l_delim) = r_delim; eq_type (r_delim) = right_delimiter; equiv_sym (r_delim) = l_delim; mp_get_x_next (mp); } @ Here is a procedure that is called when \MP\ has reached a point where some right delimiter is mandatory. @<Declarations@>= static void mp_check_delimiter (MP mp, mp_sym l_delim, mp_sym r_delim); @ @c void mp_check_delimiter (MP mp, mp_sym l_delim, mp_sym r_delim) { if (mp->cur_cmd == right_delimiter) if (mp->cur_sym2 == l_delim) return; if (mp->cur_sym != r_delim) { mp_missing_err (mp, mp_str (mp, 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."); mp_back_error (mp); } else { print_err ("The token `"); mp_print_text (r_delim); @.The token...delimiter@>; mp_print (mp, "' 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."); mp_error (mp); } } @ The next four commands save or change the values associated with tokens. @<Cases of |do_statement|...@>= case save_command: do { mp_get_symbol (mp); mp_save_variable (mp, mp->cur_sym); mp_get_x_next (mp); } while (mp->cur_cmd == comma); break; case interim_command: mp_do_interim (mp); break; case let_command: mp_do_let (mp); break; case new_internal: mp_do_new_internal (mp); break; @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_statement (MP mp); static void mp_do_interim (MP mp); @ @c void mp_do_interim (MP mp) { mp_get_x_next (mp); if (mp->cur_cmd != internal_quantity) { print_err ("The token `"); @.The token...quantity@>; if (mp->cur_sym == NULL) mp_print (mp, "(%CAPSULE)"); else mp_print_text (mp->cur_sym); mp_print (mp, "' isn't an internal quantity"); help1 ("Something like `tracingonline' should follow `interim'."); mp_back_error (mp); } else { mp_save_internal (mp, mp->cur_mod); mp_back_input (mp); } mp_do_statement (mp); } @ 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|@>= static void mp_do_let (MP mp); @ @c void mp_do_let (MP mp) { mp_sym l; /* hash location of the left-hand symbol */ mp_get_symbol (mp); l = mp->cur_sym; mp_get_x_next (mp); if (mp->cur_cmd != equals && mp->cur_cmd != assignment) { mp_missing_err (mp, "="); @.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'."); mp_back_error (mp); } mp_get_symbol (mp); switch (mp->cur_cmd) { case defined_macro: case secondary_primary_macro: case tertiary_secondary_macro: case expression_tertiary_macro: add_mac_ref (mp->cur_mod_node); break; default: break; } mp_clear_symbol (mp, l, false); eq_type (l) = mp->cur_cmd; if (mp->cur_cmd == tag_token) equiv (l) = 0; /* todo: this was |null| */ else if (mp->cur_cmd == defined_macro || mp->cur_cmd == secondary_primary_macro || mp->cur_cmd == tertiary_secondary_macro || mp->cur_cmd == expression_tertiary_macro) equiv_node (l) = mp->cur_mod_node; else if (mp->cur_cmd == left_delimiter || mp->cur_cmd == right_delimiter) equiv_sym (l) = mp->cur_sym2; else equiv (l) = mp->cur_mod; mp_get_x_next (mp); } @ @<Declarations@>= static void mp_do_new_internal (MP mp); @ @<Internal library ...@>= void mp_grow_internals (MP mp, int l); @ @c void mp_grow_internals (MP mp, int l) { mp_internal *internal; int k; if (l > max_halfword) { mp_confusion (mp, "out of memory space"); /* can't be reached */ } internal = xmalloc ((l + 1), sizeof (mp_internal)); for (k = 0; k <= l; k++) { if (k <= mp->max_internal) { memcpy (internal + k, mp->internal + k, sizeof (mp_internal)); } else { memset (internal + k, 0, sizeof (mp_internal)); } } xfree (mp->internal); mp->internal = internal; mp->max_internal = l; } void mp_do_new_internal (MP mp) { int the_type = mp_known; mp_get_x_next (mp); if (mp->cur_cmd == type_name && mp->cur_mod == mp_string_type) { the_type = mp_string_type; } else { if (!(mp->cur_cmd == type_name && mp->cur_mod == mp_numeric_type)) { mp_back_input (mp); } } do { if (mp->int_ptr == mp->max_internal) { mp_grow_internals (mp, (mp->max_internal + (mp->max_internal / 4))); } mp_get_clear_symbol (mp); incr (mp->int_ptr); eq_type (mp->cur_sym) = internal_quantity; equiv (mp->cur_sym) = mp->int_ptr; if (internal_name (mp->int_ptr) != NULL) xfree (internal_name (mp->int_ptr)); internal_name (mp->int_ptr) = mp_xstrdup (mp, mp_str (mp, text (mp->cur_sym))); if (the_type == mp_string_type) { internal_string (mp->int_ptr) = null_str; } else { internal_value (mp->int_ptr) = 0; } internal_type (mp->int_ptr) = the_type; mp_get_x_next (mp); } while (mp->cur_cmd == comma); } @ @<Dealloc variables@>= for (k = 0; k <= mp->max_internal; k++) { xfree (internal_name (k)); } xfree (mp->internal); @ 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...@>= mp_primitive (mp, "showtoken", show_command, show_token_code); @:show_token_}{\&{showtoken} primitive@>; mp_primitive (mp, "showstats", show_command, show_stats_code); @:show_stats_}{\&{showstats} primitive@>; mp_primitive (mp, "show", show_command, show_code); @:show_}{\&{show} primitive@>; mp_primitive (mp, "showvariable", show_command, show_var_code); @:show_var_}{\&{showvariable} primitive@>; mp_primitive (mp, "showdependencies", show_command, show_dependencies_code); @:show_dependencies_}{\&{showdependencies} primitive@> @ @<Cases of |print_cmd...@>= case show_command: switch (m) { case show_token_code: mp_print (mp, "showtoken"); break; case show_stats_code: mp_print (mp, "showstats"); break; case show_code: mp_print (mp, "show"); break; case show_var_code: mp_print (mp, "showvariable"); break; default: mp_print (mp, "showdependencies"); break; } break; @ @<Cases of |do_statement|...@>= case show_command: mp_do_show_whatever (mp); break; @ 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|@>= static void mp_do_show (MP mp); @ @c void mp_do_show (MP mp) { mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); do { mp_get_x_next (mp); mp_scan_expression (mp); mp_print_nl (mp, ">> "); @.>>@>; mp_print_exp (mp, NULL, 2); mp_flush_cur_exp (mp, new_expr); } while (mp->cur_cmd == comma); } @ @<Declare action procedures for use by |do_statement|@>= static void mp_disp_token (MP mp); @ @c void mp_disp_token (MP mp) { mp_print_nl (mp, "> "); @.>\relax@>; if (mp->cur_sym == NULL) { @<Show a numeric or string or capsule token@>; } else { mp_print_text (mp->cur_sym); mp_print_char (mp, xord ('=')); if (eq_type (mp->cur_sym) >= outer_tag) mp_print (mp, "(outer) "); mp_print_cmd_mod (mp, mp->cur_cmd, mp->cur_mod); if (mp->cur_cmd == defined_macro) { mp_print_ln (mp); mp_show_macro (mp, mp->cur_mod_node, NULL, 100000); } /* this avoids recursion between |show_macro| and |print_cmd_mod| */ @^recursion@> } } @ @<Show a numeric or string or capsule token@>= { if (mp->cur_cmd == numeric_token) { mp_print_scaled (mp, mp->cur_mod); } else if (mp->cur_cmd == capsule_token) { mp_print_capsule (mp, mp->cur_mod_node); } else { mp_print_char (mp, xord ('"')); mp_print_str (mp, mp->cur_mod_str); mp_print_char (mp, xord ('"')); delete_str_ref (mp->cur_mod_str); } } @ The following cases of |print_cmd_mod| might arise in connection with |disp_token|, although they don't necessarily correspond to primitive tokens. @<Cases of |print_cmd_...@>= case left_delimiter: case right_delimiter: if (c == left_delimiter) mp_print (mp, "left"); else mp_print (mp, "right"); #if 0 mp_print (mp, " delimiter that matches "); mp_print_text (m); #else mp_print (mp, " delimiter"); #endif break; case tag_token: if (m == 0) /* todo: this was |null| */ mp_print (mp, "tag"); else mp_print (mp, "variable"); break; case defined_macro: mp_print (mp, "macro:"); break; case repeat_loop: mp_print (mp, "[repeat the loop]"); break; case internal_quantity: mp_print (mp, internal_name (m)); break; @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_show_token (MP mp); @ @c void mp_do_show_token (MP mp) { do { get_t_next (mp); mp_disp_token (mp); mp_get_x_next (mp); } while (mp->cur_cmd == comma); } @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_show_stats (MP mp); @ @c void mp_do_show_stats (MP mp) { mp_print_nl (mp, "Memory usage "); @.Memory usage...@>; mp_print_int (mp, (integer) mp->var_used); mp_print_ln (mp); mp_print_nl (mp, "String usage "); mp_print_int (mp, (int) mp->strs_in_use); mp_print_char (mp, xord ('&')); mp_print_int (mp, (int) mp->pool_in_use); mp_print_ln (mp); mp_get_x_next (mp); } @ 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|@>= static void mp_disp_var (MP mp, mp_node p); @ @c void mp_disp_var (MP mp, mp_node p) { mp_node q; /* traverses attributes and subscripts */ int n; /* amount of macro text to show */ if (mp_type (p) == mp_structured) { @<Descend the structure@>; } else if (mp_type (p) >= mp_unsuffixed_macro) { @<Display a variable macro@>; } else if (mp_type (p) != undefined) { mp_print_nl (mp, ""); mp_print_variable_name (mp, p); mp_print_char (mp, xord ('=')); mp_print_exp (mp, p, 0); } } @ @<Descend the structure@>= { q = attr_head (p); do { mp_disp_var (mp, q); q = mp_link (q); } while (q != mp->end_attr); q = subscr_head (p); while (mp_name_type (q) == mp_subscr) { mp_disp_var (mp, q); q = mp_link (q); } } @ @<Display a variable macro@>= { mp_print_nl (mp, ""); mp_print_variable_name (mp, p); if (mp_type (p) > mp_unsuffixed_macro) mp_print (mp, "@@#"); /* |suffixed_macro| */ mp_print (mp, "=macro:"); if ((int) mp->file_offset >= mp->max_print_line - 20) n = 5; else n = mp->max_print_line - (int) mp->file_offset - 15; mp_show_macro (mp, value_node (p), NULL, n); } @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_show_var (MP mp); @ @c void mp_do_show_var (MP mp) { do { get_t_next (mp); if (mp->cur_sym != NULL) if (mp->cur_sym_mod == 0) if (mp->cur_cmd == tag_token) if (mp->cur_mod != 0) { mp_disp_var (mp, mp->cur_mod_node); goto DONE; } mp_disp_token (mp); DONE: mp_get_x_next (mp); } while (mp->cur_cmd == comma); } @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_show_dependencies (MP mp); @ @c void mp_do_show_dependencies (MP mp) { mp_value_node p; /* link that runs through all dependencies */ p = (mp_value_node) mp_link (mp->dep_head); while (p != mp->dep_head) { if (mp_interesting (mp, (mp_node) p)) { mp_print_nl (mp, ""); mp_print_variable_name (mp, (mp_node) p); if (mp_type (p) == mp_dependent) mp_print_char (mp, xord ('=')); else mp_print (mp, " = "); /* extra spaces imply proto-dependency */ mp_print_dependency (mp, (mp_value_node) dep_list (p), mp_type (p)); } p = (mp_value_node) dep_list (p); while (dep_info (p) != NULL) p = (mp_value_node) mp_link (p); p = (mp_value_node) mp_link (p); } mp_get_x_next (mp); } @ Finally we are ready for the procedure that governs all of the show commands. @<Declare action procedures for use by |do_statement|@>= static void mp_do_show_whatever (MP mp); @ @c void mp_do_show_whatever (MP mp) { if (mp->interaction == mp_error_stop_mode) wake_up_terminal; switch (mp->cur_mod) { case show_token_code: mp_do_show_token (mp); break; case show_stats_code: mp_do_show_stats (mp); break; case show_code: mp_do_show (mp); break; case show_var_code: mp_do_show_var (mp); break; case show_dependencies_code: mp_do_show_dependencies (mp); break; } /* there are no other cases */ if (internal_value (mp_showstopping) > 0) { print_err ("OK"); @.OK@>; if (mp->interaction < mp_error_stop_mode) { help0; decr (mp->error_count); } else { help1 ("This isn't an error message; I'm just showing something."); } if (mp->cur_cmd == semicolon) mp_error (mp); else mp_put_get_error (mp); } } @ 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}' */ @ Pre and postscripts need two new identifiers: @d with_mp_pre_script 11 @d with_mp_post_script 13 @<Put each...@>= mp_primitive (mp, "doublepath", thing_to_add, double_path_code); @:double_path_}{\&{doublepath} primitive@>; mp_primitive (mp, "contour", thing_to_add, contour_code); @:contour_}{\&{contour} primitive@>; mp_primitive (mp, "also", thing_to_add, also_code); @:also_}{\&{also} primitive@>; mp_primitive (mp, "withpen", with_option, mp_pen_type); @:with_pen_}{\&{withpen} primitive@>; mp_primitive (mp, "dashed", with_option, mp_picture_type); @:dashed_}{\&{dashed} primitive@>; mp_primitive (mp, "withprescript", with_option, with_mp_pre_script); @:with_mp_pre_script_}{\&{withprescript} primitive@>; mp_primitive (mp, "withpostscript", with_option, with_mp_post_script); @:with_mp_post_script_}{\&{withpostscript} primitive@>; mp_primitive (mp, "withoutcolor", with_option, mp_no_model); @:with_color_}{\&{withoutcolor} primitive@>; mp_primitive (mp, "withgreyscale", with_option, mp_grey_model); @:with_color_}{\&{withgreyscale} primitive@>; mp_primitive (mp, "withcolor", with_option, mp_uninitialized_model); @:with_color_}{\&{withcolor} primitive@> /* \&{withrgbcolor} is an alias for \&{withcolor} */ mp_primitive (mp, "withrgbcolor", with_option, mp_rgb_model); @:with_color_}{\&{withrgbcolor} primitive@>; mp_primitive (mp, "withcmykcolor", with_option, mp_cmyk_model); @:with_color_}{\&{withcmykcolor} primitive@> @ @<Cases of |print_cmd...@>= case thing_to_add: if (m == contour_code) mp_print (mp, "contour"); else if (m == double_path_code) mp_print (mp, "doublepath"); else mp_print (mp, "also"); break; case with_option: if (m == mp_pen_type) mp_print (mp, "withpen"); else if (m == with_mp_pre_script) mp_print (mp, "withprescript"); else if (m == with_mp_post_script) mp_print (mp, "withpostscript"); else if (m == mp_no_model) mp_print (mp, "withoutcolor"); else if (m == mp_rgb_model) mp_print (mp, "withrgbcolor"); else if (m == mp_uninitialized_model) mp_print (mp, "withcolor"); else if (m == mp_cmyk_model) mp_print (mp, "withcmykcolor"); else if (m == mp_grey_model) mp_print (mp, "withgreyscale"); else mp_print (mp, "dashed"); break; @ 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|@>= static void mp_scan_with_list (MP mp, mp_node p); @ @c void mp_scan_with_list (MP mp, mp_node p) { mp_variable_type t; /* |cur_mod| of the |with_option| (should match |cur_type|) */ mp_node q; /* for list manipulation */ unsigned old_setting; /* saved |selector| setting */ mp_node k; /* for finding the near-last item in a list */ str_number s; /* for string cleanup after combining */ mp_value new_expr; mp_node cp, pp, dp, ap, bp; /* objects being updated; |void| initially; |NULL| to suppress update */ cp = MP_VOID; pp = MP_VOID; dp = MP_VOID; ap = MP_VOID; bp = MP_VOID; k = 0; memset(&new_expr,0,sizeof(mp_value)); while (mp->cur_cmd == with_option) { /* todo this is not very nice: the color models have their own enumeration */ t = (mp_variable_type) mp->cur_mod; mp_get_x_next (mp); if (t != (mp_variable_type) mp_no_model) mp_scan_expression (mp); if (((t == with_mp_pre_script) && (mp->cur_exp.type != mp_string_type)) || ((t == with_mp_post_script) && (mp->cur_exp.type != mp_string_type)) || ((t == (mp_variable_type) mp_uninitialized_model) && ((mp->cur_exp.type != mp_cmykcolor_type) && (mp->cur_exp.type != mp_color_type) && (mp->cur_exp.type != mp_known) && (mp->cur_exp.type != mp_boolean_type))) || ((t == (mp_variable_type) mp_cmyk_model) && (mp->cur_exp.type != mp_cmykcolor_type)) || ((t == (mp_variable_type) mp_rgb_model) && (mp->cur_exp.type != mp_color_type)) || ((t == (mp_variable_type) mp_grey_model) && (mp->cur_exp.type != mp_known)) || ((t == (mp_variable_type) mp_pen_type) && (mp->cur_exp.type != t)) || ((t == (mp_variable_type) mp_picture_type) && (mp->cur_exp.type != t))) { @<Complain about improper type@>; } else if (t == (mp_variable_type) mp_uninitialized_model) { if (cp == MP_VOID) @<Make |cp| a colored object in object list~|p|@>; if (cp != NULL) @<Transfer a color from the current expression to object~|cp|@>; mp_flush_cur_exp (mp, new_expr); } else if (t == (mp_variable_type) mp_rgb_model) { if (cp == MP_VOID) @<Make |cp| a colored object in object list~|p|@>; if (cp != NULL) @<Transfer a rgbcolor from the current expression to object~|cp|@>; mp_flush_cur_exp (mp, new_expr); } else if (t == (mp_variable_type) mp_cmyk_model) { if (cp == MP_VOID) @<Make |cp| a colored object in object list~|p|@>; if (cp != NULL) @<Transfer a cmykcolor from the current expression to object~|cp|@>; mp_flush_cur_exp (mp, new_expr); } else if (t == (mp_variable_type) mp_grey_model) { if (cp == MP_VOID) @<Make |cp| a colored object in object list~|p|@>; if (cp != NULL) @<Transfer a greyscale from the current expression to object~|cp|@>; mp_flush_cur_exp (mp, new_expr); } else if (t == (mp_variable_type) mp_no_model) { if (cp == MP_VOID) @<Make |cp| a colored object in object list~|p|@>; if (cp != NULL) @<Transfer a noncolor from the current expression to object~|cp|@>; } else if (t == mp_pen_type) { if (pp == MP_VOID) @<Make |pp| an object in list~|p| that needs a pen@>; if (pp != NULL) { switch (mp_type (pp)) { case mp_fill_node_type: if (mp_pen_p ((mp_fill_node) pp) != NULL) mp_toss_knot_list (mp, mp_pen_p ((mp_fill_node) pp)); mp_pen_p ((mp_fill_node) pp) = cur_exp_knot (); break; case mp_stroked_node_type: if (mp_pen_p ((mp_stroked_node) pp) != NULL) mp_toss_knot_list (mp, mp_pen_p ((mp_stroked_node) pp)); mp_pen_p ((mp_stroked_node) pp) = cur_exp_knot (); break; default: assert (0); break; } mp->cur_exp.type = mp_vacuous; } } else if (t == with_mp_pre_script) { if (ap == MP_VOID) ap = p; while ((ap != NULL) && (!has_color (ap))) ap = mp_link (ap); if (ap != NULL) { if (mp_pre_script (ap) != NULL) { /* build a new,combined string */ s = mp_pre_script (ap); old_setting = mp->selector; mp->selector = new_string; str_room (length (mp_pre_script (ap)) + length (cur_exp_str ()) + 2); mp_print_str (mp, cur_exp_str ()); append_char (13); /* a forced \ps\ newline */ mp_print_str (mp, mp_pre_script (ap)); mp_pre_script (ap) = mp_make_string (mp); delete_str_ref (s); mp->selector = old_setting; } else { mp_pre_script (ap) = cur_exp_str (); } mp->cur_exp.type = mp_vacuous; } } else if (t == with_mp_post_script) { if (bp == MP_VOID) k = p; bp = k; while (mp_link (k) != NULL) { k = mp_link (k); if (has_color (k)) bp = k; } if (bp != NULL) { if (mp_post_script (bp) != NULL) { s = mp_post_script (bp); old_setting = mp->selector; mp->selector = new_string; str_room (length (mp_post_script (bp)) + length (cur_exp_str ()) + 2); mp_print_str (mp, mp_post_script (bp)); append_char (13); /* a forced \ps\ newline */ mp_print_str (mp, cur_exp_str ()); mp_post_script (bp) = mp_make_string (mp); delete_str_ref (s); mp->selector = old_setting; } else { mp_post_script (bp) = cur_exp_str (); } mp->cur_exp.type = mp_vacuous; } } else { if (dp == MP_VOID) { @<Make |dp| a stroked node in list~|p|@>; } if (dp != NULL) { if (mp_dash_p (dp) != NULL) delete_edge_ref (mp_dash_p (dp)); mp_dash_p (dp) = mp_make_dashes (mp, cur_exp_node ()); dash_scale (dp) = unity; mp->cur_exp.type = mp_vacuous; } } } @<Copy the information from objects |cp|, |pp|, and |dp| into the rest of the list@>; } @ @<Complain about improper type@>= { 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 == with_mp_pre_script) mp->help_line[1] = "Next time say `withprescript <known string expression>';"; else if (t == with_mp_post_script) mp->help_line[1] = "Next time say `withpostscript <known string expression>';"; else if (t == mp_picture_type) mp->help_line[1] = "Next time say `dashed <known picture expression>';"; else if (t == (mp_variable_type) mp_uninitialized_model) mp->help_line[1] = "Next time say `withcolor <known color expression>';"; else if (t == (mp_variable_type) mp_rgb_model) mp->help_line[1] = "Next time say `withrgbcolor <known color expression>';"; else if (t == (mp_variable_type) mp_cmyk_model) mp->help_line[1] = "Next time say `withcmykcolor <known cmykcolor expression>';"; else if (t == (mp_variable_type) mp_grey_model) mp->help_line[1] = "Next time say `withgreyscale <known numeric expression>';";; mp_put_get_flush_error (mp, new_expr); } @ 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|@>= { if (mp->cur_exp.type == mp_color_type) { @<Transfer a rgbcolor from the current expression to object~|cp|@>; } else if (mp->cur_exp.type == mp_cmykcolor_type) { @<Transfer a cmykcolor from the current expression to object~|cp|@>; } else if (mp->cur_exp.type == mp_known) { @<Transfer a greyscale from the current expression to object~|cp|@>; } else if (cur_exp_value () == false_code) { @<Transfer a noncolor from the current expression to object~|cp|@>; } else if (cur_exp_value () == true_code) { @<Transfer no color from the current expression to object~|cp|@>; } } @ @<Transfer a rgbcolor from the current expression to object~|cp|@>= { q = value_node (cur_exp_node ()); cyan_val (cp) = 0; magenta_val (cp) = 0; yellow_val (cp) = 0; black_val (cp) = 0; red_val (cp) = value (red_part_loc (q)); green_val (cp) = value (green_part_loc (q)); blue_val (cp) = value (blue_part_loc (q)); mp_color_model (cp) = mp_rgb_model; if (red_val (cp) < 0) red_val (cp) = 0; if (green_val (cp) < 0) green_val (cp) = 0; if (blue_val (cp) < 0) blue_val (cp) = 0; if (red_val (cp) > unity) red_val (cp) = unity; if (green_val (cp) > unity) green_val (cp) = unity; if (blue_val (cp) > unity) blue_val (cp) = unity; } @ @<Transfer a cmykcolor from the current expression to object~|cp|@>= { q = value_node (cur_exp_node ()); cyan_val (cp) = value (cyan_part_loc (q)); magenta_val (cp) = value (magenta_part_loc (q)); yellow_val (cp) = value (yellow_part_loc (q)); black_val (cp) = value (black_part_loc (q)); mp_color_model (cp) = mp_cmyk_model; if (cyan_val (cp) < 0) cyan_val (cp) = 0; if (magenta_val (cp) < 0) magenta_val (cp) = 0; if (yellow_val (cp) < 0) yellow_val (cp) = 0; if (black_val (cp) < 0) black_val (cp) = 0; if (cyan_val (cp) > unity) cyan_val (cp) = unity; if (magenta_val (cp) > unity) magenta_val (cp) = unity; if (yellow_val (cp) > unity) yellow_val (cp) = unity; if (black_val (cp) > unity) black_val (cp) = unity; } @ @<Transfer a greyscale from the current expression to object~|cp|@>= { scaled qq = cur_exp_value (); cyan_val (cp) = 0; magenta_val (cp) = 0; yellow_val (cp) = 0; black_val (cp) = 0; grey_val (cp) = qq; mp_color_model (cp) = mp_grey_model; if (grey_val (cp) < 0) grey_val (cp) = 0; if (grey_val (cp) > unity) grey_val (cp) = unity; } @ @<Transfer a noncolor from the current expression to object~|cp|@>= { cyan_val (cp) = 0; magenta_val (cp) = 0; yellow_val (cp) = 0; black_val (cp) = 0; grey_val (cp) = 0; mp_color_model (cp) = mp_no_model; } @ @<Transfer no color from the current expression to object~|cp|@>= { cyan_val (cp) = 0; magenta_val (cp) = 0; yellow_val (cp) = 0; black_val (cp) = 0; grey_val (cp) = 0; mp_color_model (cp) = mp_uninitialized_model; } @ @<Make |cp| a colored object in object list~|p|@>= { cp = p; while (cp != NULL) { if (has_color (cp)) break; cp = mp_link (cp); } } @ @<Make |pp| an object in list~|p| that needs a pen@>= { pp = p; while (pp != NULL) { if (has_pen (pp)) break; pp = mp_link (pp); } } @ @<Make |dp| a stroked node in list~|p|@>= { dp = p; while (dp != NULL) { if (mp_type (dp) == mp_stroked_node_type) break; dp = mp_link (dp); } } @ @<Copy the information from objects |cp|, |pp|, and |dp| into...@>= if (cp > MP_VOID) { @<Copy |cp|'s color into the colored objects linked to~|cp|@>; } if (pp > MP_VOID) { @<Copy |mp_pen_p(pp)| into stroked and filled nodes linked to |pp|@>; } if (dp > MP_VOID) { @<Make stroked nodes linked to |dp| refer to |mp_dash_p(dp)|@>; } @ @<Copy |cp|'s color into the colored objects linked to~|cp|@>= { q = mp_link (cp); while (q != NULL) { if (has_color (q)) { red_val (q) = red_val (cp); green_val (q) = green_val (cp); blue_val (q) = blue_val (cp); black_val (q) = black_val (cp); mp_color_model (q) = mp_color_model (cp); } q = mp_link (q); } } @ @<Copy |mp_pen_p(pp)| into stroked and filled nodes linked to |pp|@>= { q = mp_link (pp); while (q != NULL) { if (has_pen (q)) { switch (mp_type (q)) { case mp_fill_node_type: if (mp_pen_p ((mp_fill_node) q) != NULL) mp_toss_knot_list (mp, mp_pen_p ((mp_fill_node) q)); mp_pen_p ((mp_fill_node) q) = copy_pen (mp_pen_p ((mp_fill_node) pp)); break; case mp_stroked_node_type: if (mp_pen_p ((mp_stroked_node) q) != NULL) mp_toss_knot_list (mp, mp_pen_p ((mp_stroked_node) q)); mp_pen_p ((mp_stroked_node) q) = copy_pen (mp_pen_p ((mp_stroked_node) pp)); break; default: assert (0); break; } } q = mp_link (q); } } @ @<Make stroked nodes linked to |dp| refer to |mp_dash_p(dp)|@>= { q = mp_link (dp); while (q != NULL) { if (mp_type (q) == mp_stroked_node_type) { if (mp_dash_p (q) != NULL) delete_edge_ref (mp_dash_p (q)); mp_dash_p (q) = mp_dash_p (dp); dash_scale (q) = unity; if (mp_dash_p (q) != NULL) add_edge_ref (mp_dash_p (q)); } q = mp_link (q); } } @ 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|@>= static mp_node mp_find_edges_var (MP mp, mp_node t); @ @c mp_node mp_find_edges_var (MP mp, mp_node t) { mp_node p; mp_node cur_edges; /* the return value */ p = mp_find_variable (mp, t); cur_edges = NULL; if (p == NULL) { mp_obliterated (mp, t); mp_put_get_error (mp); } else if (mp_type (p) != mp_picture_type) { print_err ("Variable "); mp_show_token_list (mp, t, NULL, 1000, 0); @.Variable x is the wrong type@>; mp_print (mp, " is the wrong type ("); mp_print_type (mp, mp_type (p)); mp_print_char (mp, xord (')')); help2 ("I was looking for a \"known\" picture variable.", "So I'll not change anything just now."); mp_put_get_error (mp); } else { set_value_node (p, mp_private_edges (mp, value_node (p))); cur_edges = value_node (p); } mp_flush_node_list (mp, t); return cur_edges; } @ @<Cases of |do_statement|...@>= case add_to_command: mp_do_add_to (mp); break; case bounds_command: mp_do_bounds (mp); break; @ @<Put each...@>= mp_primitive (mp, "clip", bounds_command, mp_start_clip_node_type); @:clip_}{\&{clip} primitive@>; mp_primitive (mp, "setbounds", bounds_command, mp_start_bounds_node_type); @:set_bounds_}{\&{setbounds} primitive@> @ @<Cases of |print_cmd...@>= case bounds_command: if (m == mp_start_clip_node_type) mp_print (mp, "clip"); else mp_print (mp, "setbounds"); break; @ 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...@>= quarterword last_add_type; /* command modifier that identifies the last \&{addto} command */ @ @<Declare action procedures for use by |do_statement|@>= static mp_node mp_start_draw_cmd (MP mp, quarterword sep); @ @c mp_node mp_start_draw_cmd (MP mp, quarterword sep) { mp_node lhv; /* variable to add to left */ mp_value new_expr; quarterword add_type = 0; /* value to be returned in |last_add_type| */ lhv = NULL; memset(&new_expr,0,sizeof(mp_value)); mp_get_x_next (mp); mp->var_flag = sep; mp_scan_primary (mp); if (mp->cur_exp.type != mp_token_list) { @<Abandon edges command because there's no variable@>; } else { lhv = cur_exp_node (); add_type = (quarterword) mp->cur_mod; mp->cur_exp.type = mp_vacuous; mp_get_x_next (mp); mp_scan_expression (mp); } mp->last_add_type = add_type; return lhv; } @ @<Abandon edges command because there's no variable@>= { 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."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } @ Here is an example of how to use |start_draw_cmd|. @<Declare action procedures for use by |do_statement|@>= static void mp_do_bounds (MP mp); @ @c void mp_do_bounds (MP mp) { mp_node lhv, lhe; /* variable on left, the corresponding edge structure */ mp_node p; /* for list manipulation */ integer m; /* initial value of |cur_mod| */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); m = mp->cur_mod; lhv = mp_start_draw_cmd (mp, to_token); if (lhv != NULL) { lhe = mp_find_edges_var (mp, lhv); if (lhe == NULL) { new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr); } else if (mp->cur_exp.type != mp_path_type) { exp_err ("Improper `clip'"); @.Improper `addto'@>; help2 ("This expression should have specified a known path.", "So I'll not change anything just now."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } else if (mp_left_type (cur_exp_knot ()) == mp_endpoint) { @<Complain about a non-cycle@>; } else { @<Make |cur_exp| into a \&{setbounds} or clipping path and add it to |lhe|@>; } } } @ @<Complain about a non-cycle@>= { 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."); mp_put_get_error (mp); } @ @<Make |cur_exp| into a \&{setbounds} or clipping path and add...@>= { p = mp_new_bounds_node (mp, cur_exp_knot (), (quarterword) m); mp_link (p) = mp_link (dummy_loc (lhe)); mp_link (dummy_loc (lhe)) = p; if (obj_tail (lhe) == dummy_loc (lhe)) obj_tail (lhe) = p; if (m == mp_start_clip_node_type) { p = mp_new_bounds_node (mp, NULL, mp_stop_clip_node_type); } else if (m == mp_start_bounds_node_type) { p = mp_new_bounds_node (mp, NULL, mp_stop_bounds_node_type); } mp_link (obj_tail (lhe)) = p; obj_tail (lhe) = p; mp_init_bbox (mp, lhe); } @ 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|@>= static void mp_do_add_to (MP mp); @ @c void mp_do_add_to (MP mp) { mp_node lhv, lhe; /* variable on left, the corresponding edge structure */ mp_node p; /* the graphical object or list for |scan_with_list| to update */ mp_node e; /* an edge structure to be merged */ quarterword add_type; /* |also_code|, |contour_code|, or |double_path_code| */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); lhv = mp_start_draw_cmd (mp, thing_to_add); add_type = mp->last_add_type; if (lhv != NULL) { if (add_type == also_code) { @<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@>; } mp_scan_with_list (mp, p); @<Use |p|, |e|, and |add_type| to augment |lhv| as requested@>; } } @ 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|...@>= { p = NULL; e = NULL; if (mp->cur_exp.type != mp_picture_type) { exp_err ("Improper `addto'"); @.Improper `addto'@>; help2 ("This expression should have specified a known picture.", "So I'll not change anything just now."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } else { e = mp_private_edges (mp, cur_exp_node ()); mp->cur_exp.type = mp_vacuous; p = mp_link (dummy_loc (e)); } } @ 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...@>= { e = NULL; p = NULL; if (mp->cur_exp.type == mp_pair_type) mp_pair_to_path (mp); if (mp->cur_exp.type != mp_path_type) { exp_err ("Improper `addto'"); @.Improper `addto'@>; help2 ("This expression should have specified a known path.", "So I'll not change anything just now."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } else if (add_type == contour_code) { if (mp_left_type (cur_exp_knot ()) == mp_endpoint) { @<Complain about a non-cycle@>; } else { p = mp_new_fill_node (mp, cur_exp_knot ()); mp->cur_exp.type = mp_vacuous; } } else { p = mp_new_stroked_node (mp, cur_exp_knot ()); mp->cur_exp.type = mp_vacuous; } } @ @<Use |p|, |e|, and |add_type| to augment |lhv| as requested@>= lhe = mp_find_edges_var (mp, lhv); if (lhe == NULL) { if ((e == NULL) && (p != NULL)) e = mp_toss_gr_object (mp, p); if (e != NULL) delete_edge_ref (e); } else if (add_type == also_code) { if (e != NULL) { @<Merge |e| into |lhe| and delete |e|@>; } } else if (p != NULL) { mp_link (obj_tail (lhe)) = p; obj_tail (lhe) = p; if (add_type == double_path_code) if (mp_pen_p ((mp_stroked_node) p) == NULL) mp_pen_p ((mp_stroked_node) p) = mp_get_pen_circle (mp, 0); } @ @<Merge |e| into |lhe| and delete |e|@>= { if (mp_link (dummy_loc (e)) != NULL) { mp_link (obj_tail (lhe)) = mp_link (dummy_loc (e)); obj_tail (lhe) = obj_tail (e); obj_tail (e) = dummy_loc (e); mp_link (dummy_loc (e)) = NULL; mp_flush_dash_list (mp, lhe); } mp_toss_edges (mp, e); } @ @<Cases of |do_statement|...@>= case ship_out_command: mp_do_ship_out (mp); break; @ @<Declare action procedures for use by |do_statement|@>= @<Declare the \ps\ output procedures@>; static void mp_do_ship_out (MP mp); @ @c void mp_do_ship_out (MP mp) { integer c; /* the character code */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_picture_type) { @<Complain that it's not a known picture@>; } else { c = mp_round_unscaled (mp, internal_value (mp_char_code)) % 256; if (c < 0) c = c + 256; @<Store the width information for character code~|c|@>; mp_ship_out (mp, cur_exp_node ()); new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr); } } @ @<Complain that it's not a known picture@>= { exp_err ("Not a known picture"); help1 ("I can only output known pictures."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } @ The \&{everyjob} command simply assigns a nonzero value to the global variable |start_sym|. @<Cases of |do_statement|...@>= case every_job_command: mp_get_symbol (mp); mp->start_sym = mp->cur_sym; mp_get_x_next (mp); break; @ @<Glob...@>= mp_sym start_sym; /* a symbolic token to insert at beginning of job */ @ @<Set init...@>= mp->start_sym = NULL; @ Finally, we have only the ``message'' commands remaining. @d message_code 0 @d err_message_code 1 @d err_help_code 2 @d filename_template_code 3 @d print_with_leading_zeroes(A,B) do { size_t g = mp->cur_length; size_t f = (size_t)(B); mp_print_int(mp, (A)); g = mp->cur_length - g; if ( f>g ) { mp->cur_length = mp->cur_length - g; while ( f>g ) { mp_print_char(mp, xord('0')); decr(f); }; mp_print_int(mp, (A)); }; f = 0; } while (0) @<Put each...@>= mp_primitive (mp, "message", message_command, message_code); @:message_}{\&{message} primitive@>; mp_primitive (mp, "errmessage", message_command, err_message_code); @:err_message_}{\&{errmessage} primitive@>; mp_primitive (mp, "errhelp", message_command, err_help_code); @:err_help_}{\&{errhelp} primitive@>; mp_primitive (mp, "filenametemplate", message_command, filename_template_code); @:filename_template_}{\&{filenametemplate} primitive@> @ @<Cases of |print_cmd...@>= case message_command: if (m < err_message_code) mp_print (mp, "message"); else if (m == err_message_code) mp_print (mp, "errmessage"); else if (m == filename_template_code) mp_print (mp, "filenametemplate"); else mp_print (mp, "errhelp"); break; @ @<Cases of |do_statement|...@>= case message_command: mp_do_message (mp); break; @ @<Declare action procedures for use by |do_statement|@>= @<Declare a procedure called |no_string_err|@>; static void mp_do_message (MP mp); @ @c void mp_do_message (MP mp) { int m; /* the type of message */ mp_value new_expr; m = mp->cur_mod; memset(&new_expr,0,sizeof(mp_value)); mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_string_type) mp_no_string_err (mp, "A message should be a known string expression."); else { switch (m) { case message_code: mp_print_nl (mp, ""); mp_print_str (mp, cur_exp_str ()); break; case err_message_code: @<Print string |cur_exp| as an error message@>; break; case err_help_code: @<Save string |cur_exp| as the |err_help|@>; break; case filename_template_code: @<Save the filename template@>; break; } /* there are no other cases */ } new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr); } @ @<Save the filename template@>= { delete_str_ref (internal_string (mp_output_template)); if (length (cur_exp_str ()) == 0) { internal_string (mp_output_template) = mp_rts (mp, "%j.%c"); } else { internal_string (mp_output_template) = cur_exp_str (); add_str_ref (internal_string (mp_output_template)); } } @ @<Declare a procedure called |no_string_err|@>= static void mp_no_string_err (MP mp, const char *s) { exp_err ("Not a string"); @.Not a string@>; help1 (s); mp_put_get_error (mp); } @ 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|@>= { if (mp->err_help != NULL) delete_str_ref (mp->err_help); if (length (cur_exp_str ()) == 0) mp->err_help = NULL; else { mp->err_help = cur_exp_str (); add_str_ref (mp->err_help); } } @ If \&{errmessage} occurs often in |mp_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...@>= boolean long_help_seen; /* has the long \.{\\errmessage} help been used? */ @ @<Set init...@>= mp->long_help_seen = false; @ @<Print string |cur_exp| as an error message@>= { print_err (""); mp_print_str (mp, cur_exp_str ()); if (mp->err_help != NULL) { mp->use_err_help = true; } else if (mp->long_help_seen) { help1 ("(That was another `errmessage'.)"); } else { if (mp->interaction < mp_error_stop_mode) mp->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."); } mp_put_get_error (mp); mp->use_err_help = false; } @ @<Cases of |do_statement|...@>= case write_command: mp_do_write (mp); break; @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_write (MP mp); @ @c void mp_do_write (MP mp) { str_number t; /* the line of text to be written */ write_index n, n0; /* for searching |wr_fname| and |wr_file| arrays */ unsigned old_setting; /* for saving |selector| during output */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_string_type) { mp_no_string_err (mp, "The text to be written should be a known string expression"); } else if (mp->cur_cmd != to_token) { print_err ("Missing `to' clause"); help1 ("A write command should end with `to <filename>'"); mp_put_get_error (mp); } else { t = cur_exp_str (); mp->cur_exp.type = mp_vacuous; mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_string_type) mp_no_string_err (mp, "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);| *//* todo: is this right? */ } new_expr.data.val = 0; mp_flush_cur_exp (mp, new_expr); } @ @<Write |t| to the file named by |cur_exp|@>= { @<Find |n| where |wr_fname[n]=cur_exp| and call |open_write_file| if |cur_exp| must be inserted@>; if (mp_str_vs_str (mp, t, mp->eof_line) == 0) { @<Record the end of file on |wr_file[n]|@>; } else { old_setting = mp->selector; mp->selector = n + write_file; mp_print_str (mp, t); mp_print_ln (mp); mp->selector = old_setting; } } @ @<Find |n| where |wr_fname[n]=cur_exp| and call |open_write_file| if...@>= { char *fn = mp_str (mp, cur_exp_str ()); n = mp->write_files; n0 = mp->write_files; while (mp_xstrcmp (fn, mp->wr_fname[n]) != 0) { if (n == 0) { /* bottom reached */ if (n0 == mp->write_files) { if (mp->write_files < mp->max_write_files) { incr (mp->write_files); } else { void **wr_file; char **wr_fname; write_index l, k; l = mp->max_write_files + (mp->max_write_files / 4); wr_file = xmalloc ((l + 1), sizeof (void *)); wr_fname = xmalloc ((l + 1), sizeof (char *)); for (k = 0; k <= l; k++) { if (k <= mp->max_write_files) { wr_file[k] = mp->wr_file[k]; wr_fname[k] = mp->wr_fname[k]; } else { wr_file[k] = 0; wr_fname[k] = NULL; } } xfree (mp->wr_file); xfree (mp->wr_fname); mp->max_write_files = l; mp->wr_file = wr_file; mp->wr_fname = wr_fname; } } n = n0; mp_open_write_file (mp, fn, n); } else { decr (n); if (mp->wr_fname[n] == NULL) n0 = n; } } } @ @<Record the end of file on |wr_file[n]|@>= { (mp->close_file) (mp, mp->wr_file[n]); xfree (mp->wr_fname[n]); if (n == mp->write_files - 1) mp->write_files = n; } @* 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...@>= void *tfm_file; /* the font metric output goes here */ char *metric_file_name; /* 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. 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. @^design size@> @ 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) /* value indicating `\.{STOP}' in a lig/kern program */ @d kern_flag (128) /* op code for a kern step */ @d skip_byte(A) mp->lig_kern[(A)].b0 @d next_char(A) mp->lig_kern[(A)].b1 @d op_byte(A) mp->lig_kern[(A)].b2 @d rem_byte(A) mp->lig_kern[(A)].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(A) mp->exten[(A)].b0 /* |top| piece in a recipe */ @d ext_mid(A) mp->exten[(A)].b1 /* |mid| piece in a recipe */ @d ext_bot(A) mp->exten[(A)].b2 /* |bot| piece in a recipe */ @d ext_rep(A) mp->exten[(A)].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. @^design size@> \hang|param[2]=space| is the normal spacing between words in text. Note that character 040 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 max_tfm_int 32510 @d undefined_label max_tfm_int /* an undefined local label */ @<Glob...@>= #define TFM_ITEMS 257 eight_bits bc; eight_bits ec; /* smallest and largest character codes shipped out */ mp_node tfm_width[TFM_ITEMS]; /* \&{charwd} values */ mp_node tfm_height[TFM_ITEMS]; /* \&{charht} values */ mp_node tfm_depth[TFM_ITEMS]; /* \&{chardp} values */ mp_node tfm_ital_corr[TFM_ITEMS]; /* \&{charic} values */ boolean char_exists[TFM_ITEMS]; /* has this code been shipped out? */ int char_tag[TFM_ITEMS]; /* |remainder| category */ int char_remainder[TFM_ITEMS]; /* the |remainder| byte */ char *header_byte; /* bytes of the \.{TFM} header */ int header_last; /* last initialized \.{TFM} header byte */ int header_size; /* size of the \.{TFM} header */ four_quarters *lig_kern; /* the ligature/kern table */ short nl; /* the number of ligature/kern steps so far */ scaled *kern; /* distinct kerning amounts */ short nk; /* the number of distinct kerns so far */ four_quarters exten[TFM_ITEMS]; /* extensible character recipes */ short ne; /* the number of extensible characters so far */ scaled *param; /* \&{fontinfo} parameters */ short np; /* the largest \&{fontinfo} parameter specified so far */ short nw; short nh; short nd; short ni; /* sizes of \.{TFM} subtables */ short skip_table[TFM_ITEMS]; /* local label status */ boolean lk_started; /* has there been a lig/kern step in this command yet? */ integer bchar; /* right boundary character */ short bch_label; /* left boundary starting location */ short ll; short lll; /* registers used for lig/kern processing */ short label_loc[257]; /* lig/kern starting addresses */ eight_bits label_char[257]; /* characters for |label_loc| */ short label_ptr; /* highest position occupied in |label_loc| */ @ @<Allocate or initialize ...@>= mp->header_last = 7; mp->header_size = 128; /* just for init */ mp->header_byte = xmalloc (mp->header_size, sizeof (char)); @ @<Dealloc variables@>= xfree (mp->header_byte); xfree (mp->lig_kern); xfree (mp->kern); xfree (mp->param); @ @<Set init...@>= for (k = 0; k <= 255; k++) { mp->tfm_width[k] = 0; mp->tfm_height[k] = 0; mp->tfm_depth[k] = 0; mp->tfm_ital_corr[k] = 0; mp->char_exists[k] = false; mp->char_tag[k] = no_tag; mp->char_remainder[k] = 0; mp->skip_table[k] = undefined_label; } memset (mp->header_byte, 0, (size_t) mp->header_size); mp->bc = 255; mp->ec = 0; mp->nl = 0; mp->nk = 0; mp->ne = 0; mp->np = 0; internal_value (mp_boundary_char) = -unity; mp->bch_label = undefined_label; mp->label_loc[0] = -1; mp->label_ptr = 0; @ @<Declarations@>= static mp_node mp_tfm_check (MP mp, quarterword m); @ @c static mp_node mp_tfm_check (MP mp, quarterword m) { mp_node p = mp_get_value_node (mp); if (abs (internal_value (m)) >= fraction_half) { print_err ("Enormous "); mp_print (mp, internal_name (m)); @.Enormous charwd...@> @.Enormous chardp...@> @.Enormous charht...@> @.Enormous charic...@> @.Enormous designsize...@>; mp_print (mp, " has been reduced"); help1 ("Font metric dimensions must be less than 2048pt."); mp_put_get_error (mp); if (internal_value (m) > 0) set_value (p, (fraction_half - 1)); else set_value (p, (1 - fraction_half)); } else { set_value (p, internal_value (m)); } return p; } @ @<Store the width information for character code~|c|@>= if (c < mp->bc) mp->bc = (eight_bits) c; if (c > mp->ec) mp->ec = (eight_bits) c; mp->char_exists[c] = true; mp_xfree (mp->tfm_width[c]); mp->tfm_width[c] = mp_tfm_check (mp, mp_char_wd); mp_xfree (mp->tfm_height[c]); mp->tfm_height[c] = mp_tfm_check (mp, mp_char_ht); mp_xfree (mp->tfm_depth[c]); mp->tfm_depth[c] = mp_tfm_check (mp, mp_char_dp); mp_xfree (mp->tfm_ital_corr[c]); mp->tfm_ital_corr[c] = mp_tfm_check (mp, mp_char_ic) @ Now let's consider \MP's special \.{TFM}-oriented commands. @<Cases of |do_statement|...@>= case tfm_command: mp_do_tfm_command (mp); break; @ @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...@>= mp_primitive (mp, "charlist", tfm_command, char_list_code); @:char_list_}{\&{charlist} primitive@>; mp_primitive (mp, "ligtable", tfm_command, lig_table_code); @:lig_table_}{\&{ligtable} primitive@>; mp_primitive (mp, "extensible", tfm_command, extensible_code); @:extensible_}{\&{extensible} primitive@>; mp_primitive (mp, "headerbyte", tfm_command, header_byte_code); @:header_byte_}{\&{headerbyte} primitive@>; mp_primitive (mp, "fontdimen", tfm_command, font_dimen_code); @:font_dimen_}{\&{fontdimen} primitive@> @ @<Cases of |print_cmd...@>= case tfm_command: switch (m) { case char_list_code: mp_print (mp, "charlist"); break; case lig_table_code: mp_print (mp, "ligtable"); break; case extensible_code: mp_print (mp, "extensible"); break; case header_byte_code: mp_print (mp, "headerbyte"); break; default: mp_print (mp, "fontdimen"); break; } break; @ @<Declare action procedures for use by |do_statement|@>= static eight_bits mp_get_code (MP mp); @ @c eight_bits mp_get_code (MP mp) { /* scans a character code value */ integer c; /* the code value found */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type == mp_known) { c = mp_round_unscaled (mp, cur_exp_value ()); if (c >= 0) if (c < 256) return (eight_bits) c; } else if (mp->cur_exp.type == mp_string_type) { if (length (cur_exp_str ()) == 1) { c = (integer) (*(cur_exp_str ()->str)); return (eight_bits) c; } } 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."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); c = 0; return (eight_bits) c; } @ @<Declare action procedures for use by |do_statement|@>= static void mp_set_tag (MP mp, halfword c, quarterword t, halfword r); @ @c void mp_set_tag (MP mp, halfword c, quarterword t, halfword r) { if (mp->char_tag[c] == no_tag) { mp->char_tag[c] = t; mp->char_remainder[c] = r; if (t == lig_tag) { mp->label_ptr++; mp->label_loc[mp->label_ptr] = (short) r; mp->label_char[mp->label_ptr] = (eight_bits) c; } } else { @<Complain about a character tag conflict@>; } } @ @<Complain about a character tag conflict@>= { print_err ("Character "); if ((c > ' ') && (c < 127)) mp_print_char (mp, xord (c)); else if (c == 256) mp_print (mp, "||"); else { mp_print (mp, "code "); mp_print_int (mp, c); }; mp_print (mp, " is already "); @.Character c is already...@>; switch (mp->char_tag[c]) { case lig_tag: mp_print (mp, "in a ligtable"); break; case list_tag: mp_print (mp, "in a charlist"); break; case ext_tag: mp_print (mp, "extensible"); break; } /* 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."); mp_put_get_error (mp); } @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_tfm_command (MP mp); @ @c void mp_do_tfm_command (MP mp) { int c, cc; /* character codes */ int k; /* index into the |kern| array */ int j; /* index into |header_byte| or |param| */ mp_value new_expr; memset(&new_expr,0,sizeof(mp_value)); switch (mp->cur_mod) { case char_list_code: c = mp_get_code (mp); /* we will store a list of character successors */ while (mp->cur_cmd == colon) { cc = mp_get_code (mp); mp_set_tag (mp, c, list_tag, cc); c = cc; }; break; case lig_table_code: if (mp->lig_kern == NULL) mp->lig_kern = xmalloc ((max_tfm_int + 1), sizeof (four_quarters)); if (mp->kern == NULL) mp->kern = xmalloc ((max_tfm_int + 1), sizeof (scaled)); @<Store a list of ligature/kern steps@>; break; case extensible_code: @<Define an extensible recipe@>; break; case header_byte_code: case font_dimen_code: c = mp->cur_mod; mp_get_x_next (mp); mp_scan_expression (mp); if ((mp->cur_exp.type != mp_known) || (cur_exp_value () < half_unit)) { 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."); mp_put_get_error (mp); } else { j = mp_round_unscaled (mp, cur_exp_value ()); if (mp->cur_cmd != colon) { mp_missing_err (mp, ":"); @.Missing `:'@>; help1 ("A colon should follow a headerbyte or fontinfo location."); mp_back_error (mp); } if (c == header_byte_code) { @<Store a list of header bytes@>; } else { if (mp->param == NULL) mp->param = xmalloc ((max_tfm_int + 1), sizeof (scaled)); @<Store a list of font dimensions@>; } } break; } /* there are no other cases */ } @ @<Store a list of ligature/kern steps@>= { mp->lk_started = false; CONTINUE: mp_get_x_next (mp); if ((mp->cur_cmd == skip_to) && mp->lk_started) @<Process a |skip_to| command and |goto done|@>; if (mp->cur_cmd == bchar_label) { c = 256; mp->cur_cmd = colon; } else { mp_back_input (mp); c = mp_get_code (mp); }; if ((mp->cur_cmd == colon) || (mp->cur_cmd == double_colon)) { @<Record a label in a lig/kern subprogram and |goto continue|@>; } if (mp->cur_cmd == lig_kern_token) { @<Compile a ligature/kern command@>; } else { print_err ("Illegal ligtable step"); @.Illegal ligtable step@>; help1 ("I was looking for `=:' or `kern' here."); mp_back_error (mp); next_char (mp->nl) = qi (0); op_byte (mp->nl) = qi (0); rem_byte (mp->nl) = qi (0); skip_byte (mp->nl) = stop_flag + 1; /* this specifies an unconditional stop */ } if (mp->nl == max_tfm_int) mp_fatal_error (mp, "ligtable too large"); mp->nl++; if (mp->cur_cmd == comma) goto CONTINUE; if (skip_byte (mp->nl - 1) < stop_flag) skip_byte (mp->nl - 1) = stop_flag; } DONE: @ @<Put each...@>= mp_primitive (mp, "=:", lig_kern_token, 0); @:=:_}{\.{=:} primitive@>; mp_primitive (mp, "=:|", lig_kern_token, 1); @:=:/_}{\.{=:\char'174} primitive@>; mp_primitive (mp, "=:|>", lig_kern_token, 5); @:=:/>_}{\.{=:\char'174>} primitive@>; mp_primitive (mp, "|=:", lig_kern_token, 2); @:=:/_}{\.{\char'174=:} primitive@>; mp_primitive (mp, "|=:>", lig_kern_token, 6); @:=:/>_}{\.{\char'174=:>} primitive@>; mp_primitive (mp, "|=:|", lig_kern_token, 3); @:=:/_}{\.{\char'174=:\char'174} primitive@>; mp_primitive (mp, "|=:|>", lig_kern_token, 7); @:=:/>_}{\.{\char'174=:\char'174>} primitive@>; mp_primitive (mp, "|=:|>>", lig_kern_token, 11); @:=:/>_}{\.{\char'174=:\char'174>>} primitive@>; mp_primitive (mp, "kern", lig_kern_token, 128); @:kern_}{\&{kern} primitive@> @ @<Cases of |print_cmd...@>= case lig_kern_token: switch (m) { case 0: mp_print (mp, "=:"); break; case 1: mp_print (mp, "=:|"); break; case 2: mp_print (mp, "|=:"); break; case 3: mp_print (mp, "|=:|"); break; case 5: mp_print (mp, "=:|>"); break; case 6: mp_print (mp, "|=:>"); break; case 7: mp_print (mp, "|=:|>"); break; case 11: mp_print (mp, "|=:|>>"); break; default: mp_print (mp, "kern"); break; } break; @ 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(A) mp->ll=(A); do { mp->lll=qo(skip_byte(mp->ll)); skip_byte(mp->ll)=stop_flag; mp->ll=(short)(mp->ll-mp->lll); } while (mp->lll!=0) @d skip_error(A) { print_err("Too far to skip"); @.Too far to skip@> help1("At most 127 lig/kern steps can separate skipto1 from 1::."); mp_error(mp); cancel_skips((A)); } @<Process a |skip_to| command and |goto done|@>= { c = mp_get_code (mp); if (mp->nl - mp->skip_table[c] > 128) { skip_error (mp->skip_table[c]); mp->skip_table[c] = (short) undefined_label; } if (mp->skip_table[c] == undefined_label) skip_byte (mp->nl - 1) = qi (0); else skip_byte (mp->nl - 1) = qi (mp->nl - mp->skip_table[c] - 1); mp->skip_table[c] = (short) (mp->nl - 1); goto DONE; } @ @<Record a label in a lig/kern subprogram and |goto continue|@>= { if (mp->cur_cmd == colon) { if (c == 256) mp->bch_label = mp->nl; else mp_set_tag (mp, c, lig_tag, mp->nl); } else if (mp->skip_table[c] < undefined_label) { mp->ll = mp->skip_table[c]; mp->skip_table[c] = undefined_label; do { mp->lll = qo (skip_byte (mp->ll)); if (mp->nl - mp->ll > 128) { skip_error (mp->ll); goto CONTINUE; } skip_byte (mp->ll) = qi (mp->nl - mp->ll - 1); mp->ll = (short) (mp->ll - mp->lll); } while (mp->lll != 0); } goto CONTINUE; } @ @<Compile a ligature/kern...@>= { next_char (mp->nl) = qi (c); skip_byte (mp->nl) = qi (0); if (mp->cur_mod < 128) { /* ligature op */ op_byte (mp->nl) = qi (mp->cur_mod); rem_byte (mp->nl) = qi (mp_get_code (mp)); } else { mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_known) { 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."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } mp->kern[mp->nk] = cur_exp_value (); k = 0; while (mp->kern[k] != cur_exp_value ()) incr (k); if (k == mp->nk) { if (mp->nk == max_tfm_int) mp_fatal_error (mp, "too many TFM kerns"); mp->nk++; } op_byte (mp->nl) = qi (kern_flag + (k / 256)); rem_byte (mp->nl) = qi ((k % 256)); } mp->lk_started = true; } @ @d missing_extensible_punctuation(A) { mp_missing_err(mp, (A)); @.Missing `\char`\#'@> help1("I'm processing `extensible c: t,m,b,r'."); mp_back_error(mp); } @<Define an extensible recipe@>= { if (mp->ne == 256) mp_fatal_error (mp, "too many extensible recipies"); c = mp_get_code (mp); mp_set_tag (mp, c, ext_tag, mp->ne); if (mp->cur_cmd != colon) missing_extensible_punctuation (":"); ext_top (mp->ne) = qi (mp_get_code (mp)); if (mp->cur_cmd != comma) missing_extensible_punctuation (","); ext_mid (mp->ne) = qi (mp_get_code (mp)); if (mp->cur_cmd != comma) missing_extensible_punctuation (","); ext_bot (mp->ne) = qi (mp_get_code (mp)); if (mp->cur_cmd != comma) missing_extensible_punctuation (","); ext_rep (mp->ne) = qi (mp_get_code (mp)); mp->ne++; } @ The header could contain ASCII zeroes, so can't use |strdup|. @<Store a list of header bytes@>= j--; do { if (j >= mp->header_size) { size_t l = (size_t) (mp->header_size + (mp->header_size / 4)); char *t = xmalloc (l, 1); memset (t, 0, l); (void) memcpy (t, mp->header_byte, (size_t) mp->header_size); xfree (mp->header_byte); mp->header_byte = t; mp->header_size = (int) l; } mp->header_byte[j] = (char) mp_get_code (mp); incr (j); incr (mp->header_last); } while (mp->cur_cmd == comma) @ @<Store a list of font dimensions@>= do { if (j > max_tfm_int) mp_fatal_error (mp, "too many fontdimens"); while (j > mp->np) { mp->np++; mp->param[mp->np] = 0; }; mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_known) { exp_err ("Improper font parameter"); @.Improper font parameter@>; help1 ("I'm zeroing this one. Proceed, with fingers crossed."); new_expr.data.val = 0; mp_put_get_flush_error (mp, new_expr); } mp->param[j] = cur_exp_value (); incr (j); } while (mp->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@>= mp->inf_val = mp_get_value_node (mp); set_value (mp->inf_val, fraction_four); @ @<Free table entries@>= mp_free_value_node (mp, mp->inf_val); @ 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 |mp_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 mp_link(mp->temp_head)=mp->inf_val @c static mp_node mp_sort_in (MP mp, scaled v) { mp_node p, q, r; /* list manipulation registers */ p = mp->temp_head; while (1) { q = mp_link (p); if (v <= value (q)) break; p = q; } if (v < value (q)) { r = mp_get_value_node (mp); set_value (r, v); mp_link (r) = q; mp_link (p) = r; } return mp_link (p); } @ 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. @c static integer mp_min_cover (MP mp, scaled d) { mp_node p; /* runs through the current list */ scaled l; /* the least element covered by the current interval */ integer m; /* lower bound on the size of the minimum cover */ m = 0; p = mp_link (mp->temp_head); mp->perturbation = EL_GORDO; while (p != mp->inf_val) { incr (m); l = value (p); do { p = mp_link (p); } while (value (p) <= l + d); if (value (p) - l < mp->perturbation) mp->perturbation = value (p) - l; } return m; } @ @<Glob...@>= scaled perturbation; /* quantity related to \.{TFM} rounding */ integer excess; /* 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. @c static scaled mp_threshold (MP mp, integer m) { scaled d; /* lower bound on the smallest interval size */ mp->excess = mp_min_cover (mp, 0) - m; if (mp->excess <= 0) { return 0; } else { do { d = mp->perturbation; } while (mp_min_cover (mp, d + d) > m); while (mp_min_cover (mp, d) > m) d = mp->perturbation; return d; } } @ The |skimp| procedure reduces the current list to at most |m| entries, by changing values if necessary. It also sets |mp_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|. @c static integer mp_skimp (MP mp, integer m) { scaled d; /* the size of intervals being coalesced */ mp_node p, q, r; /* list manipulation registers */ scaled l; /* the least value in the current interval */ scaled v; /* a compromise value */ d = mp_threshold (mp, m); mp->perturbation = 0; q = mp->temp_head; m = 0; p = mp_link (mp->temp_head); while (p != mp->inf_val) { incr (m); l = value (p); set_mp_info (p, m); if (value (mp_link (p)) <= l + d) { @<Replace an interval of values by its midpoint@>; } q = p; p = mp_link (p); } return m; } @ @<Replace an interval...@>= { do { p = mp_link (p); set_mp_info (p, m); decr (mp->excess); if (mp->excess == 0) d = 0; } while (value (mp_link (p)) <= l + d); v = l + halfp (value (p) - l); if (value (p) - v > mp->perturbation) mp->perturbation = value (p) - v; r = q; do { r = mp_link (r); set_value (r, v); } while (r != p); mp_link (q) = p; /* remove duplicate values from the current list */ } @ A warning message is issued whenever something is perturbed by more than 1/16\thinspace pt. @c static void mp_tfm_warning (MP mp, quarterword m) { mp_print_nl (mp, "(some "); mp_print (mp, internal_name (m)); @.some charwds...@> @.some chardps...@> @.some charhts...@> @.some charics...@>; mp_print (mp, " values had to be adjusted by as much as "); mp_print_scaled (mp, mp->perturbation); mp_print (mp, "pt)"); } @ 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 = mp->bc; k <= mp->ec; k++) { if (mp->char_exists[k]) mp->tfm_width[k] = mp_sort_in (mp, value (mp->tfm_width[k])); } mp->nw = (short) (mp_skimp (mp, 255) + 1); mp->dimen_head[1] = mp_link (mp->temp_head); if (mp->perturbation >= 010000) mp_tfm_warning (mp, mp_char_wd) @ @<Glob...@>= mp_node dimen_head[5]; /* 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 = mp->bc; k <= mp->ec; k++) { if (mp->char_exists[k]) { if (mp->tfm_height[k] == 0) mp->tfm_height[k] = mp->zero_val; else mp->tfm_height[k] = mp_sort_in (mp, value (mp->tfm_height[k])); } } mp->nh = (short) (mp_skimp (mp, 15) + 1); mp->dimen_head[2] = mp_link (mp->temp_head); if (mp->perturbation >= 010000) mp_tfm_warning (mp, mp_char_ht); clear_the_list; for (k = mp->bc; k <= mp->ec; k++) { if (mp->char_exists[k]) { if (mp->tfm_depth[k] == 0) mp->tfm_depth[k] = mp->zero_val; else mp->tfm_depth[k] = mp_sort_in (mp, value (mp->tfm_depth[k])); } } mp->nd = (short) (mp_skimp (mp, 15) + 1); mp->dimen_head[3] = mp_link (mp->temp_head); if (mp->perturbation >= 010000) mp_tfm_warning (mp, mp_char_dp); clear_the_list; for (k = mp->bc; k <= mp->ec; k++) { if (mp->char_exists[k]) { if (mp->tfm_ital_corr[k] == 0) mp->tfm_ital_corr[k] = mp->zero_val; else mp->tfm_ital_corr[k] = mp_sort_in (mp, value (mp->tfm_ital_corr[k])); } } mp->ni = (short) (mp_skimp (mp, 63) + 1); mp->dimen_head[4] = mp_link (mp->temp_head); if (mp->perturbation >= 010000) mp_tfm_warning (mp, mp_char_ic) @ @<Initialize table entries@>= mp->zero_val = mp_get_value_node (mp); set_value (mp->zero_val, 0); set_mp_info (mp->zero_val, 0); @ @<Free table entries@>= mp_free_value_node (mp, mp->zero_val); @ Bytes 5--8 of the header are set to the design size, unless the user has some crazy reason for specifying them differently. @^design size@> 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_value(mp_design_size))|} < \\{three\_bytes}.$$ @d three_bytes 0100000000 /* $2^{24}$ */ @c static void mp_fix_design_size (MP mp) { scaled d; /* the design size */ d = internal_value (mp_design_size); if ((d < unity) || (d >= fraction_half)) { if (d != 0) mp_print_nl (mp, "(illegal design size has been changed to 128pt)"); @.illegal design size...@>; d = 040000000; internal_value (mp_design_size) = d; } if (mp->header_byte[4] == 0 && mp->header_byte[5] == 0 && mp->header_byte[6] == 0 && mp->header_byte[7] == 0) { mp->header_byte[4] = (char) (d / 04000000); mp->header_byte[5] = (char) ((d / 4096) % 256); mp->header_byte[6] = (char) ((d / 16) % 256); mp->header_byte[7] = (char) ((d % 16) * 16); } mp->max_tfm_dimen = 16 * internal_value (mp_design_size) - 1 - internal_value (mp_design_size) / 010000000; if (mp->max_tfm_dimen >= fraction_half) mp->max_tfm_dimen = fraction_half - 1; } @ 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. @c static integer mp_dimen_out (MP mp, scaled x) { if (abs (x) > mp->max_tfm_dimen) { incr (mp->tfm_changed); if (x > 0) x = mp->max_tfm_dimen; else x = -mp->max_tfm_dimen; } x = mp_make_scaled (mp, x * 16, internal_value (mp_design_size)); return x; } @ @<Glob...@>= scaled max_tfm_dimen; /* bound on widths, heights, kerns, etc. */ integer tfm_changed; /* 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@> @c static void mp_fix_check_sum (MP mp) { eight_bits k; /* runs through character codes */ eight_bits B1, B2, B3, B4; /* bytes of the check sum */ integer x; /* hash value used in check sum computation */ if (mp->header_byte[0] == 0 && mp->header_byte[1] == 0 && mp->header_byte[2] == 0 && mp->header_byte[3] == 0) { @<Compute a check sum in |(b1,b2,b3,b4)|@>; mp->header_byte[0] = (char) B1; mp->header_byte[1] = (char) B2; mp->header_byte[2] = (char) B3; mp->header_byte[3] = (char) B4; return; } } @ @<Compute a check sum in |(b1,b2,b3,b4)|@>= B1 = mp->bc; B2 = mp->ec; B3 = mp->bc; B4 = mp->ec; mp->tfm_changed = 0; for (k = mp->bc; k <= mp->ec; k++) { if (mp->char_exists[k]) { x = mp_dimen_out (mp, value (mp->tfm_width[k])) + (k + 4) * 020000000; /* this is positive */ B1 = (eight_bits) ((B1 + B1 + x) % 255); B2 = (eight_bits) ((B2 + B2 + x) % 253); B3 = (eight_bits) ((B3 + B3 + x) % 251); B4 = (eight_bits) ((B4 + B4 + x) % 247); } if (k == mp->ec) break; } @ Finally we're ready to actually write the \.{TFM} information. Here are some utility routines for this purpose. @d tfm_out(A) do { /* output one byte to |tfm_file| */ unsigned char s=(unsigned char)(A); (mp->write_binary_file)(mp,mp->tfm_file,(void *)&s,1); } while (0) @c static void mp_tfm_two (MP mp, integer x) { /* output two bytes to |tfm_file| */ tfm_out (x / 256); tfm_out (x % 256); } static void mp_tfm_four (MP mp, integer x) { /* output four bytes to |tfm_file| */ if (x >= 0) tfm_out (x / three_bytes); else { x = x + 010000000000; /* use two's complement for negative values */ x = x + 010000000000; tfm_out ((x / three_bytes) + 128); }; x = x % three_bytes; tfm_out (x / unity); x = x % unity; tfm_out (x / 0400); tfm_out (x % 0400); } static void mp_tfm_qqqq (MP mp, four_quarters x) { /* output four quarterwords to |tfm_file| */ tfm_out (qo (x.b0)); tfm_out (qo (x.b1)); tfm_out (qo (x.b2)); tfm_out (qo (x.b3)); } @ @<Finish the \.{TFM} file@>= if (mp->job_name == NULL) mp_open_log_file (mp); mp_pack_job_name (mp, ".tfm"); while (!mp_b_open_out (mp, &mp->tfm_file, mp_filetype_metrics)) mp_prompt_file_name (mp, "file name for font metrics", ".tfm"); mp->metric_file_name = xstrdup (mp->name_of_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@>; if (internal_value (mp_tracing_stats) > 0) @<Log the subfile sizes of the \.{TFM} file@>; mp_print_nl (mp, "Font metrics written on "); mp_print (mp, mp->metric_file_name); mp_print_char (mp, xord ('.')); @.Font metrics written...@>; (mp->close_file) (mp, mp->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 = mp->header_last; LH = (k + 4) / 4; /* this is the number of header words */ if (mp->bc > mp->ec) mp->bc = 1; /* if there are no characters, |ec=0| and |bc=1| */ @<Compute the ligature/kern program offset and implant the left boundary label@>; mp_tfm_two (mp, 6 + LH + (mp->ec - mp->bc + 1) + mp->nw + mp->nh + mp->nd + mp->ni + mp->nl + lk_offset + mp->nk + mp->ne + mp->np); /* this is the total number of file words that will be output */ mp_tfm_two (mp, LH); mp_tfm_two (mp, mp->bc); mp_tfm_two (mp, mp->ec); mp_tfm_two (mp, mp->nw); mp_tfm_two (mp, mp->nh); mp_tfm_two (mp, mp->nd); mp_tfm_two (mp, mp->ni); mp_tfm_two (mp, mp->nl + lk_offset); mp_tfm_two (mp, mp->nk); mp_tfm_two (mp, mp->ne); mp_tfm_two (mp, mp->np); for (k = 0; k < 4 * LH; k++) { tfm_out (mp->header_byte[k]); } @ @<Output the character information bytes...@>= for (k = mp->bc; k <= mp->ec; k++) { if (!mp->char_exists[k]) { mp_tfm_four (mp, 0); } else { tfm_out (mp_info (mp->tfm_width[k])); /* the width index */ tfm_out ((mp_info (mp->tfm_height[k])) * 16 + mp_info (mp->tfm_depth[k])); tfm_out ((mp_info (mp->tfm_ital_corr[k])) * 4 + mp->char_tag[k]); tfm_out (mp->char_remainder[k]); }; } mp->tfm_changed = 0; for (k = 1; k <= 4; k++) { mp_tfm_four (mp, 0); p = mp->dimen_head[k]; while (p != mp->inf_val) { mp_tfm_four (mp, mp_dimen_out (mp, value (p))); p = mp_link (p); } } @ 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...@>= mp->bchar = mp_round_unscaled (mp, internal_value (mp_boundary_char)); if ((mp->bchar < 0) || (mp->bchar > 255)) { mp->bchar = -1; mp->lk_started = false; lk_offset = 0; } else { mp->lk_started = true; lk_offset = 1; } @<Find the minimum |lk_offset| and adjust all remainders@>; if (mp->bch_label < undefined_label) { skip_byte (mp->nl) = qi (255); next_char (mp->nl) = qi (0); op_byte (mp->nl) = qi (((mp->bch_label + lk_offset) / 256)); rem_byte (mp->nl) = qi (((mp->bch_label + lk_offset) % 256)); mp->nl++; /* possibly |nl=lig_table_size+1| */ } @ @<Find the minimum |lk_offset|...@>= k = mp->label_ptr; /* pointer to the largest unallocated label */ if (mp->label_loc[k] + lk_offset > 255) { lk_offset = 0; mp->lk_started = false; /* location 0 can do double duty */ do { mp->char_remainder[mp->label_char[k]] = lk_offset; while (mp->label_loc[k - 1] == mp->label_loc[k]) { decr (k); mp->char_remainder[mp->label_char[k]] = lk_offset; } incr (lk_offset); decr (k); } while (!(lk_offset + mp->label_loc[k] < 256)); /* N.B.: |lk_offset=256| satisfies this when |k=0| */ } if (lk_offset > 0) { while (k > 0) { mp->char_remainder[mp->label_char[k]] = mp->char_remainder[mp->label_char[k]] + lk_offset; decr (k); } } @ @<Output the ligature/kern program@>= for (k = 0; k <= 255; k++) { if (mp->skip_table[k] < undefined_label) { mp_print_nl (mp, "(local label "); mp_print_int (mp, k); mp_print (mp, ":: was missing)"); @.local label l:: was missing@>; cancel_skips (mp->skip_table[k]); } } if (mp->lk_started) { /* |lk_offset=1| for the special |bchar| */ tfm_out (255); tfm_out (mp->bchar); mp_tfm_two (mp, 0); } else { for (k = 1; k <= lk_offset; k++) { /* output the redirection specs */ mp->ll = mp->label_loc[mp->label_ptr]; if (mp->bchar < 0) { tfm_out (254); tfm_out (0); } else { tfm_out (255); tfm_out (mp->bchar); }; mp_tfm_two (mp, mp->ll + lk_offset); do { mp->label_ptr--; } while (!(mp->label_loc[mp->label_ptr] < mp->ll)); } } for (k = 0; k < mp->nl; k++) mp_tfm_qqqq (mp, mp->lig_kern[k]); for (k = 0; k < mp->nk; k++) mp_tfm_four (mp, mp_dimen_out (mp, mp->kern[k])) @ @<Output the extensible character recipes...@>= for (k = 0; k < mp->ne; k++) mp_tfm_qqqq (mp, mp->exten[k]); for (k = 1; k <= mp->np; k++) { if (k == 1) { if (abs (mp->param[1]) < fraction_half) { mp_tfm_four (mp, mp->param[1] * 16); } else { incr (mp->tfm_changed); if (mp->param[1] > 0) mp_tfm_four (mp, max_integer); else mp_tfm_four (mp, -max_integer); } } else { mp_tfm_four (mp, mp_dimen_out (mp, mp->param[k])); } } if (mp->tfm_changed > 0) { if (mp->tfm_changed == 1) { mp_print_nl (mp, "(a font metric dimension"); @.a font metric dimension...@> } else { mp_print_nl (mp, "("); mp_print_int (mp, mp->tfm_changed); @.font metric dimensions...@>; mp_print (mp, " font metric dimensions"); } mp_print (mp, " had to be decreased)"); } @ @<Log the subfile sizes of the \.{TFM} file@>= { char s[200]; wlog_ln (" "); if (mp->bch_label < undefined_label) mp->nl--; mp_snprintf (s, 128, "(You used %iw,%ih,%id,%ii,%il,%ik,%ie,%ip metric file positions)", mp->nw, mp->nh, mp->nd, mp->ni, mp->nl, mp->nk, mp->ne, mp->np); wlog_ln (s); } @* 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...@>= void *tfm_infile; @ 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...@>= typedef unsigned int font_number; /* |0..font_max| */ @ 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...@>= font_number font_max; /* maximum font number for included text fonts */ size_t font_mem_size; /* number of words for \.{TFM} information for text fonts */ font_data *font_info; /* height, width, and depth data */ char **font_enc_name; /* encoding names, if any */ boolean *font_ps_name_fixed; /* are the postscript names fixed already? */ size_t next_fmem; /* next unused entry in |font_info| */ font_number last_fnum; /* last font number used so far */ integer *font_dsize; /* 16 times the ``design'' size in \ps\ points */ char **font_name; /* name as specified in the \&{infont} command */ char **font_ps_name; /* PostScript name for use when |internal[mp_prologues]>0| */ font_number last_ps_fnum; /* last valid |font_ps_name| index */ eight_bits *font_bc; eight_bits *font_ec; /* first and last character code */ int *char_base; /* base address for |char_info| */ int *width_base; /* index for zeroth character width */ int *height_base; /* index for zeroth character height */ int *depth_base; /* index for zeroth character depth */ mp_node *font_sizes; @ @<Allocate or initialize ...@>= mp->font_mem_size = 10000; mp->font_info = xmalloc ((mp->font_mem_size + 1), sizeof (font_data)); memset (mp->font_info, 0, sizeof (font_data) * (mp->font_mem_size + 1)); mp->last_fnum = null_font; @ @<Dealloc variables@>= for (k = 1; k <= (int) mp->last_fnum; k++) { xfree (mp->font_enc_name[k]); xfree (mp->font_name[k]); xfree (mp->font_ps_name[k]); } mp_xfree (mp->tfm_width[0]); mp_xfree (mp->tfm_height[0]); mp_xfree (mp->tfm_depth[0]); mp_xfree (mp->tfm_ital_corr[0]); xfree (mp->font_info); xfree (mp->font_enc_name); xfree (mp->font_ps_name_fixed); xfree (mp->font_dsize); xfree (mp->font_name); xfree (mp->font_ps_name); xfree (mp->font_bc); xfree (mp->font_ec); xfree (mp->char_base); xfree (mp->width_base); xfree (mp->height_base); xfree (mp->depth_base); xfree (mp->font_sizes); @ @c void mp_reallocate_fonts (MP mp, font_number l) { font_number f; XREALLOC (mp->font_enc_name, l, char *); XREALLOC (mp->font_ps_name_fixed, l, boolean); XREALLOC (mp->font_dsize, l, integer); XREALLOC (mp->font_name, l, char *); XREALLOC (mp->font_ps_name, l, char *); XREALLOC (mp->font_bc, l, eight_bits); XREALLOC (mp->font_ec, l, eight_bits); XREALLOC (mp->char_base, l, int); XREALLOC (mp->width_base, l, int); XREALLOC (mp->height_base, l, int); XREALLOC (mp->depth_base, l, int); XREALLOC (mp->font_sizes, l, mp_node); for (f = (mp->last_fnum + 1); f <= l; f++) { mp->font_enc_name[f] = NULL; mp->font_ps_name_fixed[f] = false; mp->font_name[f] = NULL; mp->font_ps_name[f] = NULL; mp->font_sizes[f] = NULL; } mp->font_max = l; } @ @<Internal library declarations@>= void mp_reallocate_fonts (MP mp, font_number l); @ 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 */ @<Set initial...@>= mp->font_dsize[null_font] = 0; mp->font_bc[null_font] = 1; mp->font_ec[null_font] = 0; mp->char_base[null_font] = 0; mp->width_base[null_font] = 0; mp->height_base[null_font] = 0; mp->depth_base[null_font] = 0; mp->next_fmem = 0; mp->last_fnum = null_font; mp->last_ps_fnum = null_font; { static char nullfont_name[] = "nullfont"; static char nullfont_psname[] = ""; mp->font_name[null_font] = nullfont_name; mp->font_ps_name[null_font] = nullfont_psname; } mp->font_ps_name_fixed[null_font] = false; mp->font_enc_name[null_font] = NULL; mp->font_sizes[null_font] = NULL; @ Each |char_info| word is of type |four_quarters|. The |b0| field contains 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_mp_info(f,c)| and the width is $$\hbox{|char_width(f,char_mp_info(f,c)).sc|.}$$ @d char_mp_info(A,B) mp->font_info[mp->char_base[(A)]+(B)].qqqq @d char_width(A,B) mp->font_info[mp->width_base[(A)]+(B).b0].sc @d char_height(A,B) mp->font_info[mp->height_base[(A)]+(B).b1].sc @d char_depth(A,B) mp->font_info[mp->depth_base[(A)]+(B).b2].sc @d ichar_exists(A) ((A).b0>0) @ 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|. @<Declarations@>= static font_number mp_find_font (MP mp, char *f); @ @c font_number mp_find_font (MP mp, char *f) { font_number n; for (n = 0; n <= mp->last_fnum; n++) { if (mp_xstrcmp (f, mp->font_name[n]) == 0) { return n; } } n = mp_read_font_info (mp, f); return n; } @ This is an interface function for getting the width of character, as a double in ps units @c double mp_get_char_dimension (MP mp, char *fname, int c, int t) { unsigned n; four_quarters cc; font_number f = 0; double w = -1.0; for (n = 0; n <= mp->last_fnum; n++) { if (mp_xstrcmp (fname, mp->font_name[n]) == 0) { f = n; break; } } if (f == 0) return 0.0; cc = char_mp_info (f, c); if (!ichar_exists (cc)) return 0.0; if (t == 'w') w = (double) char_width (f, cc); else if (t == 'h') w = (double) char_height (f, cc); else if (t == 'd') w = (double) char_depth (f, cc); return w / 655.35 * (72.27 / 72); } @ @<Exported function ...@>= double mp_get_char_dimension (MP mp, char *fname, int n, int t); @ 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|@>= { new_expr.data.val = (mp->font_dsize[mp_find_font (mp, mp_str (mp, cur_exp_str ()))] + 8) / 16; mp_flush_cur_exp (mp, new_expr); } @ 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. @<Declarations@>= static void mp_lost_warning (MP mp, font_number f, int k); @ @c void mp_lost_warning (MP mp, font_number f, int k) { if (internal_value (mp_tracing_lost_chars) > 0) { mp_begin_diagnostic (mp); if (mp->selector == log_only) incr (mp->selector); mp_print_nl (mp, "Missing character: There is no "); @.Missing character@>; mp_print_int (mp, k); mp_print (mp, " in font "); mp_print (mp, mp->font_name[f]); mp_print_char (mp, xord ('!')); mp_end_diagnostic (mp, false); } } @ 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. @<Declarations@>= static void mp_set_text_box (MP mp, mp_node p); @ @c void mp_set_text_box (MP mp, mp_node p) { font_number f; /* |mp_font_n(p)| */ ASCII_code bc, ec; /* range of valid characters for font |f| */ size_t k, kk; /* current character and character to stop at */ four_quarters cc; /* the |char_info| for the current character */ scaled h, d; /* dimensions of the current character */ width_val (p) = 0; height_val (p) = -EL_GORDO; depth_val (p) = -EL_GORDO; f = (font_number) mp_font_n (p); bc = mp->font_bc[f]; ec = mp->font_ec[f]; kk = length (mp_text_p (p)); k = 0; while (k < kk) { @<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@>; } @ @<Adjust |p|'s bounding box to contain |str_pool[k]|; advance |k|@>= { if ((*(mp_text_p (p)->str + k) < bc) || (*(mp_text_p (p)->str + k) > ec)) { mp_lost_warning (mp, f, *(mp_text_p (p)->str + k)); } else { cc = char_mp_info (f, *(mp_text_p (p)->str + k)); if (!ichar_exists (cc)) { mp_lost_warning (mp, f, *(mp_text_p (p)->str + k)); } else { 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)) height_val (p) = h; if (d > depth_val (p)) depth_val (p) = d; } } incr (k); } @ 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)) { height_val (p) = 0; depth_val (p) = 0; } @ The new primitives fontmapfile and fontmapline. @<Declare action procedures for use by |do_statement|@>= static void mp_do_mapfile (MP mp); static void mp_do_mapline (MP mp); @ @c static void mp_do_mapfile (MP mp) { mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_string_type) { @<Complain about improper map operation@>; } else { mp_map_file (mp, cur_exp_str ()); } } static void mp_do_mapline (MP mp) { mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_string_type) { @<Complain about improper map operation@>; } else { mp_map_line (mp, cur_exp_str ()); } } @ @<Complain about improper map operation@>= { exp_err ("Unsuitable expression"); help1 ("Only known strings can be map files or map lines."); mp_put_get_error (mp); } @ To print |scaled| value to PDF output we need some subroutines to ensure accurary. @d max_integer 0x7FFFFFFF /* $2^{31}-1$ */ @<Glob...@>= integer ten_pow[10]; /* $10^0..10^9$ */ integer scaled_out; /* amount of |scaled| that was taken out in |divide_scaled| */ @ @<Set init...@>= mp->ten_pow[0] = 1; for (i = 1; i <= 9; i++) { mp->ten_pow[i] = 10 * mp->ten_pow[i - 1]; } @* 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@> This is the only backend function that remains in the main |mpost.w| file. There are just too many variable accesses needed for status reporting etcetera to make it worthwile to move the code to |psout.w|. @<Internal library declarations@>= void mp_open_output_file (MP mp); @ @c static void mp_append_to_template (MP mp, integer ff, integer c, boolean rounding) { if (internal_type (c) == mp_string_type) { char *ss = mp_str (mp, internal_string (c)); mp_print (mp, ss); } else if (internal_type (c) == mp_known) { if (rounding) { integer cc = mp_round_unscaled (mp, internal_value (c)); print_with_leading_zeroes (cc, ff); } else { mp_print_scaled (mp, internal_value (c)); } } } static char *mp_set_output_file_name (MP mp, integer c) { char *ss = NULL; /* filename extension proposal */ char *nn = NULL; /* temp string for str() */ unsigned old_setting; /* previous |selector| setting */ size_t i; /* indexes into |filename_template| */ integer f; /* field width */ str_room (1024); if (mp->job_name == NULL) mp_open_log_file (mp); if (internal_string (mp_output_template) == NULL) { char *s; /* a file extension derived from |c| */ if (c < 0) s = xstrdup (".ps"); else @<Use |c| to compute the file extension |s|@>; mp_pack_job_name (mp, s); free (s); ss = xstrdup (mp->name_of_file); } else { /* initializations */ str_number s, n, template; /* a file extension derived from |c| */ scaled saved_char_code = internal_value (mp_char_code); internal_value (mp_char_code) = (c * unity); if (internal_string (mp_job_name) == NULL) { if (mp->job_name == NULL) { mp->job_name = xstrdup ("mpout"); } @<Fix up |mp->internal[mp_job_name]|@>; } old_setting = mp->selector; mp->selector = new_string; i = 0; n = null_str; /* initialize */ template = internal_string (mp_output_template); while (i < length (template)) { f = 0; if (*(template->str + i) == '%') { CONTINUE: incr (i); if (i < length (template)) { switch (*(template->str + i)) { case 'j': mp_append_to_template (mp, f, mp_job_name, true); break; case 'c': if (internal_value (mp_char_code) < 0) { mp_print (mp, "ps"); } else { mp_append_to_template (mp, f, mp_char_code, true); } break; case 'o': mp_append_to_template (mp, f, mp_output_format, true); break; case 'd': mp_append_to_template (mp, f, mp_day, true); break; case 'm': mp_append_to_template (mp, f, mp_month, true); break; case 'y': mp_append_to_template (mp, f, mp_year, true); break; case 'H': mp_append_to_template (mp, f, mp_hour, true); break; case 'M': mp_append_to_template (mp, f, mp_minute, true); break; case '{': { /* look up a name */ size_t l = 0; size_t frst = i + 1; while (i < length (template)) { i++; if (*(template->str + i) == '}') break; l++; } if (l > 0) { mp_sym p = mp_id_lookup (mp, (char *) (template->str + frst), l, false); char *id = xmalloc (mp, (size_t) (l + 1)); (void) memcpy (id, (char *) (template->str + frst), (size_t) l); *(id + l) = '\0'; if (p == NULL) { char err[256]; mp_snprintf (err, 256, "requested identifier (%s) in outputtemplate not found.", id); mp_warn (mp, err); } else { if (eq_type (p) == internal_quantity) { if (equiv (p) == mp_output_template) { char err[256]; mp_snprintf (err, 256, "The appearance of outputtemplate inside outputtemplate is ignored."); mp_warn (mp, err); } else { mp_append_to_template (mp, f, equiv (p), false); } } else { char err[256]; mp_snprintf (err, 256, "requested identifier (%s) in outputtemplate is not an internal.", id); mp_warn (mp, err); } } free (id); } } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if ((f < 10)) f = (f * 10) + template->str[i] - '0'; goto CONTINUE; break; case '%': mp_print_char (mp, '%'); break; default: { char err[256]; mp_snprintf (err, 256, "requested format (%c) in outputtemplate is unknown.", *(template->str + i)); mp_warn (mp, err); } mp_print_char (mp, *(template->str + i)); } } } else { if (*(template->str + i) == '.') if (length (n) == 0) n = mp_make_string (mp); mp_print_char (mp, *(template->str + i)); }; incr (i); } s = mp_make_string (mp); internal_value (mp_char_code) = saved_char_code; mp->selector = old_setting; if (length (n) == 0) { n = s; s = null_str; } ss = mp_str (mp, s); nn = mp_str (mp, n); mp_pack_file_name (mp, nn, "", ss); delete_str_ref (n); delete_str_ref (s); } return ss; } static char *mp_get_output_file_name (MP mp) { char *f; char *saved_name; /* saved |name_of_file| */ saved_name = xstrdup (mp->name_of_file); (void) mp_set_output_file_name (mp, mp_round_unscaled (mp, internal_value (mp_char_code))); f = xstrdup (mp->name_of_file); mp_pack_file_name (mp, saved_name, NULL, NULL); free (saved_name); return f; } void mp_open_output_file (MP mp) { char *ss; /* filename extension proposal */ integer c; /* \&{charcode} rounded to the nearest integer */ c = mp_round_unscaled (mp, internal_value (mp_char_code)); ss = mp_set_output_file_name (mp, c); while (!mp_a_open_out (mp, (void *) &mp->output_file, mp_filetype_postscript)) mp_prompt_file_name (mp, "file name for output", ss); @<Store the true output file name if appropriate@>; } @ 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|@>= { s = xmalloc (7, 1); mp_snprintf (s, 7, ".%i", (int) c); } @ 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 < mp->first_output_code) && (mp->first_output_code >= 0)) { mp->first_output_code = c; xfree (mp->first_file_name); mp->first_file_name = xstrdup (mp->name_of_file); } if (c >= mp->last_output_code) { mp->last_output_code = c; xfree (mp->last_file_name); mp->last_file_name = xstrdup (mp->name_of_file); } @ @<Glob...@>= char *first_file_name; char *last_file_name; /* full file names */ integer first_output_code; integer last_output_code; /* rounded \&{charcode} values */ @:char_code_}{\&{charcode} primitive@> integer total_shipped; /* total number of |ship_out| operations completed */ @ @<Set init...@>= mp->first_file_name = xstrdup (""); mp->last_file_name = xstrdup (""); mp->first_output_code = 32768; mp->last_output_code = -32768; mp->total_shipped = 0; @ @<Dealloc variables@>= xfree (mp->first_file_name); xfree (mp->last_file_name); @ @<Begin the progress report for the output of picture~|c|@>= if ((int) mp->term_offset > mp->max_print_line - 6) mp_print_ln (mp); else if ((mp->term_offset > 0) || (mp->file_offset > 0)) mp_print_char (mp, xord (' ')); mp_print_char (mp, xord ('[')); if (c >= 0) mp_print_int (mp, c) @ @<End progress report@>= mp_print_char (mp, xord (']')); update_terminal; incr (mp->total_shipped) @ @<Explain what output files were written@>= if (mp->total_shipped > 0) { mp_print_nl (mp, ""); mp_print_int (mp, mp->total_shipped); if (mp->noninteractive) { mp_print (mp, " figure"); if (mp->total_shipped > 1) mp_print_char (mp, xord ('s')); mp_print (mp, " created."); } else { mp_print (mp, " output file"); if (mp->total_shipped > 1) mp_print_char (mp, xord ('s')); mp_print (mp, " written: "); mp_print (mp, mp->first_file_name); if (mp->total_shipped > 1) { if (31 + strlen (mp->first_file_name) + strlen (mp->last_file_name) > (unsigned) mp->max_print_line) mp_print_ln (mp); mp_print (mp, " .. "); mp_print (mp, mp->last_file_name); } mp_print_nl (mp, ""); } } @ @<Internal library declarations@>= boolean mp_has_font_size (MP mp, font_number f); @ @c boolean mp_has_font_size (MP mp, font_number f) { return (mp->font_sizes[f] != NULL); } @ 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...@>= mp_node last_pending; /* the last token in a list of pending specials */ @ @<Cases of |do_statement|...@>= case special_command: if (mp->cur_mod == 0) mp_do_special (mp); else if (mp->cur_mod == 1) mp_do_mapfile (mp); else mp_do_mapline (mp); break; @ @<Declare action procedures for use by |do_statement|@>= static void mp_do_special (MP mp); @ @c void mp_do_special (MP mp) { mp_get_x_next (mp); mp_scan_expression (mp); if (mp->cur_exp.type != mp_string_type) { @<Complain about improper special operation@>; } else { mp_link (mp->last_pending) = mp_stash_cur_exp (mp); mp->last_pending = mp_link (mp->last_pending); mp_link (mp->last_pending) = NULL; } } @ @<Complain about improper special operation@>= { exp_err ("Unsuitable expression"); help1 ("Only known strings are allowed for output as specials."); mp_put_get_error (mp); } @ On the export side, we need an extra object type for special strings. @<Graphical object codes@>= mp_special_code = 8, @ @<Export pending specials@>= p = mp_link (mp->spec_head); while (p != NULL) { mp_special_object *tp; tp = (mp_special_object *) mp_new_graphic_object (mp, mp_special_code); gr_pre_script (tp) = mp_xstrdup(mp,mp_str (mp, str_value (p))); if (hh->body == NULL) hh->body = (mp_graphic_object *) tp; else gr_link (hp) = (mp_graphic_object *) tp; hp = (mp_graphic_object *) tp; p = mp_link (p); } mp_flush_token_list (mp, mp_link (mp->spec_head)); mp_link (mp->spec_head) = NULL; mp->last_pending = mp->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@>= static void mp_ship_out (MP mp, mp_node h); @ Once again, the |gr_XXXX| macros are defined in |mppsout.h| @d export_color(q,p) if ( mp_color_model(p)==mp_uninitialized_model ) { gr_color_model(q) = (unsigned char)(internal_value(mp_default_color_model)/65536); gr_cyan_val(q) = 0; gr_magenta_val(q) = 0; gr_yellow_val(q) = 0; gr_black_val(q) = (gr_color_model(q)==mp_cmyk_model ? unity : 0); } else { gr_color_model(q) = (unsigned char)mp_color_model(p); gr_cyan_val(q) = cyan_val(p); gr_magenta_val(q) = magenta_val(p); gr_yellow_val(q) = yellow_val(p); gr_black_val(q) = black_val(p); } @d export_scripts(q,p) if (mp_pre_script(p)!=NULL) gr_pre_script(q) = mp_xstrdup(mp, mp_str(mp,mp_pre_script(p))); if (mp_post_script(p)!=NULL) gr_post_script(q) = mp_xstrdup(mp, mp_str(mp,mp_post_script(p))); @c struct mp_edge_object *mp_gr_export (MP mp, mp_node h) { mp_node p; /* the current graphical object */ integer t; /* a temporary value */ integer c; /* a rounded charcode */ scaled d_width; /* the current pen width */ mp_edge_object *hh; /* the first graphical object */ mp_graphic_object *hq; /* something |hp| points to */ mp_text_object *tt; mp_fill_object *tf; mp_stroked_object *ts; mp_clip_object *tc; mp_bounds_object *tb; mp_graphic_object *hp = NULL; /* the current graphical object */ mp_set_bbox (mp, h, true); hh = xmalloc (1, sizeof (mp_edge_object)); hh->body = NULL; hh->next = NULL; hh->parent = mp; hh->minx = minx_val (h); hh->miny = miny_val (h); hh->maxx = maxx_val (h); hh->maxy = maxy_val (h); hh->filename = mp_xstrdup (mp, mp_get_output_file_name (mp)); c = mp_round_unscaled (mp, internal_value (mp_char_code)); hh->charcode = c; hh->width = internal_value (mp_char_wd); hh->height = internal_value (mp_char_ht); hh->depth = internal_value (mp_char_dp); hh->ital_corr = internal_value (mp_char_ic); @<Export pending specials@>; p = mp_link (dummy_loc (h)); while (p != NULL) { hq = mp_new_graphic_object (mp, (int) ((mp_type (p) - mp_fill_node_type) + 1)); switch (mp_type (p)) { case mp_fill_node_type: tf = (mp_fill_object *) hq; gr_pen_p (tf) = mp_export_knot_list (mp, mp_pen_p ((mp_fill_node) p)); d_width = mp_get_pen_scale (mp, mp_pen_p ((mp_fill_node) p)); if ((mp_pen_p ((mp_fill_node) p) == NULL) || pen_is_elliptical (mp_pen_p ((mp_fill_node) p))) { gr_path_p (tf) = mp_export_knot_list (mp, mp_path_p ((mp_fill_node) p)); } else { mp_knot pc, pp; pc = mp_copy_path (mp, mp_path_p ((mp_fill_node) p)); pp = mp_make_envelope (mp, pc, mp_pen_p ((mp_fill_node) p), ljoin_val (p), 0, miterlim_val (p)); gr_path_p (tf) = mp_export_knot_list (mp, pp); mp_toss_knot_list (mp, pp); pc = mp_htap_ypoc (mp, mp_path_p ((mp_fill_node) p)); pp = mp_make_envelope (mp, pc, mp_pen_p ((mp_fill_node) p), ljoin_val (p), 0, miterlim_val (p)); gr_htap_p (tf) = mp_export_knot_list (mp, pp); mp_toss_knot_list (mp, pp); } export_color (tf, p); export_scripts (tf, p); gr_ljoin_val (tf) = (unsigned char) ljoin_val (p); gr_miterlim_val (tf) = miterlim_val (p); break; case mp_stroked_node_type: ts = (mp_stroked_object *) hq; gr_pen_p (ts) = mp_export_knot_list (mp, mp_pen_p ((mp_stroked_node) p)); d_width = mp_get_pen_scale (mp, mp_pen_p ((mp_stroked_node) p)); if (pen_is_elliptical (mp_pen_p ((mp_stroked_node) p))) { gr_path_p (ts) = mp_export_knot_list (mp, mp_path_p ((mp_stroked_node) p)); } else { mp_knot pc; pc = mp_copy_path (mp, mp_path_p ((mp_stroked_node) p)); t = lcap_val (p); if (mp_left_type (pc) != mp_endpoint) { mp_left_type (mp_insert_knot (mp, pc, mp_x_coord (pc), mp_y_coord (pc))) = mp_endpoint; mp_right_type (pc) = mp_endpoint; pc = mp_next_knot (pc); t = 1; } pc = mp_make_envelope (mp, pc, mp_pen_p ((mp_stroked_node) p), ljoin_val (p), (quarterword) t, miterlim_val (p)); gr_path_p (ts) = mp_export_knot_list (mp, pc); mp_toss_knot_list (mp, pc); } export_color (ts, p); export_scripts (ts, p); gr_ljoin_val (ts) = (unsigned char) ljoin_val (p); gr_miterlim_val (ts) = miterlim_val (p); gr_lcap_val (ts) = (unsigned char) lcap_val (p); gr_dash_p (ts) = mp_export_dashes (mp, (mp_stroked_node) p, &d_width); break; case mp_text_node_type: tt = (mp_text_object *) hq; gr_text_p (tt) = mp_xstrdup (mp, mp_str (mp, mp_text_p (p))); gr_text_l (tt) = (size_t) length (mp_text_p (p)); gr_font_n (tt) = (unsigned int) mp_font_n (p); gr_font_name (tt) = mp_xstrdup (mp, mp->font_name[mp_font_n (p)]); gr_font_dsize (tt) = (unsigned int) mp->font_dsize[mp_font_n (p)]; export_color (tt, p); export_scripts (tt, p); gr_width_val (tt) = width_val (p); gr_height_val (tt) = height_val (p); gr_depth_val (tt) = depth_val (p); gr_tx_val (tt) = tx_val (p); gr_ty_val (tt) = ty_val (p); gr_txx_val (tt) = txx_val (p); gr_txy_val (tt) = txy_val (p); gr_tyx_val (tt) = tyx_val (p); gr_tyy_val (tt) = tyy_val (p); break; case mp_start_clip_node_type: tc = (mp_clip_object *) hq; gr_path_p (tc) = mp_export_knot_list (mp, mp_path_p ((mp_start_clip_node) p)); break; case mp_start_bounds_node_type: tb = (mp_bounds_object *) hq; gr_path_p (tb) = mp_export_knot_list (mp, mp_path_p ((mp_start_bounds_node) p)); break; case mp_stop_clip_node_type: case mp_stop_bounds_node_type: /* nothing to do here */ break; default: /* there are no other valid cases, but please the compiler */ break; } if (hh->body == NULL) hh->body = hq; else gr_link (hp) = hq; hp = hq; p = mp_link (p); } return hh; } @ This function is only used for the |glyph| operator, so it takes quite a few shortcuts for cases that cannot appear in the output of |mp_ps_font_charstring|. @c mp_node mp_gr_unexport (MP mp, struct mp_edge_object *hh) { mp_node h; /* the edge object */ mp_node ph, pn, pt; /* for adding items */ mp_graphic_object *p; /* the current graphical object */ h = mp_get_edge_header_node (mp); mp_init_edges (mp, h); ph = dummy_loc (h); pt = ph; p = hh->body; minx_val (h) = hh->minx; miny_val (h) = hh->miny; maxx_val (h) = hh->maxx; maxy_val (h) = hh->maxy; while (p != NULL) { switch (gr_type (p)) { case mp_fill_code: if (gr_pen_p ((mp_fill_object *) p) == NULL) { pn = mp_new_fill_node (mp, NULL); mp_path_p ((mp_fill_node) pn) = mp_import_knot_list (mp, gr_path_p ((mp_fill_object *) p)); mp_color_model (pn) = mp_grey_model; if (mp_new_turn_cycles (mp, mp_path_p ((mp_fill_node) pn)) < 0) { grey_val (pn) = unity; mp_link (pt) = pn; pt = mp_link (pt); } else { mp_link (pn) = mp_link (ph); mp_link (ph) = pn; if (ph == pt) pt = pn; } } break; case mp_stroked_code: case mp_text_code: case mp_start_clip_code: case mp_stop_clip_code: case mp_start_bounds_code: case mp_stop_bounds_code: case mp_special_code: break; } /* all cases are enumerated */ p = p->next; } mp_gr_toss_objects (hh); return h; } @ @<Declarations@>= static struct mp_edge_object *mp_gr_export (MP mp, mp_node h); static mp_node mp_gr_unexport (MP mp, struct mp_edge_object *h); @ This function is now nearly trivial. @c void mp_ship_out (MP mp, mp_node h) { /* output edge structure |h| */ integer c; /* \&{charcode} rounded to the nearest integer */ c = mp_round_unscaled (mp, internal_value (mp_char_code)); @<Begin the progress report for the output of picture~|c|@>; (mp->shipout_backend) (mp, h); @<End progress report@>; if (internal_value (mp_tracing_output) > 0) mp_print_edges (mp, h, " (just shipped out)", true); } @ @<Declarations@>= static void mp_shipout_backend (MP mp, void *h); @ @c void mp_shipout_backend (MP mp, void *voidh) { char *s; mp_edge_object *hh; /* the first graphical object */ mp_node h = (mp_node) voidh; hh = mp_gr_export (mp, h); s = NULL; if (internal_string (mp_output_format) != NULL) s = mp_str (mp, internal_string (mp_output_format)); if (s && strcmp (s, "svg") == 0) { (void) mp_svg_gr_ship_out (hh, (internal_value (mp_prologues) / 65536), false); } else { (void) mp_gr_ship_out (hh, (internal_value (mp_prologues) / 65536), (internal_value (mp_procset) / 65536), false); } mp_gr_toss_objects (hh); } @ @<Exported types@>= typedef void (*mp_backend_writer) (MP, void *); @ @<Option variables@>= mp_backend_writer shipout_backend; @ 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@>= { halfword vv = cur_exp_value (); set_cur_exp_value (mp_round_unscaled (mp, vv) % 256); if (cur_exp_value () < 0) { vv = cur_exp_value (); set_cur_exp_value (vv + 256); } boolean_reset (mp->char_exists[cur_exp_value ()]); mp->cur_exp.type = mp_boolean_type; } @ @<Glob...@>= psout_data ps; svgout_data svg; @ @<Allocate or initialize ...@>= mp_ps_backend_initialize (mp); mp_svg_backend_initialize (mp); @ @<Dealloc...@>= mp_ps_backend_free (mp); mp_svg_backend_free (mp); @* Dumping and undumping the tables. When \.{MP} is started, it is possible to preload a macro file containing definitions that will be usable in the main input file. This action even takes place automatically, based on the name of the executable (\.{mpost} will attempt to preload the macros in the file \.{mpost.mp}). If such a preload is not desired, the option variable |ini_version| has to be set |true|. The global variable |mem_ident| will be set to `\.{ (INIMP)}' when |ini_version| is true; otherwise it will contain the filename of the macro file being preloaded. The variable |mem_file| holds the open file pointer. @<Glob...@>= char *mem_ident; void *mem_file; /* file for input or preloaded macros */ @ @<Set init...@>= mp->mem_ident = NULL; @ @<Dealloc variables@>= xfree (mp->mem_ident); @ @<Declarations@>= extern boolean mp_load_preload_file (MP mp); @ Preloading a file is a lot like |mp_run| itself, except that \MP\ should not exit and that a bit of trickery is needed with the input buffer to make sure that the preloading does not interfere with the actual job. @c boolean mp_load_preload_file (MP mp) { size_t k; in_state_record old_state; integer old_in_open = mp->in_open; void *old_cur_file = cur_file; char *fname = xstrdup (mp->name_of_file); size_t l = strlen (fname); old_state = mp->cur_input; str_room (l); for (k = 0; k < l; k++) { append_char (*(fname + k)); } name = mp_make_string (mp); if (!mp->log_opened) { mp_open_log_file (mp); } /* |open_log_file| doesn't |show_context|, so |limit| and |loc| needn't be set to meaningful values yet */ if (((int) mp->term_offset + (int) strlen (fname)) > (mp->max_print_line - 2)) mp_print_ln (mp); else if ((mp->term_offset > 0) || (mp->file_offset > 0)) mp_print_char (mp, xord (' ')); mp_print_char (mp, xord ('(')); incr (mp->open_parens); mp_print (mp, fname); mp->mem_ident = fname; update_terminal; { line = 1; start = loc = limit + 1; cur_file = mp->mem_file; (void) mp_input_ln (mp, cur_file); mp_firm_up_the_line (mp); mp->buffer[limit] = xord ('%'); mp->first = (size_t) (limit + 1); loc = start; } mp->reading_preload = true; do { mp_do_statement (mp); } while (!(mp->cur_cmd == stop && mp->cur_mod == 1)); /* "dump" or EOF */ mp->reading_preload = false; mp_primitive (mp, "dump", relax, 0); /* reset |dump| */ mp_print_char (mp, xord (')')); decr (mp->open_parens); /* |(mp->close_file) (mp, mp->mem_file);| */ cur_file = old_cur_file; mp->cur_input = old_state; mp->in_open = old_in_open; return true; } @* 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. @c @<Declare the basic parsing subroutines@>; @<Declare miscellaneous procedures that were declared |forward|@> @ 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@> @ @c void mp_close_files_and_terminate (MP mp) { integer k; /* all-purpose index */ integer LH; /* the length of the \.{TFM} header, in words */ int lk_offset; /* extra words inserted at beginning of |lig_kern| array */ mp_node p; /* runs through a list of \.{TFM} dimensions */ if (mp->finished) return; @<Close all open files in the |rd_file| and |wr_file| arrays@>; if (internal_value (mp_tracing_stats) > 0) @<Output statistics about this job@>; wake_up_terminal; @<Do all the finishing work on the \.{TFM} file@>; @<Explain what output files were written@>; if (mp->log_opened && !mp->noninteractive) { wlog_cr; (mp->close_file) (mp, mp->log_file); mp->selector = mp->selector - 2; if (mp->selector == term_only) { mp_print_nl (mp, "Transcript written on "); @.Transcript written...@>; mp_print (mp, mp->log_name); mp_print_char (mp, xord ('.')); } } mp_print_ln (mp); mp->finished = true; } @ @<Declarations@>= static void mp_close_files_and_terminate (MP mp); @ @<Close all open files in the |rd_file| and |wr_file| arrays@>= if (mp->rd_fname != NULL) { for (k = 0; k < (int) mp->read_files; k++) { if (mp->rd_fname[k] != NULL) { (mp->close_file) (mp, mp->rd_file[k]); xfree (mp->rd_fname[k]); } } } if (mp->wr_fname != NULL) { for (k = 0; k < (int) mp->write_files; k++) { if (mp->wr_fname[k] != NULL) { (mp->close_file) (mp, mp->wr_file[k]); xfree (mp->wr_fname[k]); } } } @ @<Dealloc ...@>= for (k = 0; k < (int) mp->max_read_files; k++) { if (mp->rd_fname[k] != NULL) { (mp->close_file) (mp, mp->rd_file[k]); xfree (mp->rd_fname[k]); } } xfree (mp->rd_file); xfree (mp->rd_fname); for (k = 0; k < (int) mp->max_write_files; k++) { if (mp->wr_fname[k] != NULL) { (mp->close_file) (mp, mp->wr_file[k]); xfree (mp->wr_fname[k]); } } xfree (mp->wr_file); xfree (mp->wr_fname); @ We want to produce a \.{TFM} file if and only if |mp_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_value (mp_fontmaking) > 0) { @<Massage the \.{TFM} widths@>; mp_fix_design_size (mp); mp_fix_check_sum (mp); @<Massage the \.{TFM} heights, depths, and italic corrections@>; internal_value (mp_fontmaking) = 0; /* avoid loop in case of fatal error */ @<Finish the \.{TFM} file@>; } @ 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 (mp->log_opened) { char s[128]; wlog_ln (" "); wlog_ln ("Here is how much of MetaPost's memory you used:"); @.Here is how much...@>; mp_snprintf (s, 128, " %i string%s using %i character%s", (int) mp->max_strs_used, (mp->max_strs_used != 1 ? "s" : ""), (int) mp->max_pl_used, (mp->max_pl_used != 1 ? "s" : "")); wlog_ln (s); mp_snprintf (s, 128, " %i bytes of node memory", (int) mp->var_used_max); wlog_ln (s); mp_snprintf (s, 128, " %i symbolic tokens", (int) mp->st_count); wlog_ln (s); mp_snprintf (s, 128, " %ii,%in,%ip,%ib,%if stack positions out of %ii,%in,%ip,%ib,%if", (int) mp->max_in_stack, (int) mp->int_ptr, (int) mp->max_param_stack, (int) mp->max_buf_stack + 1, (int) mp->in_open_max - file_bottom, (int) mp->stack_size, (int) mp->max_internal, (int) mp->param_size, (int) mp->buf_size, (int) mp->max_in_open - file_bottom); wlog_ln (s); } @ It is nice to have have some of the stats available from the API. @<Exported function ...@>= int mp_memory_usage (MP mp); int mp_hash_usage (MP mp); int mp_param_usage (MP mp); int mp_open_usage (MP mp); @ @c int mp_memory_usage (MP mp) { return (int) mp->var_used; } int mp_hash_usage (MP mp) { return (int) mp->st_count; } int mp_param_usage (MP mp) { return (int) mp->max_param_stack; } int mp_open_usage (MP mp) { return (int) mp->max_in_stack; } @ We get to the |final_cleanup| routine when \&{end} or \&{dump} has been scanned. @c void mp_final_cleanup (MP mp) { integer c; /* 0 for \&{end}, 1 for \&{dump} */ c = mp->cur_mod; if (mp->job_name == NULL) mp_open_log_file (mp); while (mp->input_ptr > 0) { if (token_state) mp_end_token_list (mp); else mp_end_file_reading (mp); } while (mp->loop_ptr != NULL) mp_stop_iteration (mp); while (mp->open_parens > 0) { mp_print (mp, " )"); decr (mp->open_parens); }; while (mp->cond_ptr != NULL) { mp_print_nl (mp, "(end occurred when "); @.end occurred...@>; mp_print_cmd_mod (mp, fi_or_else, mp->cur_if); /* `\.{if}' or `\.{elseif}' or `\.{else}' */ if (mp->if_line != 0) { mp_print (mp, " on line "); mp_print_int (mp, mp->if_line); } mp_print (mp, " was incomplete)"); mp->if_line = if_line_field (mp->cond_ptr); mp->cur_if = mp_name_type (mp->cond_ptr); mp->cond_ptr = mp_link (mp->cond_ptr); } if (mp->history != mp_spotless) if (((mp->history == mp_warning_issued) || (mp->interaction < mp_error_stop_mode))) if (mp->selector == term_and_log) { mp->selector = term_only; mp_print_nl (mp, "(see the transcript file for additional information)"); @.see the transcript file...@>; mp->selector = term_and_log; } } @ @<Declarations@>= static void mp_final_cleanup (MP mp); static void mp_init_prim (MP mp); static void mp_init_tab (MP mp); @ @c void mp_init_prim (MP mp) { /* initialize all the primitives */ @<Put each...@>; } @# void mp_init_tab (MP mp) { /* initialize other tables */ @<Initialize table entries@>; } @ When we begin the following code, \MP's tables may still contain garbage; 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...@>= { @<Initialize the input routines@>; if (mp->mem_ident == NULL) { if (!mp->ini_version) { if (!mp_load_preload_file (mp)) { mp->history = mp_fatal_error_stop; return mp; } } else { mp->mem_ident = mp_xstrdup (mp, " (INIMP)"); } } @<Initializations following first line@>; } @ @<Initializations following first line@>= mp->buffer[limit] = (ASCII_code) '%'; mp_fix_date_and_time (mp); if (mp->random_seed == 0) mp->random_seed = (internal_value (mp_time) / unity) + internal_value (mp_day); mp_init_randoms (mp, mp->random_seed); @<Initialize the print |selector|...@>; mp_normalize_selector (mp); if (loc < limit) if (mp->buffer[loc] != '\\') mp_start_input (mp); /* \&{input} assumed */ @* Debugging. @* 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@> @* 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.