diff options
Diffstat (limited to 'source/luametatex/source/mp/mpw/mp.w')
-rw-r--r-- | source/luametatex/source/mp/mpw/mp.w | 31138 |
1 files changed, 31138 insertions, 0 deletions
diff --git a/source/luametatex/source/mp/mpw/mp.w b/source/luametatex/source/mp/mpw/mp.w new file mode 100644 index 000000000..017cc0fd7 --- /dev/null +++ b/source/luametatex/source/mp/mpw/mp.w @@ -0,0 +1,31138 @@ +% This file is part of MetaPost. The MetaPost program is in the public domain. + +@* Nota bene. + +This is not the official reference library but a version meant for \LUAMETATEX\ +in combination with \METAFUN, which is integrated in \CONTEXT. When the original +gets improved I will diff the progression of the original \CWEB\ files and merge +improvements. + +I'm pretty sure that the \TEX part of this file doesn't process but I'll look into +that later. The comments are kept as they were but there are occasional remakts +because we changes some bits and pieces. The references to properties, variables, +constants etc, are mostly kept. I due time I'll fix it and see if I can render +the file, but for not it's okay to just read the comments. I admit that I check +things in Visual Studio anyway, which is why there are now |enum| used. + +This split is needed because the original library is the one used for \METAPOST\ +the program which is used by DEK, and I don't want to mess up his workflow. At +some point I might emulate \METAPOST\ but I might as well decide to remove the +interaction completely from this variant. It al depends on the outcome of +experiments that Alan and I conduct, and as it's done in free time, it will take +while. Don't push us, don't nag, don't complain. The original library is where +the support is concentrated and you can always use that with the \MKIV\ macros. + +Todo: check typecasts, the halfword and quarterwords are now integers. +Todo: Move more variables into the scope that they're used. +Todo: Remove some (int) cast that are left overs from quarterword. +Todo: Remove unused variables ... postpone more padding till that is done. +Todo: Support color in group objects + +Because we don't want macros to clash with fields in record, setters and getters +are prefixed by |mp_|. In order not clash with typedefs and accessors, in some +cases |mp_get_| and |mp_set_| are used (eventually that might be true for all +these cases). The |mp_free_| functions are complemented by |mp_new_| functions. +In \MPLIB\ 2 |mp_get_| is used instead so keep that in mind when comparing the +sources. I might also pass |mp| to all macros, just for consistency. + +To be considered: use the same record for rgb and cmyk (less code eventually). + +In order to make extensions a bit easier (and also because of consistency in +enumerations, some _token and _sym and similar specifiers have been made _command +(it was already somewhat inconsistent anyway). When something gets compared to +cur_cmd it makes sense to use _command anyway. + +% some (int) can go + +(Hans Hagen, 2019+) + +@* Comment. + +At some point Taco Hoekwater brilliantly converted \MP\ into a library. Since +then usage and integration of \METAPOST\ in \CONTEXT\ went even further than +before. There were some backends added for \SVG\ and \PNG, and several number +systems could be used. This was quite an effort! The \MP\ program became a +wrapper around this library. + +The library is also used in \LUATEX\ but there we don't need the backend code at +all. Also, having the traditional \TFM\ generating code (inherited from \MF) +makes not much sense because we now live in an \OPENTYPE\ universum and the hard +coded 256 limitations were even for \TYPEONE\ not okay. The GUST font team use +their own tools around \MP\ anyway. + +This variant (below) is therefore a stripped down library. Everything related to +loading fonts is gone, and if a \PS\ backend is needed the functionality has to +go into its own module (as with \SVG\ and \PNG). This means that code removed +here has to go there. One problen then is that the output primitives have to be +brought in too, but in good \CWEB\ practices, that then can be done via change +files (basically extending the data structures and such). + +However, a more modern variant could be to just use the library with \LUA, +produce \PDF\ and convert that to any format needed. This is what we do in +\CONTEXT. After a decade of usage I like to change a few interface aspects so +here this happens. + +So: this variant is {\em not} the official \MP\ library but one meant for usage +in \LUAMETATEX\ and experiments by Alan Braslau and Hans Hagen for more advanced +graphics, produced by cooperation between \LUA\ and \MP. This strategy permits +experiments without interference with the full blown version. Of course we can +retrofit interesting extensions into its larger version at some point. It's all a +work of love, done in our own time, so don't push our agenda in this. + +Stripping is easier than adding and the things I added were not at the level of +the language or processing but the interface to \LUA\ as well as some details of +text processing. Some more of that might happen. For instance, all file \IO\ now +goes via \LUA\ so we assume the callbacks being set. + +On my agenda are to delegate printing of messages and errors to the plugin. Also +filenames might be done differently. Messages are already normalized. + +As a start the psout.w file was stripped and turned into a mpcommon.w file. This +means that the old \PS\ output code is no longer there. Because that file got +small it eventually got merged in here which (1) permits some reshuffling and (2) +gives room for optimizing the interface to \LUA\ (do we need the indirectness?). + +Quite some code has been stripped because we assume that \LUA\ can provide these +features: file io, logging, management, error handling, etc. This saves quite a +bit of code and also detangles a bit the mixed program vs. library code. For now +the \quote {terminal} approach is kept. + +In the process I reformatted the source a bit. Sorry. It is no big deal because +it looks like \METAPOST\ is not evolving, but what does evolve is the code here: +scanners and more access, to mention a few. I've added braces so that comments +can go with single statements and there can be no doubt when \WEB\ macros are +used (some braces could go there. More variables will become local (to branches +for instance). Messages are done more directly, etc. etc. One of the reasons for +doing that is that it looks nicer in Visual Studio. There it helps to move some +variables to a more local scope. Of course a side effect is that backporting is +now no longer an option. In some cases redundant braces were removed (when it's +clear in the w file) and some else statements have been added where confusion +takes place because that one doesn't return (so compilers can for instance warn +about uninitialized pointers). I made sure that the resulting code is readable +in visual studio. + +Work in progress: prefix with mp_ so that macros don't clash with fields and we +can get rid of _ hackery. + +Maybe some day: zpair zpath zdraw ztransform: just add an extra z dimension +to the existing data types which makes it compatible too. + +Todo: consider double only +Todo: use documented c +Todo: rework some (more) helpers + +Todo: center +Todo: centerofmass +Todo: ceiling x +Todo: x div y +Todo: x mod y +Todo: dir x +Todo: unitvector + +(Hans Hagen, 2019+) + +@* 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 {\em The \METAFONT\ book} as well as the manual @.WEB@> @:METAFONTbook}{\sl +The {\logos METAFONT}book@> {\em 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@> + +At some point I started adding features to the library (think of stacking) but +the more interesting additions came when Mikael Sundqvist and we side tracked from +extending math at the \TEX\ end to more \METAFUN: intersection lists, arctime +lists, path iteration, a few more helpers, some fixes, a bit more control, access +to previously hidden functionality, appended paths, etc. And there is undoubtly +more to come. As with all \LUATEX\ and \LUAMETATEX\ development, most gets +explained in the history documents in the \CONTEXT\ distribution and articles. It +was around version 3.14 (end May 2022). + +@d default_banner "This is MPLIB for LuaMetaTeX, version 3.14" + +@<Metapost version header@>= +# define metapost_version "3.14" + +@ We used to have three header files: common, mpmp and mplib, but there ws some +(growing) dependency on the one hand and we decided to target just \LUAMETATEX\ +on the other. After all, this is a special version. So, we now have one header +file only. The variables from |MP_options| are included inside the |MP_instance| +wholesale. This also permits some further stripping. Actually we can probably +get rid of the intermediate \POSTSCRIPT\ representation or add a little more +abstraction. + +@(mp.h@>= +# ifndef MP_H +# define MP_H 1 + +# include "avl.h" +# include "auxmemory.h" +# include <string.h> +# include <setjmp.h> + +@<Metapost version header@> +typedef struct MP_instance *MP; +@<Exported types@> +typedef struct MP_options { + @<Option variables@> +} MP_options; +@<Exported function headers@> +@<MPlib header stuff@> +@<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@> +@<MPlib internal header stuff@> +@<MPlib export header stuff@> +# endif + +@ @c +# include "mpconfig.h" +# include "mp.h" +# include "mpmath.h" +# include "mpmathdouble.h" +# include "mpmathbinary.h" +# include "mpmathdecimal.h" +# include "mpstrings.h" + +@h @<Declarations@> +@<Error handling procedures@> + +@ 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 = mp_memory_clear_allocate(sizeof(MP_options)); + return opt; +} + +@ The whole instance structure is initialized with zeroes, this greatly reduces +the number of statements needed in the |Allocate or initialize variables| block. + +@c +static MP mp_do_new (jmp_buf *buf) +{ + MP mp = mp_memory_clear_allocate(sizeof(MP_instance)); + if (mp == NULL) { + mp_memory_free(buf); + return NULL; + } else { + mp->jump_buf = buf; + return mp; + } +} + +static void mp_free (MP mp) +{ + @<Dealloc variables@> + @<Finish non-interactive use@> + mp_memory_free(mp->jump_buf); + @<Free table entries@> + free_math(); + mp_memory_free(mp); +} + +static void mp_do_initialize (MP mp) +{ + @<Set initial values of key variables@> +} + +@ For the retargetable math library, we need to have a pointer, at least. + +@<Global variables@>= +math_data *math; + +@ @<Exported types@>= +typedef enum mp_number_type { + mp_nan_type, + mp_scaled_type, + mp_fraction_type, + mp_angle_type, + mp_double_type, + mp_binary_type, + mp_decimal_type +} mp_number_type; + +typedef union mp_number_store { + void *num; + double dval; + int val; +} mp_number_store; + +typedef struct mp_number_data { + mp_number_store data; + mp_number_type type; +} mp_number_data; + +typedef struct mp_number_data mp_number; + +# define is_number(A) ((A).type != mp_nan_type) + +@ Switching to also passing pointers for the origins made the \LUAMETATEX\ binary +go down from 3061799 bytes to 2960091 bytes (mid May 2022). + +We have a few more helpers for cloning: |negated| and |abs| because these happen +often and it saves some lines of code in already long functions. + +@<Types in the outer block@>= +typedef void (*convert_func) (mp_number *r); +typedef void (*m_log_func) (MP mp, mp_number *r, mp_number *a); +typedef void (*m_exp_func) (MP mp, mp_number *r, mp_number *a); +typedef void (*m_unif_rand_func) (MP mp, mp_number *ret, mp_number *x_orig); +typedef void (*m_norm_rand_func) (MP mp, mp_number *ret); +typedef void (*pyth_add_func) (MP mp, mp_number *r, mp_number *a, mp_number *b); +typedef void (*pyth_sub_func) (MP mp, mp_number *r, mp_number *a, mp_number *b); +typedef void (*power_of_func) (MP mp, mp_number *r, mp_number *a, mp_number *b); +typedef void (*n_arg_func) (MP mp, mp_number *r, mp_number *a, mp_number *b); +typedef void (*velocity_func) (MP mp, mp_number *r, mp_number *a, mp_number *b, mp_number *c, mp_number *d, mp_number *e); +typedef int (*ab_vs_cd_func) (mp_number *a, mp_number *b, mp_number *c, mp_number *d); +typedef void (*crossing_point_func) (MP mp, mp_number *r, mp_number *a, mp_number *b, mp_number *c); +typedef void (*number_from_int_func) (mp_number *A, int B); +typedef void (*number_from_boolean_func) (mp_number *A, int B); +typedef void (*number_from_scaled_func) (mp_number *A, int B); +typedef void (*number_from_double_func) (mp_number *A, double B); +typedef void (*number_from_addition_func) (mp_number *A, mp_number *B, mp_number *C); +typedef void (*number_half_from_addition_func) (mp_number *A, mp_number *B, mp_number *C); +typedef void (*number_from_subtraction_func) (mp_number *A, mp_number *B, mp_number *C); +typedef void (*number_half_from_subtraction_func) (mp_number *A, mp_number *B, mp_number *C); +typedef void (*number_from_div_func) (mp_number *A, mp_number *B, mp_number *C); +typedef void (*number_from_mul_func) (mp_number *A, mp_number *B, mp_number *C); +typedef void (*number_from_int_div_func) (mp_number *A, mp_number *B, int C); +typedef void (*number_from_int_mul_func) (mp_number *A, mp_number *B, int C); +typedef void (*number_from_oftheway_func) (MP mp, mp_number *A, mp_number *t, mp_number *B, mp_number *C); +typedef void (*number_negate_func) (mp_number *A); +typedef void (*number_add_func) (mp_number *A, mp_number *B); +typedef void (*number_subtract_func) (mp_number *A, mp_number *B); +typedef void (*number_modulo_func) (mp_number *A, mp_number *B); +typedef void (*number_half_func) (mp_number *A); +typedef void (*number_double_func) (mp_number *A); +typedef void (*number_abs_func) (mp_number *A); +typedef void (*number_clone_func) (mp_number *A, mp_number *B); +typedef void (*number_negated_clone_func) (mp_number *A, mp_number *B); +typedef void (*number_abs_clone_func) (mp_number *A, mp_number *B); +typedef void (*number_swap_func) (mp_number *A, mp_number *B); +typedef void (*number_add_scaled_func) (mp_number *A, int b); +typedef void (*number_multiply_int_func) (mp_number *A, int b); +typedef void (*number_divide_int_func) (mp_number *A, int b); +typedef int (*number_to_int_func) (mp_number *A); +typedef int (*number_to_boolean_func) (mp_number *A); +typedef int (*number_to_scaled_func) (mp_number *A); +typedef int (*number_round_func) (mp_number *A); +typedef void (*number_floor_func) (mp_number *A); +typedef double (*number_to_double_func) (mp_number *A); +typedef int (*number_odd_func) (mp_number *A); +typedef int (*number_equal_func) (mp_number *A, mp_number *B); +typedef int (*number_less_func) (mp_number *A, mp_number *B); +typedef int (*number_greater_func) (mp_number *A, mp_number *B); +typedef int (*number_nonequalabs_func) (mp_number *A, mp_number *B); +typedef void (*make_scaled_func) (MP mp, mp_number *ret, mp_number *A, mp_number *B); +typedef void (*make_fraction_func) (MP mp, mp_number *ret, mp_number *A, mp_number *B); +typedef void (*take_fraction_func) (MP mp, mp_number *ret, mp_number *A, mp_number *B); +typedef void (*take_scaled_func) (MP mp, mp_number *ret, mp_number *A, mp_number *B); +typedef void (*sin_cos_func) (MP mp, mp_number *A, mp_number *S, mp_number *C); +typedef void (*slow_add_func) (MP mp, mp_number *A, mp_number *S, mp_number *C); +typedef void (*sqrt_func) (MP mp, mp_number *ret, mp_number *A); +typedef void (*init_randoms_func) (MP mp, int seed); +typedef void (*allocate_number_func) (MP mp, mp_number *A, mp_number_type t); +typedef void (*allocate_number_clone_func) (MP mp, mp_number *A, mp_number_type t, mp_number *B); +typedef void (*allocate_number_abs_func) (MP mp, mp_number *A, mp_number_type t, mp_number *B); +typedef void (*allocate_number_double_func) (MP mp, mp_number *A, double B); +typedef void (*free_number_func) (MP mp, mp_number *n); +typedef void (*fraction_to_round_scaled_func) (mp_number *n); +typedef void (*print_func) (MP mp, mp_number *A); +typedef char *(*tostring_func) (MP mp, mp_number *A); +typedef void (*scan_func) (MP mp, int A); +typedef void (*mp_free_func) (MP mp); +typedef void (*set_precision_func) (MP mp); + +/* + We use a prefix |md_| so that we don't get complaints about recursive macro + definitions. This is cleaner than redefining the macros. +*/ + +typedef struct math_data { + mp_number md_precision_default; + mp_number md_precision_max; + mp_number md_precision_min; + mp_number md_epsilon_t; + mp_number md_inf_t; + mp_number md_negative_inf_t; + mp_number md_one_third_inf_t; + mp_number md_zero_t; + mp_number md_unity_t; + mp_number md_two_t; + mp_number md_three_t; + mp_number md_half_unit_t; + mp_number md_three_quarter_unit_t; + mp_number md_fraction_one_t; + mp_number md_fraction_half_t; + mp_number md_fraction_three_t; + mp_number md_fraction_four_t; + mp_number md_one_eighty_deg_t; + mp_number md_negative_one_eighty_deg_t; + mp_number md_three_sixty_deg_t; + mp_number md_one_k; + mp_number md_sqrt_8_e_k; + mp_number md_twelve_ln_2_k; + mp_number md_coef_bound_k; + mp_number md_coef_bound_minus_1; + mp_number md_twelvebits_3; + mp_number md_arc_tol_k; + mp_number md_twentysixbits_sqrt2_t; + mp_number md_twentyeightbits_d_t; + mp_number md_twentysevenbits_sqrt2_d_t; + mp_number md_fraction_threshold_t; + mp_number md_half_fraction_threshold_t; + mp_number md_scaled_threshold_t; + mp_number md_half_scaled_threshold_t; + mp_number md_near_zero_angle_t; + mp_number md_p_over_v_threshold_t; + mp_number md_equation_threshold_t; + mp_number md_warning_limit_t; + allocate_number_func md_allocate; + allocate_number_clone_func md_allocate_clone; + allocate_number_abs_func md_allocate_abs; + allocate_number_double_func md_allocate_double; + free_number_func md_free; + number_from_int_func md_from_int; + number_from_boolean_func md_from_boolean; + number_from_scaled_func md_from_scaled; + number_from_double_func md_from_double; + number_from_addition_func md_from_addition; + number_half_from_addition_func md_half_from_addition; + number_from_subtraction_func md_from_subtraction; + number_half_from_subtraction_func md_half_from_subtraction; + number_from_div_func md_from_div; + number_from_mul_func md_from_mul; + number_from_int_div_func md_from_int_div; + number_from_int_mul_func md_from_int_mul; + number_from_oftheway_func md_from_oftheway; + number_negate_func md_negate; + number_add_func md_add; + number_subtract_func md_subtract; + number_half_func md_half; + number_modulo_func md_modulo; + number_double_func md_do_double; + number_abs_func md_abs; + number_clone_func md_clone; + number_negated_clone_func md_negated_clone; + number_abs_clone_func md_abs_clone; + number_swap_func md_swap; + number_add_scaled_func md_add_scaled; + number_multiply_int_func md_multiply_int; + number_divide_int_func md_divide_int; + number_to_int_func md_to_int; + number_to_boolean_func md_to_boolean; + number_to_scaled_func md_to_scaled; + number_to_double_func md_to_double; + number_odd_func md_odd; + number_equal_func md_equal; + number_less_func md_less; + number_greater_func md_greater; + number_nonequalabs_func md_nonequalabs; + number_round_func md_round_unscaled; + number_floor_func md_floor_scaled; + make_scaled_func md_make_scaled; + make_fraction_func md_make_fraction; + take_fraction_func md_take_fraction; + take_scaled_func md_take_scaled; + velocity_func md_velocity; + ab_vs_cd_func md_ab_vs_cd; + crossing_point_func md_crossing_point; + n_arg_func md_n_arg; + m_log_func md_m_log; + m_exp_func md_m_exp; + m_unif_rand_func md_m_unif_rand; + m_norm_rand_func md_m_norm_rand; + pyth_add_func md_pyth_add; + pyth_sub_func md_pyth_sub; + power_of_func md_power_of; + fraction_to_round_scaled_func md_fraction_to_round_scaled; + convert_func md_fraction_to_scaled; + convert_func md_scaled_to_fraction; + convert_func md_scaled_to_angle; + convert_func md_angle_to_scaled; + init_randoms_func md_init_randoms; + sin_cos_func md_sin_cos; + sqrt_func md_sqrt; + slow_add_func md_slow_add; + print_func md_print; + tostring_func md_tostring; + scan_func md_scan_numeric; + scan_func md_scan_fractional; + mp_free_func md_free_math; + set_precision_func md_set_precision; +} math_data; + +@ This procedure gets things started properly. + +@c +MP mp_initialize (MP_options * opt) +{ + MP mp; + jmp_buf *buf = mp_memory_allocate(sizeof(jmp_buf)); + if (buf == NULL || setjmp(*buf) != 0) { + return NULL; + } + mp = mp_do_new(buf); + if (mp == NULL) { + return NULL; + } + if (opt->job_name == NULL || ! *(opt->job_name)) { + return NULL; + } + mp->job_name = mp_strdup(opt->job_name); + mp->userdata = opt->userdata; + mp->extensions = opt->extensions; + @<Set default function pointers@> + + mp->find_file = opt->find_file ? opt->find_file : mp_find_file ; + mp->open_file = opt->open_file ? opt->open_file : mp_open_file ; + mp->read_file = opt->read_file ? opt->read_file : mp_read_file ; + mp->close_file = opt->close_file ? opt->close_file : mp_close_file ; + mp->write_file = opt->write_file ? opt->write_file : mp_write_file ; + mp->shipout_backend = opt->shipout_backend ? opt->shipout_backend : mp_shipout_backend; + mp->run_script = opt->run_script ? opt->run_script : mp_run_script ; + mp->run_internal = opt->run_internal ? opt->run_internal : mp_run_internal ; + mp->run_logger = opt->run_logger ? opt->run_logger : mp_run_logger ; + mp->run_overload = opt->run_overload ? opt->run_overload : mp_run_overload ; + mp->run_error = opt->run_error ? opt->run_error : mp_run_error ; + mp->run_warning = opt->run_warning ? opt->run_warning : mp_run_warning ; + mp->make_text = opt->make_text ? opt->make_text : mp_make_text ; + + mp->find_file_id = opt->find_file_id; + mp->run_script_id = opt->run_script_id; + mp->run_internal_id = opt->run_internal_id; + mp->run_logger_id = opt->run_logger_id; + mp->run_overload_id = opt->run_overload_id; + mp->run_error_id = opt->run_error_id; + mp->run_warning_id = opt->run_warning_id; + mp->make_text_id = opt->make_text_id; + mp->open_file_id = opt->open_file_id; + + if (opt->banner && *(opt->banner)) { + mp->banner = mp_strdup(opt->banner); + } else { + mp->banner = mp_strdup(default_banner); + } + switch (opt->math_mode) { + case mp_math_scaled_mode: + mp->math = mp_initialize_scaled_math(mp); + break; + case mp_math_decimal_mode: + mp->math = mp_initialize_decimal_math(mp); + break; + case mp_math_binary_mode: + mp->math = mp_initialize_binary_math(mp); + break; + default: + mp->math = mp_initialize_double_math(mp); + break; + } + @<Allocate or initialize variables@> + mp_reallocate_paths(mp, 1000); + /* in case we quit during initialization: */ + mp->history = mp_fatal_error_stop; + mp_do_initialize(mp); + /* initialize the tables */ + mp_init_tab(mp); + switch (opt->math_mode) { + case mp_math_scaled_mode: + set_internal_string(mp_number_system_internal, mp_intern(mp, "scaled")); + break; + case mp_math_decimal_mode: + set_internal_string(mp_number_system_internal, mp_intern(mp, "decimal")); + break; + case mp_math_binary_mode: + set_internal_string(mp_number_system_internal, mp_intern(mp, "binary")); + break; + default: + set_internal_string(mp_number_system_internal, mp_intern(mp, "double")); + break; + } + /* call |primitive| for each primitive */ + mp_init_prim(mp); + mp_fix_date_and_time(mp); + mp->history = mp_spotless; + set_precision(); + @<Fix up |job_name|@> + return mp; +} + +@ @<Exported function headers@>= +extern MP_options *mp_options (void); +extern MP mp_initialize (MP_options * opt); +extern int mp_status (MP mp); +extern int mp_finished (MP mp); +extern void *mp_userdata (MP mp); + +@ @c +int mp_status (MP mp) { return mp->history; } +int mp_finished (MP mp) { return mp->finished; } +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 \CWEB\ +description you are now reading, since the present ordering is intended to +combine the advantages of the \quote {bottom up} and \quote {top down} approaches +to the problem of understanding a somewhat complicated system. + +@ 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 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 */ +int padding_size; /* so that the next array nicely sits in the cache */ + +@ @<Option variables@>= +int halt_on_error; /* do we quit at the first error? */ +void *userdata; /* this allows the calling application to setup local (e.g. L for Lua) */ +char *banner; /* the banner that is printed to the screen and log */ +int utf8_mode; +int text_mode; +int show_mode; + +@ @<Dealloc variables@>= +mp_memory_free(mp->banner); + +@ @<Allocate or ...@>= +mp->param_size = 4; +mp->max_in_open = 0; +mp->halt_on_error = opt->halt_on_error ? 1 : 0; +mp->utf8_mode = opt->utf8_mode ? 1 : 0; +mp->text_mode = opt->text_mode ? 1 : 0; +mp->show_mode = opt->show_mode ? 1 : 0; + +@ Here are some macros for common programming idioms (incr and decr are now +inlined). + +@d odd(A) (abs(A) % 2 == 1) /* replaced by proper number_odd calls, tex has: ((x) & 1) */ + +@* The character set. + +@ We assume proper ASCII codes to be used and likely UTF-8 so we dropped the two +way mapping from input to internal and from internal to to output (actually that +mapping was not that robust because some strings bypassed the conversions). + +@* 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 \quote {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 \quote +{open} or to terminate \quote {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. + +@ @<Exported types@>= +enum mp_filetype { + mp_filetype_terminal, /* the terminal (input) */ + mp_filetype_program, /* \MP\ language input */ + mp_filetype_text /* first text file for readfrom and writeto primitives */ +}; + +typedef char *(*mp_file_finder) (MP, const char *, const char *, int); +typedef char *(*mp_script_runner) (MP, const char *m, size_t len, int n); +typedef void (*mp_internal_runner) (MP, int action, int n, int type, const char *iname); +typedef void (*mp_log_runner) (MP, int, const char *s, size_t l); +typedef int (*mp_overload_runner) (MP, int, const char *, int); +typedef void (*mp_error_runner) (MP, const char *, const char *, int); +typedef void (*mp_warning_runner) (MP, const char *); +typedef char *(*mp_text_maker) (MP, const char *, size_t, int); +typedef void *(*mp_file_opener) (MP, const char *, const char *, int); +typedef char *(*mp_file_reader) (MP, 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 *); + +@ @<Option variables@>= +mp_file_finder find_file; +mp_script_runner run_script; +mp_internal_runner run_internal; +mp_log_runner run_logger; +mp_overload_runner run_overload; +mp_error_runner run_error; +mp_warning_runner run_warning; +mp_text_maker make_text; +mp_file_opener open_file; +mp_file_closer close_file; +mp_file_reader read_file; +mp_file_writer write_file; + +int find_file_id; +int run_script_id; +int run_internal_id; +int run_logger_id; +int run_overload_id; +int run_error_id; +int run_warning_id; +int make_text_id; +int open_file_id; + +@ The default function for finding files is |mp_find_file|. It is pretty stupid: +it will only find files in the current directory. + +@c +static char *mp_find_file (MP mp, const char *fname, const char *fmode, int ftype) +{ + (void) mp; (void) fname; (void) fmode; (void) ftype; + mp_fatal_error(mp, "no 'find_file' callback set"); + return NULL; +} + +static char *mp_run_script (MP mp, const char *str, size_t len, int n) +{ + (void) mp; (void) str; (void) len; (void) n; + mp_fatal_error(mp, "no 'run_script' callback set"); + return NULL; +} + +void mp_run_internal (MP mp, int action, int n, int type, const char *iname) +{ + (void) mp; (void) action; (void) n; (void) type; (void) iname; + mp_fatal_error(mp, "no 'run_internal' callback set"); +} + +@ The logger has to deal with the console and the log file and gets information +about the target. + +@c +static void mp_run_logger (MP mp, int target, const char *s, size_t l) +{ + (void) mp; (void) target; (void) s; (void) l; + mp_fatal_error(mp, "no 'run_logger' callback set"); +} + + +@ The overload catch is responsible for its own reporting and quitting +if needed. The check only happens when the mode is set. + +@c +static int mp_run_overload (MP mp, int property, const char *str, int mode) +{ + (void) mp; (void) property; (void) str; (void) mode; + mp_fatal_error(mp, "no 'run_overload' callback set"); + return 0; +} + +static void mp_check_overload (MP mp, mp_sym p) +{ + /* not the fastest check */ + if (number_nonzero(internal_value(mp_overloadmode_internal))) { + if (mp->run_overload(mp, p->property, (const char *) p->text->str, number_to_int(internal_value(mp_overloadmode_internal)))) { + p->property = 0; + } else { + /* we keep the property */ + } + } else { + /* we reset the mode */ + p->property = 0; + } +} + +@ Error and warning handling can be delegated too. Warnings are not really used yet +but they might show up some day. + +@c +static void mp_run_error (MP mp, const char *msg, const char *hlp, int interaction) +{ + (void) mp; (void) msg; (void) hlp; (void) interaction; + mp_fatal_error(mp, "no 'run_error' callback set"); +} + +static void mp_run_warning (MP mp, const char *msg) +{ + (void) mp; (void) msg; + mp_fatal_error(mp, "no 'run_warning' callback set"); +} + +@ The |btex ... etex| handling is still present and depends on a callback and +some cooperation with the backend. In \CONTEXT\ we implements text objects as +paths with properties (pre- and postscripts). + +@c +static char *mp_make_text (MP mp, const char *str, size_t len, int mode) +{ + (void) mp; (void) mode; (void) str; (void) len; + mp_fatal_error(mp, "no 'make_text' callback set"); + return NULL; +} + +@ Watch out: at this moment we have |mp_find_file| as well as |open_file| and +both need to be set. + +@<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_file (MP mp, void *f, size_t * size); +static void mp_close_file (MP mp, void *f); +static void mp_write_file (MP mp, void *f, const char *s); +static char *mp_run_script (MP mp, const char *str, size_t len, int n); +static void mp_run_internal (MP mp, int action, int n, int type, const char *iname); +static void mp_run_logger (MP mp, int target, const char *s, size_t l); +static int mp_run_overload (MP mp, int property, const char *, int); +static void mp_run_error (MP mp, const char *, const char *, int); +static void mp_run_warning (MP mp, const char *); +static char *mp_make_text (MP mp, const char *str, size_t len, int mode); + +@ As with the other callbacks, once they are needed and not set an error is +triggered. It made no sense to keep not used code around. + +@c +static void *mp_open_file (MP mp, const char *fname, const char *fmode, int ftype) +{ + (void) mp; (void) fname; (void) fmode; (void) ftype; + mp_fatal_error(mp, "no 'open_file' callback set"); + 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. + +@ \MP's file-opening procedures return |false| if no file identified by +|name_of_file| could be opened. + +The |do_open_file| function takes care of the |print_found_names| parameter. The +file helpers are mandate callbacks. Not setting them triggers an error. + +@c +static int mp_do_open_file (MP mp, void **f, int ftype, const char *mode) +{ + /* + For now we keep the two step find and open approach because we get back + the full (found) name but all logic is at the \LUA\ end. Maybe some day + we need the original name. + */ + char *s = (mp->find_file)(mp, mp->name_of_file, mode, ftype); + if (s != NULL) { + mp_memory_free(mp->name_of_file); + mp->name_of_file = mp_strdup(s); + // lmt_generic_free(s); + lmt_memory_free(s); + *f = (mp->open_file)(mp, mp->name_of_file, mode, ftype); + } else { + *f = NULL; + } + return (*f ? 1 : 0); +} + +static int mp_open_in (MP mp, void **f, int ftype) +{ + return mp_do_open_file(mp, f, ftype, "r"); +} + +static int mp_open_out (MP mp, void **f, int ftype) +{ + return mp_do_open_file(mp, f, ftype, "w"); +} + +static char *mp_read_file (MP mp, void *f, size_t *size) +{ + (void) mp; (void) f; (void) size; + mp_fatal_error(mp, "no 'read_file' callback set"); + return NULL; +} + +static void mp_write_file (MP mp, void *f, const char *s) +{ + (void) mp; (void) f; (void) s; + mp_fatal_error(mp, "no 'read_file' callback set"); +} + +static void mp_close_file (MP mp, void *f) +{ + (void) mp; (void) f; + mp_fatal_error(mp, "no 'close_file' callback set"); +} + +@ 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 |unsigned char| 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 */ +unsigned char *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 = mp_memory_allocate((size_t) (mp->buf_size + 1) * sizeof(unsigned char)); + +@ @<Dealloc variables@>= +mp_memory_free(mp->buffer); + +@ @c +static void mp_reallocate_buffer (MP mp, size_t l) +{ + if (l > max_halfword) { + mp_confusion(mp, "buffer size"); /* can't happen (I hope) */ + } else { + unsigned char *buffer = mp_memory_allocate((size_t) (l + 1) * sizeof(unsigned char)); + memcpy(buffer, mp->buffer, (mp->buf_size + 1)); + mp_memory_free(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 |unsigned char| 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 int 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_file)(mp, f, &size); + if (s == NULL) { + return 0; + } else 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))); + } + } + memcpy((mp->buffer + mp->first), s, size); + } + // lmt_generic_free(s); + lmt_memory_free(s); + return 1; +} + +@ 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 */ + +@ 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@> + +@<MPlib internal header stuff@>= +# define update_terminal() mp_print_nl_only(mp); /* empty the terminal output buffer */ +# define clear_terminal() /* clear the terminal input buffer */ +# define wake_up_terminal() mp_print_nl_only(mp); /* cancel the user's cancellation of output */ + +@ 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|. + +@d loc mp->cur_input.loc_field /* location of first unread character in |buffer| */ + +@* Globals for strings. + +@ 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 |mp_string|. + +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 mp_lstring { + unsigned char *str; /* the string value */ + size_t len; /* its length */ + int refs; /* number of references */ +} mp_lstring; + +typedef mp_lstring *mp_string; /* for pointers to string values */ + +@ The string handling functions are in |mpstrings.w|, but strings need a bunch +of globals and those are defined here in the main file. + +@<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| */ + +@ @<Allocate or initialize ...@>= +mp_initialize_strings(mp); + +@ @<Dealloc variables@>= +mp_dealloc_strings(mp); + +@ The next four variables are for keeping track of string memory usage. + +@<Glob...@>= +int pool_in_use; /* total number of string bytes actually in use */ +int max_pl_used; /* maximum |pool_in_use| so far */ +int strs_in_use; /* total number of strings actually in use */ +int max_strs_used; /* maximum |strs_in_use| so far */ + +@* 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 |>= first_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|. We no longer use that +magic and just test the constants. + +Two additional global variables, |term_offset| and |file_offset| record if +characters have been printed since they were most recently cleared. We use +|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 piped into \LUA. + +@<MPlib internal header stuff@>= +typedef enum mp_selectors { + mp_new_string_selector, /* printing is deflected to the string pool */ + mp_no_print_selector, /* |selector| setting that makes data disappear */ + mp_term_only_selector, /* printing is destined for the terminal only */ + mp_log_only_selector, /* printing is destined for the transcript file only */ + mp_term_and_log_selector, /* normal |selector| setting */ + mp_first_file_selector, /* first write file selector */ +} mp_selectors; + +typedef enum mp_logging_targets { + mp_void_logging_target, + mp_term_logging_target, + mp_file_logging_target, + mp_both_logging_target, + mp_error_logging_target, +} mp_logging_targets; + +@ @<Glob...@>= +unsigned int selector; /* where to print a message */ +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 */ + +@ @<Initialize the output routines@>= +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@> + +@<MPlib internal header stuff@>= +# define mp_fputs(b,f) (mp->write_file)(mp, f, b) + +# define mp_log_string(target,s) (mp->run_logger)(mp, target, s, strlen(s)) +# define mp_log_mpstr(target,s,l) (mp->run_logger)(mp, target, s, l) +# define mp_log_cr(target) (mp->run_logger)(mp, target, "\n", 1) +# define mp_log_chr(target,s) { unsigned char ss[2] = { s, 0 }; (mp->run_logger)(mp, target, (const char *) ss, 1); } +# define mp_log_error(s) (mp->run_logger)(mp, mp_error_logging_target, s, strlen(s)) + +@ 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. + +The names of the print functions are more or less in sync with the ones used +in the \LUAMETATEX\ source code. + +@<Declarations@>= +static void mp_print_str (MP mp, const char *s); +static void mp_print_nl (MP mp, const char *s); +static void mp_print_fmt (MP mp, const char *s, ...); +static void mp_print_ln (MP mp); +static void mp_print_chr (MP mp, unsigned char k); +static void mp_print_mp_str (MP mp, mp_string s); +static void mp_print_nl (MP mp, const char *s); +static void mp_print_two (MP mp, mp_number *x, mp_number *y); + +@ @<Exported function headers@>= +extern void mp_print_e_str (MP mp, const char *s); +extern void mp_print_e_chr (MP mp, unsigned char k); + +@ @c +static void mp_print_ln (MP mp) +{ + switch (mp->selector) { + case mp_term_and_log_selector: + mp_log_cr(mp_both_logging_target); + mp->term_offset = 0; + mp->file_offset = 0; + break; + case mp_log_only_selector: + mp_log_cr(mp_file_logging_target); + mp->file_offset = 0; + break; + case mp_term_only_selector: + mp_log_cr(mp_term_logging_target); + mp->term_offset = 0; + break; + case mp_no_print_selector: + case mp_new_string_selector: + break; + default: + mp_fputs("\n", mp->wr_file[mp->selector - mp_first_file_selector]); + } +} + +@ The |print_char| procedure sends one character to the desired destination. All +printing comes through |print_ln| or |print_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 \POSTSCRIPT\ output file since it is not safe to cut up lines +arbitrarily in \POSTSCRIPT. Anyway, we don't have a backend other than \LUA\ so +we just flush all without checking, so the nicely cleaned up offset code is +now gone too (just a boolean) so we lost |max_print_line|, |error_line| etc. + +@c +static void mp_print_chr (MP mp, unsigned char s) +{ + switch (mp->selector) { + case mp_term_and_log_selector: + mp_log_chr(mp_both_logging_target, s); + mp->term_offset = 1; + mp->file_offset = 1; + break; + case mp_log_only_selector: + mp_log_chr(mp_file_logging_target, s); + mp->file_offset = 1; + break; + case mp_term_only_selector: + mp_log_chr(mp_term_logging_target, s); + mp->term_offset = 1; + break; + case mp_no_print_selector: + break; + case mp_new_string_selector: + mp_str_room(mp, 1); + mp_append_char(mp, s); + break; + default: + { + unsigned char ss[2] = { s, 0 }; + mp_fputs((char *) ss, mp->wr_file[mp->selector - mp_first_file_selector]); + } + } +} + +void mp_print_e_chr (MP mp, unsigned char s) +{ + mp_print_chr(mp, s); +} + +static void mp_do_print (MP mp, const char *s, size_t len) +{ + if (len == 0) { + return; + } else if (mp->selector == mp_new_string_selector) { + mp_str_room(mp, (int) len); + memcpy((mp->cur_string + mp->cur_length), s, len); + mp->cur_length += len; + } else { + switch (mp->selector) { + case mp_term_and_log_selector: + mp_log_mpstr(mp_both_logging_target, s, (int) len); + mp->term_offset = 1; + mp->file_offset = 1; + break; + case mp_log_only_selector: + mp_log_mpstr(mp_file_logging_target, s, (int) len); + mp->file_offset = 1; + break; + case mp_term_only_selector: + mp_log_mpstr(mp_term_logging_target, s, (int) len); + mp->term_offset = 1; + break; + case mp_no_print_selector: + break; + case mp_new_string_selector: + mp_str_room(mp, (int) len); + mp_append_str(mp, s); + break; + default: + mp_fputs(s, mp->wr_file[mp->selector - mp_first_file_selector]); + break; + } + } +} + +static void mp_print_str (MP mp, const char *s) +{ + mp_do_print(mp, s, strlen(s)); +} + +void mp_print_e_str (MP mp, const char *s) +{ + mp_print_str(mp,s); +} + +static void mp_print_fmt (MP mp, const char *s, ...) +{ + va_list ap; + char pval[256]; + va_start(ap, s); + vsnprintf(pval, 256, s, ap); + mp_do_print(mp, pval, strlen(pval)); + va_end(ap); +} + +static void mp_print_mp_str (MP mp, mp_string s) +{ + 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. + +@<Initialize the output...@>= +mp_log_string(mp_term_logging_target, mp->banner); +mp_log_string(mp_term_logging_target, ", running in "); +mp_log_string(mp_term_logging_target, mp_str(mp, internal_string(mp_number_system_internal))); +mp_log_string(mp_term_logging_target, " mode."); +mp_print_ln(mp); +update_terminal(); + +@ @<Declarations@>= +static void mp_print_nl_only (MP mp); + +@ The procedure |print_nl| is like |print|, but it makes sure that the string +appears at the beginning of a new line. + +@c +static void mp_print_nl_only (MP mp) +{ + switch (mp->selector) { + case mp_term_and_log_selector: + if (mp->file_offset > 0) { + mp_log_cr(mp_file_logging_target); + mp->file_offset = 0; + } + if (mp->term_offset > 0) { + mp_log_cr(mp_term_logging_target); + mp->term_offset = 0; + } + break; + case mp_log_only_selector: + if (mp->file_offset > 0) { + mp_log_cr(mp_file_logging_target); + mp->file_offset = 0; + } + break; + case mp_term_only_selector: + if (mp->term_offset > 0) { + mp_log_cr(mp_term_logging_target); + mp->term_offset = 0; + } + break; + case mp_no_print_selector: + case mp_new_string_selector: + break; + } +} + +static void mp_print_nl (MP mp, const char *s) +{ + mp_print_nl_only(mp); + mp_print_str(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@> + +@c +static void mp_print_int (MP mp, int n) +{ + char s[12]; + mp_snprintf(s, 12, "%d", (int) n); + mp_print_str(mp, s); +} + +@ @<Declarations@>= +static void mp_print_int (MP mp, int n); + +@* Reporting errors. + +@ The global variable |interaction| has four settings, representing increasing +amounts of user interaction: + +@<Exported types@>= +enum mp_interaction_mode { + mp_unspecified_mode, /* 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 */ + mp_silent_mode /* stops at every opportunity to interact but not always*/ +}; + +@ @<Option variables@>= +int interaction; /* current level of interaction */ +int extensions; + +@ 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_silent_mode) { + mp->interaction = mp_error_stop_mode; +} +if (mp->interaction < mp_unspecified_mode) { + mp->interaction = mp_batch_mode; +} + +@ \MP\ is careful not to call |error| when the print |selector| setting might be +unusual. The only possible values of |selector| at the time of error messages are + +\yskip + +\hang|no_print| (when |interaction=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). + +@ 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, /* |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...@>= +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. + +@ 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_begin_file_reading (MP mp); + +@ @<Exported function ...@>= +extern void mp_show_context (MP mp); + +@ @<Internal ...@>= +void mp_normalize_selector (MP mp); + +@ @<Glob...@>= +int use_err_help; /* should the |err_help| string be shown? */ +int padding_help; /* well ... why not. */ +mp_string err_help; /* a string set up by |errhelp| */ + +@ @<Allocate or ...@>= +mp->use_err_help = 0; + +@ The |jump_out| procedure just cuts across all active procedure levels and goes +to |end_of_MP|. This is the only nonlocal |goto| statement in the whole program. +It is used when there is no recovery from a particular error. + +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 entry 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...@>= +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); +} + +@ @<Internal ...@>= +void mp_jump_out (MP mp); + +@ @<Error hand...@>= +void mp_warn (MP mp, const char *msg) +{ + int selector = mp->selector; + mp_normalize_selector(mp); + mp_print_nl(mp, "Warning: "); + mp_print_str(mp, msg); + mp_print_ln(mp); + mp->selector = selector; +} + +@ Here now is the general |error| routine. + +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. + +@c +void mp_error (MP mp, const char *msg, const char *hlp) +{ + int selector = mp->selector; + mp_normalize_selector(mp); + mp->run_error(mp, msg, hlp, mp->interaction); + if (mp->history < mp_error_message_issued) { + mp->history = mp_error_message_issued; + } + if (mp->halt_on_error) { + mp->history = mp_fatal_error_stop; + mp_jump_out(mp); + } + ++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); + } + mp->selector = selector; +} + +@ @<Exported function ...@>= +extern void mp_error (MP mp, const char *msg, const char *hlp); +extern void mp_warn (MP mp, const char *msg); + +@ 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) +{ + mp->selector = mp->interaction == mp_batch_mode ? mp_log_only_selector : mp_term_and_log_selector; +} + +@ The following procedure prints \MP's last words before dying. + +@<Error hand...@>= +void mp_fatal_error (MP mp, const char *s) +{ + /* prints |s|, and that's it */ + mp_normalize_selector(mp); + if (mp->interaction == mp_error_stop_mode) { + /* no more interaction */ + mp->interaction = mp_scroll_mode; + } + mp_error(mp, "Emergency stop", s); + mp->history = mp_fatal_error_stop; + /* irrecoverable error */ + mp_jump_out(mp); +@.Emergency stop@> +} + +@ @<Exported function ...@>= +extern void mp_fatal_error (MP mp, const char *s); + +@ 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 \quote {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]; + const char *hlp = NULL; + mp_normalize_selector(mp); + if (mp->history < mp_error_message_issued) { + mp_snprintf(msg, 256, "This can't happen (%s)", s); + hlp = + "I'm broken. Please show this to someone who can fix can fix it and try\n" + "again"; + @.This can't happen@> + } else { + hlp = + "One of your faux pas seems to have wounded me deeply ... in fact, I'm barely\n" + "conscious. Please fix it and try again."; + mp_snprintf(msg, 256, "I can't go on meeting you like this"); + @.I can't go on...@> + } + if (mp->interaction == mp_error_stop_mode) { + /* no more interaction */ + mp->interaction = mp_scroll_mode; + } + mp_error(mp, msg, hlp); + mp->history=mp_fatal_error_stop; + /* irrecoverable error */ + mp_jump_out(mp); +} + +@ A couple of state variables: + +@<Global...@>= +int run_state; /* are we processing input ? */ +int finished; /* set true by |close_files_and_terminate| */ + +@ @<Allocate or ...@>= +mp->finished = 0; + +@* 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: \quotation {translation-preserving,} in which the identity |(a + q * +b) / b = (a / b) + q| is valid; and \quotation {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 inf_t mp->math->md_inf_t +@d negative_inf_t mp->math->md_negative_inf_t + +@ 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...@>= +int arith_error; + +@ @<Allocate or ...@>= +mp->arith_error = 0; + +@ At crucial points the program will say |check_arith|, to test if an arithmetic +error has been detected. + +@d check_arith() + if (mp->arith_error) { + mp_clear_arith(mp); + } + +@c +static void mp_clear_arith (MP mp) { + mp_error( + mp, + "Arithmetic overflow", + "Uh, oh. A little while ago one of the quantities that I was computing got too\n" + "large, so I'm afraid your answers will be somewhat askew. You'll probably have to\n" + "adopt different tactics next time. But I shall try to carry on anyway." + ); + @.Arithmetic overflow@> + mp->arith_error = 0; +} + +@ The definitions of these are set up by the math initialization. Here +|arc_tol_k| is the criterium to quit when change in arc length +estimate reaches it. + +@d arc_tol_k mp->math->md_arc_tol_k +@d coef_bound_k mp->math->md_coef_bound_k +@d coef_bound_minus_1 mp->math->md_coef_bound_minus_1 +@d sqrt_8_e_k mp->math->md_sqrt_8_e_k +@d twelve_ln_2_k mp->math->md_twelve_ln_2_k +@d twelvebits_3 mp->math->md_twelvebits_3 +@d one_k mp->math->md_one_k +@d epsilon_t mp->math->md_epsilon_t +@d unity_t mp->math->md_unity_t +@d zero_t mp->math->md_zero_t +@d two_t mp->math->md_two_t +@d three_t mp->math->md_three_t +@d half_unit_t mp->math->md_half_unit_t +@d three_quarter_unit_t mp->math->md_three_quarter_unit_t +@d twentysixbits_sqrt2_t mp->math->md_twentysixbits_sqrt2_t +@d twentyeightbits_d_t mp->math->md_twentyeightbits_d_t +@d twentysevenbits_sqrt2_d_t mp->math->md_twentysevenbits_sqrt2_d_t +@d warning_limit_t mp->math->md_warning_limit_t +@d precision_default mp->math->md_precision_default +@d precision_min mp->math->md_precision_min +@d precision_max mp->math->md_precision_max + +@ In fact, the two sorts of scaling discussed above aren't quite sufficient; \MP\ +has yet another, used internally to keep track of angles. + +@ We often want to print two scaled quantities in parentheses, separated by a +comma. + +@c +static void mp_print_two (MP mp, mp_number *x, mp_number *y) +{ + mp_print_chr(mp, '('); + print_number(*x); + mp_print_chr(mp, ','); + print_number(*y); + mp_print_chr(mp, ')'); +} + +@d fraction_one_t mp->math->md_fraction_one_t +@d fraction_half_t mp->math->md_fraction_half_t +@d fraction_three_t mp->math->md_fraction_three_t +@d fraction_four_t mp->math->md_fraction_four_t +@d one_eighty_deg_t mp->math->md_one_eighty_deg_t +@d negative_one_eighty_deg_t mp->math->md_negative_one_eighty_deg_t +@d three_sixty_deg_t mp->math->md_three_sixty_deg_t + +@ 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 {\em 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...@>= +mp_number randoms[55]; /* the last 55 random values generated */ +int j_random; /* the number of unused |randoms| */ +int j_padding; /* the number of unused |randoms| */ + +@ @<Option variables@>= +int random_seed; /* the default random seed */ + +@ @<Allocate or initialize ...@>= +mp->random_seed = opt->random_seed; +for (int i = 0; i < 55; i++) { + new_fraction(mp->randoms[i]); +} + +@ @<Dealloc...@>= +for (int i = 0; i < 55; i++) { + free_number(mp->randoms[i]); +} + +@ @<Internal library ...@>= +void mp_new_randoms (MP mp); + +@ @c +void mp_new_randoms (MP mp) +{ + mp_number x; /* accumulator */ + new_number(x); + for (int k = 0; k <= 23; k++) { + set_number_from_subtraction(x, mp->randoms[k], mp->randoms[k + 31]); + if (number_negative(x)) { + number_add(x, fraction_one_t); + } + number_clone(mp->randoms[k], x); + } + for (int k = 24; k <= 54; k++) { + set_number_from_subtraction(x, mp->randoms[k], mp->randoms[k - 24]); + if (number_negative(x)) { + number_add(x, fraction_one_t); + } + number_clone(mp->randoms[k], x); + } + free_number(x); + mp->j_random = 54; +} + +@ To consume a random fraction, the program below will say |next_random|. Now +each number system has its own implementation, true to the original as much as +possibile. + +@ 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. This is the original one, that +stays as reference: As said before, now each number system has its own +implementation. + +@ Finally, a normal deviate with mean zero and unit standard deviation can +readily be obtained with the ratio method (Algorithm 3.4.1R in {\em The Art of +Computer Programming}). This is the original one, that stays as reference: Now +each number system has its own implementation, true to the original as much as +possibile. + +@ The random related code is in the number system modules. + +@* Packed data. + +@d max_quarterword 0x3FFF /* largest allowable value in a |quarterword| */ +@d max_halfword 0xFFFFFFF /* largest allowable value in a |halfword| */ + +@ 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 unsigned short quarterword; /* 1/4 of a 64 bit word */ +typedef int halfword; /* 1/2 of a 64 bit word */ + +typedef struct mp_independent_data { + int scale; /* only for |indep_scale|, used together with |serial| */ + int serial; /* only for |indep_value|, used together with |scale| */ +} mp_independent_data; + +typedef struct mp_value_data { + mp_independent_data indep; + mp_number n; + mp_string str; + mp_sym sym; + mp_node node; + mp_knot p; +} mp_value_data; + +typedef struct mp_value { + mp_variable_type type; + int padding; + mp_value_data data; +} mp_value; + +@ The global variable |math_mode| has four settings, representing the math value +type that will be used in this run. + +The typedef for |mp_number| is here because it has to come very early. + +@<Exported types@>= +typedef enum mp_math_mode { + mp_math_scaled_mode, + mp_math_double_mode, + mp_math_binary_mode, + mp_math_decimal_mode +} mp_math_mode; + +@ @<Option variables@>= +int math_mode; /* math mode */ + +@ @<Allocate or initialize ...@>= +mp->math_mode = opt->math_mode; + +@ Most important memory is kept in a chain so we don't need to allocate that +often. We could (at some point) decide to use mimalloc. + +@<Declare helpers@>= +extern void *mp_memory_allocate (size_t size); +extern void *mp_memory_clear_allocate (size_t size); +extern void *mp_memory_reallocate (void *p, size_t size); +extern void mp_memory_free (void *p); + +extern void *mp_allocate_node (MP mp, size_t size); +extern void *mp_allocate_dash (MP mp); + +@ This is an attempt to spend less time in |malloc()|: + +@d max_num_token_nodes 8000 /* maybe make this configureable */ +@d max_num_pair_nodes 1000 +@d max_num_knot_nodes 1000 +@d max_num_value_nodes 1000 +@d max_num_symbolic_nodes 1000 + +@<Global ...@>= +mp_node token_nodes; +mp_node pair_nodes; +int num_token_nodes; +int num_pair_nodes; +mp_knot knot_nodes; +mp_node value_nodes; +int max_knot_nodes; +int num_knot_nodes; +int num_value_nodes; +mp_node symbolic_nodes; +int num_symbolic_nodes; + +@ @<Allocate or initialize ...@>= +mp->token_nodes = NULL; +mp->num_token_nodes = 0; +mp->pair_nodes = NULL; +mp->num_pair_nodes = 0; +mp->knot_nodes = NULL; +mp->max_knot_nodes = max_num_knot_nodes; +mp->num_knot_nodes = 0; +mp->value_nodes = NULL; +mp->num_value_nodes = 0; +mp->symbolic_nodes = NULL; +mp->num_symbolic_nodes = 0; + +@ @<Dealloc ...@>= +while (mp->value_nodes) { + mp_node p = mp->value_nodes; + mp->value_nodes = p->link; + mp_free_node(mp, p, sizeof(mp_value_node_data)); +} +while (mp->symbolic_nodes) { + mp_node p = mp->symbolic_nodes; + mp->symbolic_nodes = p->link; + mp_free_node(mp, p, sizeof(mp_node_data)); +} +while (mp->pair_nodes) { + mp_node p = mp->pair_nodes; + mp->pair_nodes = p->link; + mp_free_node(mp, p, sizeof(mp_pair_node_data)); +} +while (mp->token_nodes) { + mp_node p = mp->token_nodes; + mp->token_nodes = p->link; + mp_free_node(mp, p, sizeof(mp_node_data)); +} +while (mp->knot_nodes) { + mp_knot p = mp->knot_nodes; + mp->knot_nodes = p->next; + mp_free_knot(mp, p); +} + +@ This is a nicer way of allocating nodes. 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. All kind of statistics are +available on request but we no longer display them in the library. + +@ @<Glob...@>= +size_t var_used; /* how much memory is in use */ +size_t var_used_max; /* how much memory was in use max */ + +@ @c +void *mp_allocate_node (MP mp, size_t size) +{ + void *p = mp_memory_allocate(size); + ((mp_node) p)->link = NULL; + ((mp_node) p)->hasnumber = 0; + mp->var_used += size; + if (mp->var_used > mp->var_used_max) { + mp->var_used_max = mp->var_used; + } + return p; +} + +void *mp_allocate_dash (MP mp) +{ + void *p = mp_memory_allocate(sizeof(mp_dash_object)); + mp->var_used += sizeof(mp_dash_object); + if (mp->var_used > mp->var_used_max) { + mp->var_used_max = mp->var_used; + } + return p; +} + +@ We want to be able to overload the allocator but then we also need to +pass to the avl handler and that one doesn't take the |mp| pointer so +we just do a hard exit. + +@c +void *mp_memory_allocate (size_t size) +{ + void *w = lmt_memory_malloc(size); + if (! w) { + printf("mplib ran out of memory, case 1"); + exit(EXIT_FAILURE); + } + return w; +} + +void *mp_memory_clear_allocate (size_t size) +{ + void *w = lmt_memory_calloc(1, size); + if (! w) { + printf("mplib ran out of memory, case 2"); + exit(EXIT_FAILURE); + } + return w; +} + +void *mp_memory_reallocate (void *p, size_t size) +{ + void *w = lmt_memory_realloc(p, size); + if (! w) { + printf("mplib ran out of memory, case 3"); + exit(EXIT_FAILURE); + } + return w; +} + +void mp_memory_free (void *p) +{ + lmt_memory_free(p); +} + +@ @<Internal library declarations@>= +# define mp_snprintf snprintf + +@* 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_link(A) (A)->link /* the |link| field of a node */ +@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 */ + +@d mp_set_link(A,B) (A)->link = (mp_node) (B) + +@ @<MPlib internal header stuff@>= +typedef struct mp_node_data { + union { + mp_command_code command; + mp_variable_type type; + }; + mp_name_type_type name_type; + int hasnumber; + int padding; + struct mp_node_data *link; + /*specific */ + mp_value_data data; +} mp_node_data; + +typedef struct mp_node_data *mp_symbolic_node; + +@ These will become inline: + +@c +# define mp_get_sym_info(A) mp_get_indep_value(A) +# define mp_set_sym_info(A,B) mp_set_indep_value(A, (B)) +# define mp_get_sym_sym(A) (A)->data.sym +# define mp_set_sym_sym(A,B) (A)->data.sym = (mp_sym)(B) + +@ The function |get_symbolic_node| returns a pointer to a new symbolic node whose +|link| field is null. @^inner loop@> + +@c +static mp_node mp_new_symbolic_node (MP mp) +{ + mp_symbolic_node p; + if (mp->symbolic_nodes) { + p = (mp_symbolic_node) mp->symbolic_nodes; + mp->symbolic_nodes = p->link; + mp->num_symbolic_nodes--; + p->link = NULL; + } else { + p = mp_allocate_node(mp, sizeof(mp_node_data)); + new_number(p->data.n); + p->hasnumber = 1; + } + p->type = mp_symbol_node_type; + p->name_type = mp_normal_operation; + 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|. + +@c +static void mp_free_node (MP mp, mp_node p, size_t siz) +{ + /* node liberation */ + if (p) { + mp->var_used -= siz; + if (mp->math_mode > mp_math_double_mode) { + if (p->hasnumber >= 1 && is_number(((mp_symbolic_node) p)->data.n)) { + free_number(((mp_symbolic_node) p)->data.n); + } + if (p->hasnumber == 2 && is_number(((mp_value_node) p)->subscript)) { + free_number(((mp_value_node) p)->subscript); + } + /* + There was a quite large |switch| here first, but the |mp_dash_node| + case was the only one that did anything ... + */ + if (mp_type(p) == mp_dash_node_type) { + free_number(((mp_dash_node) p)->start_x); + free_number(((mp_dash_node) p)->stop_x); + free_number(((mp_dash_node) p)->dash_y); + } + } + mp_memory_free(p); + } +} + +static void mp_free_symbolic_node (MP mp, mp_node p) +{ + /* node liberation */ + if (p) { + if (mp->num_symbolic_nodes < max_num_symbolic_nodes) { + p->link = mp->symbolic_nodes; + mp->symbolic_nodes = p; + mp->num_symbolic_nodes++; + } else { + mp->var_used -= sizeof(mp_node_data); + mp_memory_free(p); + } + } +} + +static void mp_free_value_node (MP mp, mp_node p) +{ + /* node liberation */ + if (p) { + if (mp->num_value_nodes < max_num_value_nodes) { + p->link = mp->value_nodes; + mp->value_nodes = p; + mp->num_value_nodes++; + } else { + mp->var_used -= sizeof(mp_value_node_data); + if (mp->math_mode > mp_math_double_mode) { + free_number(((mp_value_node) p)->data.n); + free_number(((mp_value_node) p)->subscript); + } + mp_memory_free(p); + } + } +} + +@ @<declarations@>= +static void mp_free_node (MP mp, mp_node p, size_t siz); +static void mp_free_symbolic_node (MP mp, mp_node p); +static void mp_free_value_node (MP mp, mp_node p); + +@* 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_dash_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_new_symbolic_node(mp); +mp->temp_head = mp_new_symbolic_node(mp); +mp->hold_head = mp_new_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) +{ + while (p != NULL) { + mp_node q = p; + p = p->link; + if (q->type != mp_symbol_node_type) { + mp_free_token_node(mp, q); + } 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 mp_max_command_code mp_stop +@d mp_max_pre_command mp_etex_command +@d mp_min_command (mp_defined_macro_command+1) +@d mp_max_statement_command mp_type_name_command +@d mp_min_primary_command mp_type_name_command +@d mp_min_suffix_token mp_internal_command +@d mp_max_suffix_token mp_numeric_command +@d mp_max_primary_command mp_plus_or_minus_command /* should also be |numeric_token+1| */ +@d mp_min_tertiary_command mp_plus_or_minus_command +@d mp_max_tertiary_command mp_tertiary_binary_command +@d mp_min_expression_command mp_left_brace_command +@d mp_max_expression_command mp_equals_command +@d mp_min_secondary_command mp_and_command +@d mp_max_secondary_command mp_secondary_binary_command +@d mp_end_of_statement (cur_cmd>mp_comma_command) + +@<Enumeration types@>= +typedef enum mp_command_code { + mp_undefined_command, + mp_btex_command, /* begin \TeX\ material (|btex|, |verbatimtex|) */ + mp_etex_command, /* end \TeX\ material (|etex|) */ + mp_if_test_command, /* conditional text (|if|) */ + mp_fi_or_else_command, /* delimiters for conditionals (|elseif|, |else|, |fi|) */ + mp_input_command, /* input a source file (|input|, |endinput|) */ + mp_iteration_command, /* iterate (|for|, |forsuffixes|, |forever|, |endfor|) */ + mp_repeat_loop_command, /* special command substituted for |endfor| */ + mp_exit_test_command, /* premature exit from a loop (|exitif|) */ + mp_relax_command, /* do nothing (|\char`\\|) */ + mp_scan_tokens_command, /* put a string into the input buffer */ + mp_runscript_command, /* put a script result string into the input buffer */ + mp_maketext_command, /* make a text (typesetting) */ + mp_expand_after_command, /* look ahead one token */ + mp_defined_macro_command, /* a macro defined by the user */ + mp_save_command, /* save a list of tokens (|save|) */ + mp_interim_command, /* save an internal quantity (|interim|) */ + mp_let_command, /* redefine a symbolic token (|let|) */ + mp_new_internal_command, /* define a new internal quantity (|newinternal|) */ + mp_macro_def_command, /* define a macro (|def|, |vardef|, etc.) */ + mp_ship_out_command, /* output a character (|shipout|) */ + mp_add_to_command, /* add to edges (|addto|) */ + mp_bounds_command, /* add bounding path to edges (|setbounds|, |clip|) */ + mp_protection_command, /* set protection flag (|outer|, |inner|) */ + mp_property_command, + mp_show_command, /* diagnostic output (|show|, |showvariable|, etc.) */ + mp_mode_command, /* set interaction level (|batchmode|, etc.) */ + mp_only_set_command, /* initialize random number generator (|randomseed|) */ + mp_message_command, /* communicate to user (|message|, |errmessage|) */ + mp_every_job_command, /* designate a starting token (|everyjob|) */ + mp_delimiters_command, /* define a pair of delimiters (|delimiters|) */ + mp_write_command, /* write text to a file (|write|) */ + mp_type_name_command, /* declare a type (|numeric|, |pair|, etc.) */ + mp_left_delimiter_command, /* the left delimiter of a matching pair */ + mp_begin_group_command, /* beginning of a group (|begingroup|) */ + mp_nullary_command, /* an operator without arguments (e.g., |normaldeviate|) */ + mp_unary_command, /* an operator with one argument (e.g., |sqrt|) */ + mp_str_command, /* convert a suffix to a string (|str|) */ + mp_void_command, /* convert a suffix to a boolean (|void|) */ + mp_cycle_command, /* close a cyclic path (|cycle|) */ + mp_of_binary_command, /* binary operation taking |of| (e.g., |point|) */ + mp_capsule_command, /* a value that has been put into a token list */ + mp_string_command, /* a string constant (e.g., |"hello"|) */ + mp_internal_command, /* internal numeric parameter (e.g., |pausing|) */ + mp_tag_command, /* a symbolic token without a primitive meaning */ + mp_numeric_command, /* a numeric constant (e.g., |3.14159|) */ + mp_plus_or_minus_command, /* either |+| or |-| */ + mp_secondary_def_command, /* a macro defined by |secondarydef| */ + mp_tertiary_binary_command, /* an operator at the tertiary level (e.g., |++|) */ + mp_left_brace_command, /* the operator `|\char||| */ + mp_path_join_command, /* the operator |..| */ + mp_ampersand_command, /* the operator `\.\&' */ + mp_tertiary_def_command, /* a macro defined by |tertiarydef| */ + mp_primary_binary_command, /* an operator at the expression level (e.g., |<|) */ + mp_equals_command, /* the operator |=| */ + mp_and_command, /* the operator |and| */ + mp_primary_def_command, /* a macro defined by |primarydef| */ + mp_slash_command, /* the operator |/| */ + mp_secondary_binary_command, /* an operator at the binary level (e.g., |shifted|) */ + mp_parameter_commmand, /* type of parameter (|primary|, |expr|, |suffix|, etc.) */ + mp_controls_command, /* specify control points explicitly (|controls|) */ + mp_tension_command, /* specify tension between knots (|tension|) */ + mp_at_least_command, /* bounded tension value (|atleast|) */ + mp_curl_command, /* specify curl at an end knot (|curl|) */ + mp_macro_special_command, /* special macro operators (|quote|, |\#\AT!|, etc.) */ + mp_right_delimiter_command, /* the right delimiter of a matching pair */ + mp_left_bracket_command, /* the operator |[| */ + mp_right_bracket_command, /* the operator |]| */ + mp_right_brace_command, /* the operator `|\char|}| */ + mp_with_option_command, /* option for filling (|withpen|, |withweight|, etc.) */ + mp_thing_to_add_command, /* variant of |addto| (|contour|, |doublepath|, |also|) */ + mp_of_command, /* the operator |of| */ + mp_to_command, /* the operator |to| */ + mp_step_command, /* the operator |step| */ + mp_until_command, /* the operator |until| */ + mp_within_command, /* the operator |within| */ + mp_assignment_command, /* the operator |:=| */ + mp_colon_command, /* the operator |:| */ + mp_comma_command, /* the operator |,|, must be |colon+1| */ + mp_semicolon_command, /* the operator |;|, must be |comma+1| */ + mp_end_group_command, /* end a group (|endgroup|), must be |semicolon+1| */ + mp_stop_command, /* end a job (|end|, |dump|), must be |end_group+1| */ + // mp_outer_tag_command, /* protection code added to command code */ + mp_undefined_cs_command, /* protection code added to command code */ +} mp_command_code; + +@ Variables and capsules in \MP\ have a variety of \quote {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 unknown_tag 1 /* this constant is added to certain type codes below */ + +@<Enumeration types@>= +typedef enum mp_variable_type { + mp_undefined_type, /* no type has been declared */ + mp_vacuous_type, /* no expression was present */ + mp_boolean_type, /* |boolean| with a known value */ + mp_unknown_boolean_type, + mp_string_type, /* |string| with a known value */ + mp_unknown_string_type, + mp_pen_type, /* |pen| with a known value */ + mp_unknown_pen_type, + mp_nep_type, /* |pen| with a known value */ + mp_unknown_nep_type, + mp_path_type, /* |path| with a known value */ + mp_unknown_path_type, + mp_picture_type, /* |picture| with a known value */ + mp_unknown_picture_type, + 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_type, /* |numeric| with a known value */ + mp_dependent_type, /* a linear combination with |fraction| coefficients */ + mp_proto_dependent_type, /* a linear combination with |scaled| coefficients */ + mp_independent_type, /* |numeric| with unknown value */ + mp_token_list_type, /* variable name or suffix argument or text argument */ + mp_structured_type, /* variable with subscripts and attributes */ + mp_unsuffixed_macro_type, /* variable defined with |vardef| but no |\AT!\#| */ + mp_suffixed_macro_type, /* variable defined with |vardef| and |\AT!\#| */ + + mp_symbol_node_type, + mp_token_node_type, + mp_value_node_type, + mp_attribute_node_type, + mp_subscript_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 as + well as switch/case offsets. + */ + + mp_fill_node_type, + mp_stroked_node_type, + mp_start_clip_node_type, + mp_start_group_node_type, + mp_start_bounds_node_type, + + mp_stop_clip_node_type, + mp_stop_group_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, int t); + +@ @c +static const char *mp_type_string(int t) +{ + const char *s = NULL; + switch (t) { + case mp_undefined_type: s = "undefined"; break; + case mp_vacuous_type: s = "vacuous"; break; + case mp_boolean_type: s = "boolean"; break; + case mp_unknown_boolean_type: s = "unknown boolean"; break; + case mp_string_type: s = "string"; break; + case mp_unknown_string_type: s = "unknown string"; break; + case mp_pen_type: s = "pen"; break; + case mp_unknown_pen_type: s = "unknown pen"; break; + case mp_nep_type: s = "pen"; break; + case mp_unknown_nep_type: s = "unknown pen"; break; + case mp_path_type: s = "path"; break; + case mp_unknown_path_type: s = "unknown path"; break; + case mp_picture_type: s = "picture"; break; + case mp_unknown_picture_type: 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_type: s = "known numeric"; break; + case mp_dependent_type: s = "dependent"; break; + case mp_proto_dependent_type: s = "proto-dependent"; break; + case mp_numeric_type: s = "numeric"; break; + case mp_independent_type: s = "independent"; break; + case mp_token_list_type: s = "token list"; break; + case mp_structured_type: s = "mp_structured"; break; + case mp_unsuffixed_macro_type: s = "unsuffixed macro"; break; + case mp_suffixed_macro_type: s = "suffixed macro"; break; + case mp_symbol_node_type: s = "symbol node"; break; + case mp_token_node_type: s = "token node"; break; + case mp_value_node_type: s = "value node"; break; + case mp_attribute_node_type: s = "attribute node"; break; + case mp_subscript_node_type: s = "subscript node"; break; + case mp_pair_node_type: s = "pair node"; break; + case mp_transform_node_type: s = "transform node"; break; + case mp_color_node_type: s = "color node"; break; + case mp_cmykcolor_node_type: s = "cmykcolor node"; break; + case mp_fill_node_type: s = "fill node"; break; + case mp_stroked_node_type: s = "stroked node"; break; + case mp_start_clip_node_type: s = "start clip node"; break; + case mp_start_group_node_type: s = "start group 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_group_node_type: s = "stop group 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: + { + char ss[256]; + mp_snprintf(ss, 256, "<unknown type %d>", t); + s = mp_strdup(ss); + } + break; + } + return s; +} + +void mp_print_type (MP mp, int t) +{ + if (t >= 0 && t <= mp_edge_header_node_type) { + mp_print_str(mp, mp_type_string(t)); + } else { + mp_print_str(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. + +@<Enumeration types...@>= +typedef enum mp_name_type_type { + mp_root_operation, /* |name_type| at the top level of a variable */ + mp_saved_root_operation, /* same, when the variable has been saved */ + mp_structured_root_operation, /* |name_type| where a |mp_structured| branch occurs */ + mp_subscript_operation, /* |name_type| in a subscript node */ + mp_attribute_operation, /* |name_type| in an attribute node */ + mp_x_part_operation, /* |name_type| in the |xpart| of a node */ + mp_y_part_operation, /* |name_type| in the |ypart| of a node */ + mp_xx_part_operation, /* |name_type| in the |xxpart| of a node */ + mp_xy_part_operation, /* |name_type| in the |xypart| of a node */ + mp_yx_part_operation, /* |name_type| in the |yxpart| of a node */ + mp_yy_part_operation, /* |name_type| in the |yypart| of a node */ + mp_red_part_operation, /* |name_type| in the |redpart| of a node */ + mp_green_part_operation, /* |name_type| in the |greenpart| of a node */ + mp_blue_part_operation, /* |name_type| in the |bluepart| of a node */ + mp_cyan_part_operation, /* |name_type| in the |redpart| of a node */ + mp_magenta_part_operation, /* |name_type| in the |greenpart| of a node */ + mp_yellow_part_operation, /* |name_type| in the |bluepart| of a node */ + mp_black_part_operation, /* |name_type| in the |greenpart| of a node */ + mp_grey_part_operation, /* |name_type| in the |greypart| of a node */ + mp_capsule_operation, /* |name_type| in stashed-away subexpressions */ + mp_token_operation, /* |name_type| in a numeric token or string token */ + + mp_boolean_type_operation, /* the order needs to match the types (as we use deltas) ! */ + mp_string_type_operation, + mp_pen_type_operation, + mp_nep_type_operation, + mp_path_type_operation, + mp_picture_type_operation, + mp_transform_type_operation, + mp_color_type_operation, + mp_cmykcolor_type_operation, + mp_pair_type_operation, + mp_numeric_type_operation, + + /* Symbolic nodes also have |name_type|, which is a different enumeration */ + + mp_normal_operation, + mp_internal_operation, /* for values of internals */ + mp_macro_operation, /* for macro names */ + mp_expr_operation, /* for macro parameters if type |expr| */ + mp_suffix_operation, /* for macro parameters if type |suffix| */ + mp_text_operation, /* for macro parameters if type |text| */ + @<Operation codes@> +} mp_name_type_type; + +@ 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 such 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_operation..mp_blue_part_operation|, +and the ordering of |filled_op..bounded_op| must match that of the code values +they test for. + +Beware! The operation and type unumerations in some places run in parallel (with +an offset. That makes it possible the handle types with common code using a +delta. In some cases the delta is multiplied by 2 because we have knowns and +unknowns. A less sensitive to patches would be to just duplicate the code (or to +use a function call), + +@d mp_min_of_operation mp_substring_operation + +@<Operation codes@>= +mp_true_operation, /* operation code for |true| */ +mp_false_operation, /* operation code for |false| */ +mp_null_picture_operation, /* operation code for |nullpicture| */ +mp_null_pen_operation, /* operation code for |nullpen| */ +mp_read_string_operation, /* operation code for |readstring| */ +mp_pen_circle_operation, /* operation code for |pencircle| */ +mp_normal_deviate_operation, /* operation code for |normaldeviate| */ +mp_read_from_operation, /* operation code for |readfrom| */ +mp_close_from_operation, /* operation code for |closefrom| */ +mp_odd_operation, /* operation code for |odd| */ +mp_known_operation, /* operation code for |known| */ +mp_unknown_operation, /* operation code for |unknown| */ +mp_not_operation, /* operation code for |not| */ +mp_decimal_operation, /* operation code for |decimal| */ +mp_reverse_operation, /* operation code for |reverse| */ +mp_uncycle_operation, /* operation code for |uncycle| */ +mp_make_path_operation, /* operation code for |makepath| */ +mp_make_pen_operation, /* operation code for |makepen| */ +mp_make_nep_operation, /* operation code for |makenep| */ +mp_convexed_operation, /* operation code for |convexed| */ +mp_uncontrolled_operation, /* operation code for |uncontrolled| */ +mp_oct_operation, /* operation code for |oct| */ +mp_hex_operation, /* operation code for |hex| */ +mp_ASCII_operation, /* operation code for |ASCII| */ +mp_char_operation, /* operation code for |char| */ +mp_length_operation, /* operation code for |length| */ +mp_turning_operation, /* operation code for |turningnumber| */ +mp_color_model_operation, /* operation code for |colormodel| */ +mp_path_part_operation, /* operation code for |pathpart| */ +mp_pen_part_operation, /* operation code for |penpart| */ +mp_dash_part_operation, /* operation code for |dashpart| */ +mp_prescript_part_operation, /* operation code for |prescriptpart| */ +mp_postscript_part_operation, /* operation code for |postscriptpart| */ +mp_stacking_part_operation, /* operation code for |stackingpart| */ +mp_sqrt_operation, /* operation code for |sqrt| */ +mp_m_exp_operation, /* operation code for |mexp| */ +mp_m_log_operation, /* operation code for |mlog| */ +mp_sin_d_operation, /* operation code for |sind| */ +mp_cos_d_operation, /* operation code for |cosd| */ +mp_floor_operation, /* operation code for |floor| */ +mp_uniform_deviate_operation, /* operation code for |uniformdeviate| */ +mp_ll_corner_operation, /* operation code for |llcorner| */ +mp_lr_corner_operation, /* operation code for |lrcorner| */ +mp_ul_corner_operation, /* operation code for |ulcorner| */ +mp_ur_corner_operation, /* operation code for |urcorner| */ +mp_center_of_operation, /* operation code for |centerof| */ +mp_center_of_mass_operation, /* operation code for |centerofmass| */ +mp_corners_operation, /* operation code for |corners| */ +mp_x_range_operation, /* operation code for |xrange| */ +mp_y_range_operation, /* operation code for |yrange| */ +mp_delta_point_operation, /* operation code for |deltapoint| */ +mp_delta_precontrol_operation, /* operation code for |deltaprecontrol| */ +mp_delta_postcontrol_operation,/* operation code for |deltapostcontrol| */ +mp_delta_direction_operation, /* operation code for |deltadirection| */ +mp_arc_length_operation, /* operation code for |arclength| */ +mp_angle_operation, /* operation code for |angle| */ +mp_cycle_operation, /* operation code for |cycle| */ +mp_no_cycle_operation, /* operation code for |nocycle| */ +mp_filled_operation, /* operation code for |filled| */ +mp_stroked_operation, /* operation code for |stroked| */ +mp_clipped_operation, /* operation code for |clipped| */ +mp_grouped_operation, /* operation code for |bounded| */ +mp_bounded_operation, /* operation code for |grouped| */ +mp_plus_operation, /* operation code for \.+ */ +mp_minus_operation, /* operation code for \.- */ +mp_times_operation, /* operation code for \.* */ +mp_over_operation, /* operation code for \./ */ +mp_power_operation, /* operation code for \.^ */ +mp_pythag_add_operation, /* operation code for |++| */ +mp_pythag_sub_operation, /* operation code for |+-+| */ +mp_or_operation, /* operation code for |or| */ +mp_and_operation, /* operation code for |and| */ +mp_less_than_operation, /* operation code for \.< */ +mp_less_or_equal_operation, /* operation code for |<=| */ +mp_greater_than_operation, /* operation code for \.> */ +mp_greater_or_equal_operation, /* operation code for |>=| */ +mp_equal_operation, /* operation code for \.= */ +mp_unequal_operation, /* operation code for |<>| */ +mp_concatenate_operation, /* operation code for \.\& */ +mp_just_append_operation, /* operation code for \.\&\& */ +mp_rotated_operation, /* operation code for |rotated| */ +mp_slanted_operation, /* operation code for |slanted| */ +mp_scaled_operation, /* operation code for |scaled| */ +mp_shifted_operation, /* operation code for |shifted| */ +mp_transformed_operation, /* operation code for |transformed| */ +mp_uncycled_operation, /* operation code for |uncycled| */ +mp_x_scaled_operation, /* operation code for |xscaled| */ +mp_y_scaled_operation, /* operation code for |yscaled| */ +mp_z_scaled_operation, /* operation code for |zscaled| */ +mp_intertimes_operation, /* operation code for |intersectiontimes| */ +mp_intertimes_list_operation, /* operation code for |intersectiontimeslist| */ +mp_double_dot_operation, /* operation code for improper |..| */ +mp_substring_operation, /* operation code for |substring| */ +mp_subpath_operation, /* operation code for |subpath| */ +mp_direction_time_operation, /* operation code for |directiontime| */ +mp_point_operation, /* operation code for |point| */ +mp_precontrol_operation, /* operation code for |precontrol| */ +mp_postcontrol_operation, /* operation code for |postcontrol| */ +mp_direction_operation, /* operation code for |direction| */ +mp_path_point_operation, /* operation code for |pathpoint| */ +mp_path_precontrol_operation, /* operation code for |pathprecontrol| */ +mp_path_postcontrol_operation, /* operation code for |pathpostcontrol| */ +mp_path_direction_operation, /* operation code for |pathdirection| */ +mp_pen_offset_operation, /* operation code for |penoffset| */ +mp_arc_time_operation, /* operation code for |arctime| */ +mp_arc_point_operation, /* operation code for |arcpoint| */ +mp_arc_point_list_operation, /* operation code for |arcpointlist| */ +mp_subarc_length_operation, /* operation code for |subarclength| */ +mp_version_operation, /* operation code for |mpversion| */ +mp_envelope_operation, /* operation code for |envelope| */ +mp_boundingpath_operation, /* operation code for |boundingpath| */ + +@ @c +static const char *mp_op_string (int c) +{ + if (c <= mp_numeric_type) { + return mp_type_string(c); + } else { + switch (c) { + case mp_true_operation : return "true"; + case mp_false_operation : return "false"; + case mp_null_picture_operation : return "nullpicture"; + case mp_null_pen_operation : return "nullpen"; + case mp_read_string_operation : return "readstring"; + case mp_pen_circle_operation : return "pencircle"; + case mp_normal_deviate_operation : return "normaldeviate"; + case mp_read_from_operation : return "readfrom"; + case mp_close_from_operation : return "closefrom"; + case mp_odd_operation : return "odd"; + case mp_known_operation : return "known"; + case mp_unknown_operation : return "unknown"; + case mp_not_operation : return "not"; + case mp_decimal_operation : return "decimal"; + case mp_reverse_operation : return "reverse"; + case mp_uncycle_operation : return "uncycle"; + case mp_make_path_operation : return "makepath"; + case mp_make_pen_operation : return "makepen"; + case mp_make_nep_operation : return "makenep"; + case mp_convexed_operation : return "convexed"; + case mp_uncontrolled_operation : return "uncontrolled"; + case mp_oct_operation : return "oct"; + case mp_hex_operation : return "hex"; + case mp_ASCII_operation : return "ASCII"; + case mp_char_operation : return "char"; + case mp_length_operation : return "length"; + case mp_turning_operation : return "turningnumber"; + case mp_x_part_operation : return "xpart"; + case mp_y_part_operation : return "ypart"; + case mp_xx_part_operation : return "xxpart"; + case mp_xy_part_operation : return "xypart"; + case mp_yx_part_operation : return "yxpart"; + case mp_yy_part_operation : return "yypart"; + case mp_red_part_operation : return "redpart"; + case mp_green_part_operation : return "greenpart"; + case mp_blue_part_operation : return "bluepart"; + case mp_cyan_part_operation : return "cyanpart"; + case mp_magenta_part_operation : return "magentapart"; + case mp_yellow_part_operation : return "yellowpart"; + case mp_black_part_operation : return "blackpart"; + case mp_grey_part_operation : return "greypart"; + case mp_color_model_operation : return "colormodel"; + case mp_prescript_part_operation : return "prescriptpart"; + case mp_postscript_part_operation : return "postscriptpart"; + case mp_stacking_part_operation : return "stackingpart"; + case mp_path_part_operation : return "pathpart"; + case mp_pen_part_operation : return "penpart"; + case mp_dash_part_operation : return "dashpart"; + case mp_sqrt_operation : return "sqrt"; + case mp_m_exp_operation : return "mexp"; + case mp_m_log_operation : return "mlog"; + case mp_sin_d_operation : return "sind"; + case mp_cos_d_operation : return "cosd"; + case mp_floor_operation : return "floor"; + case mp_uniform_deviate_operation : return "uniformdeviate"; + case mp_ll_corner_operation : return "llcorner"; + case mp_lr_corner_operation : return "lrcorner"; + case mp_ul_corner_operation : return "ulcorner"; + case mp_ur_corner_operation : return "urcorner"; + case mp_center_of_operation : return "centerof"; + case mp_center_of_mass_operation : return "centerofmass"; + case mp_corners_operation : return "corners"; + case mp_x_range_operation : return "xrange"; + case mp_y_range_operation : return "yrange"; + case mp_delta_point_operation : return "deltapoint"; + case mp_delta_precontrol_operation : return "deltaprecontrol"; + case mp_delta_postcontrol_operation: return "deltapostcontrol"; + case mp_delta_direction_operation : return "deltadirection"; + case mp_arc_length_operation : return "arclength"; + case mp_angle_operation : return "angle"; + case mp_cycle_operation : return "cycle"; + case mp_no_cycle_operation : return "nocycle"; + case mp_filled_operation : return "filled"; + case mp_stroked_operation : return "stroked"; + case mp_clipped_operation : return "clipped"; + case mp_grouped_operation : return "grouped"; + case mp_bounded_operation : return "bounded"; + case mp_plus_operation : return "+"; + case mp_minus_operation : return "-"; + case mp_times_operation : return "*"; + case mp_over_operation : return "/"; + case mp_power_operation : return "^"; + case mp_pythag_add_operation : return "++"; + case mp_pythag_sub_operation : return "+-+"; + case mp_or_operation : return "or"; + case mp_and_operation : return "and"; + case mp_less_than_operation : return "<"; + case mp_less_or_equal_operation : return "<="; + case mp_greater_than_operation : return ">"; + case mp_greater_or_equal_operation : return ">="; + case mp_equal_operation : return "="; + case mp_unequal_operation : return "<>"; + case mp_concatenate_operation : return "&"; + case mp_just_append_operation : return "&&"; + case mp_rotated_operation : return "rotated"; + case mp_slanted_operation : return "slanted"; + case mp_scaled_operation : return "scaled"; + case mp_shifted_operation : return "shifted"; + case mp_transformed_operation : return "transformed"; + case mp_x_scaled_operation : return "xscaled"; + case mp_y_scaled_operation : return "yscaled"; + case mp_z_scaled_operation : return "zscaled"; + case mp_intertimes_operation : return "intersectiontimes"; + case mp_intertimes_list_operation : return "intersectiontimeslist"; + case mp_substring_operation : return "substring"; + case mp_subpath_operation : return "subpath"; + case mp_direction_time_operation : return "directiontime"; + case mp_point_operation : return "point"; + case mp_precontrol_operation : return "precontrol"; + case mp_postcontrol_operation : return "postcontrol"; + case mp_direction_operation : return "direction"; + case mp_path_point_operation : return "pathpoint"; + case mp_path_precontrol_operation : return "pathprecontrol"; + case mp_path_postcontrol_operation : return "pathpostcontrol"; + case mp_path_direction_operation : return "pathdirection"; + case mp_pen_offset_operation : return "penoffset"; + case mp_arc_time_operation : return "arctime"; + case mp_arc_point_operation : return "arcpoint"; + case mp_arc_point_list_operation : return "arcpointlist"; + case mp_subarc_length_operation : return "subarclength"; + case mp_version_operation : return "mpversion"; + case mp_envelope_operation : return "envelope"; + case mp_boundingpath_operation : return "boundingpath"; + + case mp_pen_type_operation : return "pen"; + case mp_nep_type_operation : return "nep"; + case mp_path_type_operation : return "path"; + case mp_picture_type_operation : return "picture"; + case mp_transform_type_operation : return "transform"; + case mp_color_type_operation : return "color"; + case mp_cmykcolor_type_operation : return "cmykcolor"; + case mp_pair_type_operation : return "pair"; + case mp_numeric_type_operation : return "numeric"; + + default : return ".."; + } + } +} +static void mp_print_op (MP mp, int c) +{ + mp_print_str(mp, mp_op_string(c)); +} + +@ \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...@>= +typedef enum mp_given_internal { + mp_number_system_internal = 1, /* the number system as set up by |numbersystem| */ + mp_number_precision_internal, /* the number system precision as set up by |numberprecision| */ + mp_job_name_internal, /* the jobname as set up from the options stucture */ + mp_tracing_titles_internal, /* show titles online when they appear */ + mp_tracing_equations_internal, /* show each variable when it becomes known */ + mp_tracing_capsules_internal, /* show capsules too */ + mp_tracing_choices_internal, /* show the control points chosen for paths */ + mp_tracing_specs_internal, /* show path subdivision prior to filling with polygonal a pen */ + mp_tracing_commands_internal, /* show commands and operations before they are performed */ + mp_tracing_restores_internal, /* show when a variable or internal is restored */ + mp_tracing_macros_internal, /* show macros before they are expanded */ + mp_tracing_output_internal, /* dummy */ + mp_tracing_stats_internal, /* show memory usage at end of job */ /* now a dummy */ + mp_tracing_online_internal, /* show long diagnostics on terminal and in the log file */ + mp_year_internal, /* the current year (e.g., 1984) */ + mp_month_internal, /* the current month (e.g., 3 $\equiv$ March) */ + mp_day_internal, /* the current day of the month */ + mp_time_internal, /* the number of minutes past midnight when this job started */ + mp_hour_internal, /* the number of hours past midnight when this job started */ + mp_minute_internal, /* the number of minutes in that hour when this job started */ + mp_char_code_internal, /* the number of the next character to be output */ + mp_char_wd_internal, /* the width of the next character to be output */ + mp_char_ht_internal, /* the height of the next character to be output */ + mp_char_dp_internal, /* the depth of the next character to be output */ + mp_char_ic_internal, /* the italic correction of the next character to be output */ + mp_pausing_internal, /* dummy */ + mp_showstopping_internal, /* positive to stop after each |show| command */ + mp_texscriptmode_internal, /* controls spacing in texmode */ + mp_overloadmode_internal, + mp_linejoin_internal, /* as in \ps: 0 for mitered, 1 for round, 2 for beveled */ + mp_linecap_internal, /* as in \ps: 0 for butt, 1 for round, 2 for square */ + mp_stacking_internal, + mp_miterlimit_internal, /* controls miter length as in \ps */ + mp_warning_check_internal, /* controls error message when variable value is large */ + mp_true_corners_internal, /* positive to make |llcorner| etc. ignore |setbounds| */ + mp_default_color_model_internal, /* the default color model for unspecified items */ + mp_restore_clip_color_internal, +} mp_given_internal; + +typedef struct mp_internal { + mp_value v; + char *intname; + int run; + int padding; +} mp_internal; + +@ @<MPlib internal header stuff@>= +typedef enum mp_linecap_codes { + mp_butt_linecap_code, + mp_rounded_linecap_code, + mp_squared_linecap_code, + /* see below */ + mp_weird_linecap_code, +} mp_linecap_codes; + +typedef enum mp_linejoin_codes { + mp_mitered_linejoin_code, + mp_rounded_linejoin_code, + mp_beveled_linejoin_code, + /* we see this value being used */ + mp_weird_linejoin_code, +} mp_linejoin_codes; + +@ @<MPlib internal header stuff@>= +# define internal_value(A) mp->internal[(A)].v.data.n +# define internal_string(A) mp->internal[A].v.data.str +# define set_internal_string(A,B) mp->internal[(A)].v.data.str=(B) +# define internal_name(A) mp->internal[(A)].intname +# define set_internal_name(A,B) mp->internal[(A)].intname=(B) +# define internal_type(A) mp->internal[A].v.type +# define set_internal_type(A,B) mp->internal[(A)].v.type=(B) +# define internal_run(A) mp->internal[(A)].run +# define set_internal_run(A,B) mp->internal[(A)].run=(B) + +@ @d max_given_internal mp_restore_clip_color_internal + +@<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 */ + +@ @<Allocate or initialize ...@>= +//mp->max_internal = 2 * max_given_internal; +mp->max_internal = 1000 + max_given_internal; +mp->internal = mp_memory_allocate((size_t) (mp->max_internal + 1) * sizeof(mp_internal)); + +memset(mp->internal, 0, (size_t) (mp->max_internal + 1) * sizeof(mp_internal)); +for (int i = 1; i <= mp->max_internal; i++) { + new_number(mp->internal[i].v.data.n); +} +for (int i = 1; i <= max_given_internal; i++) { + set_internal_type(i, mp_known_type); +} + +set_internal_type(mp_number_system_internal, mp_string_type); +set_internal_type(mp_job_name_internal, mp_string_type); + +@ @<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", mp_internal_command, mp_tracing_titles_internal); +@:tracingtitles_}{|tracingtitles| primitive@> +mp_primitive(mp, "tracingequations", mp_internal_command, mp_tracing_equations_internal); +@:mp_tracing_equations_}{|tracingequations| primitive@> +mp_primitive(mp, "tracingcapsules", mp_internal_command, mp_tracing_capsules_internal); +@:mp_tracing_capsules_}{|tracingcapsules| primitive@> +mp_primitive(mp, "tracingchoices", mp_internal_command, mp_tracing_choices_internal); +@:mp_tracing_choices_}{|tracingchoices| primitive@> +mp_primitive(mp, "tracingspecs", mp_internal_command, mp_tracing_specs_internal); +@:mp_tracing_specs_}{|tracingspecs| primitive@> +mp_primitive(mp, "tracingcommands", mp_internal_command, mp_tracing_commands_internal); +@:mp_tracing_commands_}{|tracingcommands| primitive@> +mp_primitive(mp, "tracingrestores", mp_internal_command, mp_tracing_restores_internal); +@:mp_tracing_restores_}{|tracingrestores| primitive@> +mp_primitive(mp, "tracingmacros", mp_internal_command, mp_tracing_macros_internal); +@:mp_tracing_macros_}{|tracingmacros| primitive@> +mp_primitive(mp, "tracingoutput", mp_internal_command, mp_tracing_output_internal); +@:mp_tracing_output_}{|tracingoutput| primitive@> +mp_primitive(mp, "tracingstats", mp_internal_command, mp_tracing_stats_internal); +@:mp_tracing_stats_}{|tracingstats| primitive@> +mp_primitive(mp, "tracingonline", mp_internal_command, mp_tracing_online_internal); +@:mp_tracing_online_}{|tracingonline| primitive@> +mp_primitive(mp, "year", mp_internal_command, mp_year_internal); +@:mp_year_}{|year| primitive@> +mp_primitive(mp, "month", mp_internal_command, mp_month_internal); +@:mp_month_}{|month| primitive@> +mp_primitive(mp, "day", mp_internal_command, mp_day_internal); +@:mp_day_}{|day| primitive@> +mp_primitive(mp, "time", mp_internal_command, mp_time_internal); +@:time_}{|time| primitive@> +mp_primitive(mp, "hour", mp_internal_command, mp_hour_internal); +@:hour_}{|hour| primitive@> +mp_primitive(mp, "minute", mp_internal_command, mp_minute_internal); +@:minute_}{|minute| primitive@> +mp_primitive(mp, "charcode", mp_internal_command, mp_char_code_internal); +@:mp_char_code_}{|charcode| primitive@> +mp_primitive(mp, "charwd", mp_internal_command, mp_char_wd_internal); +@:mp_char_wd_}{|charwd| primitive@> +mp_primitive(mp, "charht", mp_internal_command, mp_char_ht_internal); +@:mp_char_ht_}{|charht| primitive@> +mp_primitive(mp, "chardp", mp_internal_command, mp_char_dp_internal); +@:mp_char_dp_}{|chardp| primitive@> +mp_primitive(mp, "charic", mp_internal_command, mp_char_ic_internal); +@:mp_char_ic_}{|charic| primitive@> +mp_primitive(mp, "pausing", mp_internal_command, mp_pausing_internal); +@:mp_pausing_}{|pausing| primitive@> +mp_primitive(mp, "showstopping", mp_internal_command, mp_showstopping_internal); +@:mp_showstopping_}{|showstopping| primitive@> +mp_primitive(mp, "texscriptmode", mp_internal_command, mp_texscriptmode_internal); +@:mp_texscriptmode_}{|texscriptmode| primitive@> +mp_primitive(mp, "overloadmode", mp_internal_command, mp_overloadmode_internal); +@:mp_overloadmode_}{|overloadmode| primitive@> +mp_primitive(mp, "linejoin", mp_internal_command, mp_linejoin_internal); +@:mp_linejoin_}{|linejoin| primitive@> +mp_primitive(mp, "linecap", mp_internal_command, mp_linecap_internal); +@:mp_linecap_}{|linecap| primitive@> +mp_primitive(mp, "stacking", mp_internal_command, mp_stacking_internal); +@:mp_stacking_}{|stacking| primitive@> +mp_primitive(mp, "miterlimit", mp_internal_command, mp_miterlimit_internal); +@:mp_miterlimit_}{|miterlimit| primitive@> +mp_primitive(mp, "warningcheck", mp_internal_command, mp_warning_check_internal); +@:mp_warning_check_}{|warningcheck| primitive@> +mp_primitive(mp, "truecorners", mp_internal_command, mp_true_corners_internal); +@:mp_true_corners_}{|truecorners| primitive@> +mp_primitive(mp, "defaultcolormodel", mp_internal_command, mp_default_color_model_internal); +@:mp_default_color_model_}{|defaultcolormodel| primitive@> +mp_primitive(mp, "restoreclipcolor", mp_internal_command, mp_restore_clip_color_internal); +@:mp_restore_clip_color_}{|restoreclipcolor| primitive@> +mp_primitive(mp, "numbersystem", mp_internal_command, mp_number_system_internal); +@:mp_number_system_}{|numbersystem| primitive@> +mp_primitive(mp, "numberprecision", mp_internal_command, mp_number_precision_internal); +@:mp_number_precision_}{|numberprecision| primitive@> +mp_primitive(mp, "jobname", mp_internal_command, mp_job_name_internal); +@: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 header stuff@>= +typedef enum mp_color_model { + mp_no_model, + mp_grey_model, + mp_rgb_model, + mp_cmyk_model, + mp_uninitialized_model, +} mp_color_model; + +@ @<Initialize table entries@>= +number_clone(internal_value(mp_default_color_model_internal),unity_t); +number_multiply_int(internal_value(mp_default_color_model_internal), mp_rgb_model); +number_clone(internal_value(mp_restore_clip_color_internal), unity_t); +set_internal_string(mp_number_system_internal, mp_intern (mp, "scaled")); +number_clone(internal_value(mp_number_precision_internal), precision_default); +number_clone(internal_value(mp_texscriptmode_internal), unity_t); +number_clone(internal_value(mp_overloadmode_internal), zero_t); + +@ Well, we do have to list the names one more time, for use in symbolic +printouts. + +@<Initialize table...@>= +set_internal_name(mp_tracing_titles_internal, mp_strdup("tracingtitles")); +set_internal_name(mp_tracing_equations_internal, mp_strdup("tracingequations")); +set_internal_name(mp_tracing_capsules_internal, mp_strdup("tracingcapsules")); +set_internal_name(mp_tracing_choices_internal, mp_strdup("tracingchoices")); +set_internal_name(mp_tracing_specs_internal, mp_strdup("tracingspecs")); +set_internal_name(mp_tracing_commands_internal, mp_strdup("tracingcommands")); +set_internal_name(mp_tracing_restores_internal, mp_strdup("tracingrestores")); +set_internal_name(mp_tracing_macros_internal, mp_strdup("tracingmacros")); +set_internal_name(mp_tracing_output_internal, mp_strdup("tracingoutput")); +set_internal_name(mp_tracing_stats_internal, mp_strdup("tracingstats")); +set_internal_name(mp_tracing_online_internal, mp_strdup("tracingonline")); +set_internal_name(mp_year_internal, mp_strdup("year")); +set_internal_name(mp_month_internal, mp_strdup("month")); +set_internal_name(mp_day_internal, mp_strdup("day")); +set_internal_name(mp_time_internal, mp_strdup("time")); +set_internal_name(mp_hour_internal, mp_strdup("hour")); +set_internal_name(mp_minute_internal, mp_strdup("minute")); +set_internal_name(mp_char_code_internal, mp_strdup("charcode")); +set_internal_name(mp_char_wd_internal, mp_strdup("charwd")); +set_internal_name(mp_char_ht_internal, mp_strdup("charht")); +set_internal_name(mp_char_dp_internal, mp_strdup("chardp")); +set_internal_name(mp_char_ic_internal, mp_strdup("charic")); +set_internal_name(mp_pausing_internal, mp_strdup("pausing")); /* dummy */ +set_internal_name(mp_showstopping_internal, mp_strdup("showstopping")); +set_internal_name(mp_texscriptmode_internal, mp_strdup("texscriptmode")); +set_internal_name(mp_overloadmode_internal, mp_strdup("overloadmode")); +set_internal_name(mp_linejoin_internal, mp_strdup("linejoin")); +set_internal_name(mp_linecap_internal, mp_strdup("linecap")); +set_internal_name(mp_stacking_internal, mp_strdup("stacking")); +set_internal_name(mp_miterlimit_internal, mp_strdup("miterlimit")); +set_internal_name(mp_warning_check_internal, mp_strdup("warningcheck")); +set_internal_name(mp_true_corners_internal, mp_strdup("truecorners")); +set_internal_name(mp_default_color_model_internal, mp_strdup("defaultcolormodel")); +set_internal_name(mp_restore_clip_color_internal, mp_strdup("restoreclipcolor")); +set_internal_name(mp_job_name_internal, mp_strdup("jobname")); +set_internal_name(mp_number_system_internal, mp_strdup("numbersystem")); +set_internal_name(mp_number_precision_internal, mp_strdup("numberprecision")); + +@ 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); + number_clone(internal_value(mp_time_internal), unity_t); + number_multiply_int(internal_value(mp_time_internal), (tmptr->tm_hour * 60 + tmptr->tm_min)); + number_clone(internal_value(mp_hour_internal), unity_t); + number_multiply_int(internal_value(mp_hour_internal), (tmptr->tm_hour)); + number_clone(internal_value(mp_minute_internal), unity_t); + number_multiply_int(internal_value(mp_minute_internal), (tmptr->tm_min)); + number_clone(internal_value(mp_day_internal), unity_t); + number_multiply_int(internal_value(mp_day_internal), (tmptr->tm_mday)); + number_clone(internal_value(mp_month_internal), unity_t); + number_multiply_int(internal_value(mp_month_internal), (tmptr->tm_mon + 1)); + number_clone(internal_value(mp_year_internal), unity_t); + number_multiply_int(internal_value(mp_year_internal), (tmptr->tm_year + 1900)); +} + +@ @<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, int blank_line); +static void mp_print_diagnostic (MP mp, const char *s, const char *t, int nuline); + +@ @c +static void mp_begin_diagnostic (MP mp) +{ + /* prepare to do some tracing */ + mp->old_selector = mp->selector; + if (number_nonpositive(internal_value(mp_tracing_online_internal)) && (mp->selector == mp_term_and_log_selector)) { + mp->selector = mp_log_only_selector; + if (mp->history == mp_spotless) { + mp->history = mp_warning_issued; + } + } +} + +void mp_end_diagnostic (MP mp, int blank_line) +{ + /* restore proper conditions after tracing */ + mp_print_nl(mp, ""); + if (blank_line) { + mp_print_ln(mp); + } + mp->selector = mp->old_selector; +} + +@ @<Glob...@>= +unsigned int old_selector; + +@ We will occasionally use |begin_diagnostic| in connection with line-number +printing, as follows. (The parameter |s| is typically |"Path"| or |"Cycle spec"|, +etc.) + +@c +static void mp_print_diagnostic (MP mp, const char *s, const char *t, int nuline) +{ + mp_begin_diagnostic(mp); + if (nuline) { + mp_print_nl(mp, s); + } else { + mp_print_str(mp, s); + } + mp_print_str(mp, " at line "); + mp_print_int(mp, mp_true_line(mp)); + mp_print_str(mp, t); + mp_print_chr(mp, ':'); +} + +@ The 256 |unsigned char| 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. + +@<Enumeration types@>= +typedef enum mp_class_codes { + mp_digit_class = 0, /* the class number of |0123456789| */ + mp_period_class = 1, /* the class number of |.| */ + mp_space_class = 2, /* the class number of spaces and nonstandard characters */ + mp_percent_class = 3, /* the class number of `\.\%' */ + mp_string_class = 4, /* the class number of |"| */ + mp_comma_class = 5, /* the , */ + mp_semicolon_class = 6, /* the ; */ + mp_left_parenthesis_class = 7, /* the class number of |(| */ + mp_right_parenthesis_class = 8, /* the class number of |)| */ + mp_letter_class = 9, /* letters and the underline character */ + mp_suffix_class = 15, + mp_left_bracket_class = 17, /* |[| */ + mp_right_bracket_class = 18, /* |]| */ + mp_brace_class = 19, + mp_invalid_class = 20, /* bad character in the input */ + mp_max_class = 20, /* the largest class number */ +} mp_class_codes; + +@ The class numbers: + +@<Glob...@>= +int char_class[256]; + +@ 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 (int k = '0'; k <= '9'; k++) { + mp->char_class[k] = mp_digit_class; +} +for (int k = 'A'; k <= 'Z'; k++) { + mp->char_class[k] = mp_letter_class; +} +for (int k = 'a'; k <= 'z'; k++) { + mp->char_class[k] = mp_letter_class; +} + +mp->char_class['.'] = mp_period_class; +mp->char_class[' '] = mp_space_class; +mp->char_class['%'] = mp_percent_class; +mp->char_class['"'] = mp_string_class; +mp->char_class[','] = mp_comma_class; +mp->char_class[';'] = mp_semicolon_class; +mp->char_class['('] = mp_left_parenthesis_class; +mp->char_class[')'] = mp_right_parenthesis_class; + +mp->char_class['_'] = mp_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['^'] = 13; +mp->char_class['!'] = 14; +mp->char_class['?'] = 14; +mp->char_class['#'] = mp_suffix_class; +mp->char_class['&'] = mp_suffix_class; +mp->char_class['@@'] = mp_suffix_class; /* will become one after cwebbing */ +mp->char_class['$'] = mp_suffix_class; +mp->char_class['^'] = 16; +mp->char_class['~'] = 16; +mp->char_class['['] = mp_left_bracket_class; +mp->char_class[']'] = mp_right_bracket_class; +mp->char_class['{'] = mp_brace_class; +mp->char_class['}'] = mp_brace_class; + +for (int k = 0; k < ' '; k++) { + mp->char_class[k] = mp_invalid_class; +} + +mp->char_class['\r'] = mp_space_class; +mp->char_class['\n'] = mp_space_class; +mp->char_class['\t'] = mp_space_class; +mp->char_class['\f'] = mp_space_class; + +for (int k = 127; k <= 255; k++) { + mp->char_class[k] = mp->utf8_mode ? mp_letter_class : mp_invalid_class; +} + +if (mp->text_mode) { + mp->char_class[2] = mp_string_class; /* ascii 2 STX*/ + /* mp->char_class[3] = mp_string_class; */ /* ascii 3 ETX */ +} + +@* 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 |mp_string| 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 set_text(A) { + (A)->text = (B) ; +} + +@d set_eq_type(A,B) { + (A)->type = (B) ; +} + +@d set_eq_property(A,B) { + (A)->property = (B) ; +} + +@d set_equiv(A,B) { + (A)->v.data.node = NULL ; + (A)->v.data.indep.serial = (B); +} + +@d set_equiv_node(A,B) { + (A)->v.data.node = (B) ; + (A)->v.data.indep.serial = 0; +} + +@d set_equiv_sym(A,B) { + (A)->v.data.node = (mp_node) (B); + (A)->v.data.indep.serial = 0; +} + +@ @c +# define text(A) (A)->text +# define eq_type(A) (A)->type +# define eq_property(A) (A)->property +# define equiv(A) (A)->v.data.indep.serial +# define equiv_node(A) (A)->v.data.node +# define equiv_sym(A) (mp_sym)(A)->v.data.node + +@ @<Types...@>= +typedef struct mp_symbol_entry { + int type; + int property; /* we had padding room anyway */ + mp_value v; + mp_string text; + void *parent; +} mp_symbol_entry; + +@ @<Glob...@>= +int 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 */ +avl_iterator symbol_iterator; + +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_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 mp_compare_symbols_entry (void *p, const void *pa, const void *pb); +static void *mp_copy_symbols_entry (const void *p); +static void *mp_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 mp_compare_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| +and |mp_number| needs to be deep copied, every thing else can be reassigned. + +@c +static void *mp_copy_symbols_entry (const void *p) +{ + // const mp_symbol_entry *fp = (const mp_symbol_entry *) p; + mp_symbol_entry *fp = (mp_symbol_entry *) p; + MP mp = (MP)fp->parent; + mp_sym ff = mp_memory_allocate(sizeof(mp_symbol_entry)); + if (ff == NULL) { + return NULL; + } + ff->text = mp_aux_copy_strings_entry(fp->text); + if (ff->text == NULL) { + return NULL; + } + ff->v = fp->v; + ff->type = fp->type; + ff->property = fp->property; + ff->parent = mp; + new_number_clone(ff->v.data.n, fp->v.data.n); + return ff; +} + +@ In the current implementation, symbols are not freed until the end of the run. + +@c +static void *mp_delete_symbols_entry (void *p) +{ + mp_sym ff = (mp_sym) p; + MP mp = (MP) ff->parent; + free_number(ff->v.data.n); + mp_memory_free(ff->text->str); + mp_memory_free(ff->text); + mp_memory_free(ff); + return NULL; +} + +@ @<Allocate or initialize ...@>= +mp->symbols = avl_create(mp_compare_symbols_entry, mp_copy_symbols_entry, mp_delete_symbols_entry, mp_memory_allocate, mp_memory_free, NULL); +mp->frozen_symbols = avl_create(mp_compare_symbols_entry, mp_copy_symbols_entry, mp_delete_symbols_entry, mp_memory_allocate, mp_memory_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 = mp_memory_clear_allocate(sizeof(mp_symbol_entry)); + ff->parent = mp; + ff->text = mp_memory_allocate(sizeof(mp_lstring)); + ff->text->str = nam; + ff->text->len = len; + ff->type = mp_tag_command; + ff->v.type = mp_known_type; + new_number(ff->v.data.n); + return ff; +} + +@ There is one global variable so that |id_lookup| does not always have to create +a new entry just for testing. This is not freed because it creates a double-free +thanks to the |NULL| init. + +@<Global ...@>= +mp_sym id_lookup_test; + +@ @<Initialize table entries@>= +mp->id_lookup_test = new_symbols_entry(mp, NULL, 0); + +@ Certain symbols are \quote {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", mp_tag_command, 0); +mp->frozen_right_delimiter = mp_frozen_primitive(mp, ")", mp_right_delimiter_command, 0); +mp->frozen_inaccessible = mp_frozen_primitive(mp, " INACCESSIBLE", mp_tag_command, 0); +mp->frozen_undefined = mp_frozen_primitive(mp, " UNDEFINED", mp_tag_command, 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. + +Note: simple symbols like |+|, |-|, |*| and |/| are also looked up. One can argue +that a user can redefine them but colons etc. are interpreted direct. Maybe +there's room for some optimization here. We could just put references (to +|mp_sym|) in the |mp| instance object for the handful. Okay, we also have |:=| so +maybe only for single character ones ... not worth the trouble. + +@d mp_id_lookup(A,B,C,D) mp_do_id_lookup((A), mp->symbols, (B), (C), (D)) + +@c +static mp_sym mp_do_id_lookup (MP mp, avl_tree symbols, char *j, size_t l, int insert_new) +{ + mp_sym str; + mp->id_lookup_test->text->str = (unsigned char *) j; + mp->id_lookup_test->text->len = l; + str = (mp_sym) avl_find(mp->id_lookup_test, symbols); + if (str == NULL && insert_new) { + unsigned char *nam = (unsigned char *) mp_strndup(j, l); + mp_sym s = new_symbols_entry(mp, nam, l); + mp->st_count++; + avl_ins(s, symbols, avl_false); + str = (mp_sym) avl_find(s, symbols); + mp_delete_symbols_entry(s); + } + return str; +} + +@ @<Exported function headers@>= +extern int mp_initialize_symbol_traverse (MP mp); +extern void mp_kill_symbol_traverse (MP mp); +extern void *mp_fetch_symbol_traverse (MP mp); +extern void *mp_fetch_symbol (MP mp, char *s); + +@ @c +int mp_initialize_symbol_traverse (MP mp) +{ + mp->symbol_iterator = avl_iterator_new(mp->symbols, AVL_ITERATOR_INI_PRE); + return (mp->symbol_iterator != NULL); +} + +void mp_kill_symbol_traverse (MP mp) +{ + avl_iterator_kill(mp->symbol_iterator); +} + +void *mp_fetch_symbol_traverse (MP mp) +{ + return avl_iterator_next(mp->symbol_iterator); +} + +void *mp_fetch_symbol (MP mp, char *s) +{ + return mp_id_lookup(mp, s, strlen(s), 0); +} + +@ We need to put \MP's \quote {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, int c, int o) +{ +// char *s = mp_strdup(ss); +// set_cur_sym(mp_id_lookup(mp, s, strlen(s), 1)); +// mp_memory_free(s); + set_cur_sym(mp_id_lookup(mp, (char *) ss, strlen(ss), 1)); + set_eq_type(cur_sym, c); + set_eq_property(cur_sym, 0x1); /* todo: enumeration values */ + set_equiv(cur_sym, o); +} + +@ Some other symbolic tokens only exist for error recovery. + +@c +static mp_sym mp_frozen_primitive (MP mp, const char *ss, int c, int o) +{ +// char *s = mp_strdup(ss); +// mp_sym str = mp_do_id_lookup(mp, mp->frozen_symbols, s, strlen(s), 1); +// mp_memory_free(s); + mp_sym str = mp_do_id_lookup(mp, mp->frozen_symbols, (char *) ss, strlen(ss), 1); + str->type = c; + str->property = 0x1; /* todo: enumeration values */ + str->v.data.indep.serial = 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 int mp_is_frozen (MP mp, mp_sym sym) +{ + mp_sym temp = mp_do_id_lookup(mp, mp->frozen_symbols, (char *) sym->text->str, sym->text->len, 0); + if (temp == mp->frozen_inaccessible) { + return 0; + } else { + 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, "..", mp_path_join_command, 0); +@:.._}{|..| primitive@> +mp_primitive(mp, "[", mp_left_bracket_command, 0); +mp->frozen_left_bracket = mp_frozen_primitive (mp, "[", mp_left_bracket_command, 0); +@:[ }{|[| primitive@> +mp_primitive(mp, "]", mp_right_bracket_command, 0); +@:] }{|]| primitive@> +mp_primitive(mp, "}", mp_right_brace_command, 0); +@:]]}{|\char`\|} primitive@> +mp_primitive(mp, "{", mp_left_brace_command, 0); +@:][}{|\char`\{| primitive@> +mp_primitive(mp, ":", mp_colon_command, 0); +mp->frozen_colon = mp_frozen_primitive (mp, ":", mp_colon_command, 0); +@:: }{|:| primitive@> +mp_primitive(mp, ":=", mp_assignment_command, 0); +@::=_}{|:=| primitive@> +mp_primitive(mp, ",", mp_comma_command, 0); +@:, }{\., primitive@> +mp_primitive(mp, ";", mp_semicolon_command, 0); +mp->frozen_semicolon = mp_frozen_primitive (mp, ";", mp_semicolon_command, 0); +@:; }{\.; primitive@> +mp_primitive(mp, "\\", mp_relax_command, 0); +@:]]\||\char`\\| primitive@> +mp_primitive(mp, "addto", mp_add_to_command, 0); +@:add_to_|{|addto| primitive@> +mp_primitive(mp, "atleast", mp_at_least_command, 0); +@:at_least_}{|atleast| primitive@> +mp_primitive(mp, "begingroup", mp_begin_group_command, 0); +mp->bg_loc = cur_sym; +@:begin_group_}{|begingroup| primitive@> +mp_primitive(mp, "controls", mp_controls_command, 0); +@:controls_}{|controls| primitive@> +mp_primitive(mp, "curl", mp_curl_command, 0); +@:curl_}{|curl| primitive@> +mp_primitive(mp, "delimiters", mp_delimiters_command, 0); +@:delimiters_}{|delimiters| primitive@> +mp_primitive(mp, "endgroup", mp_end_group_command, 0); + +mp->eg_loc = cur_sym; +mp->frozen_end_group = mp_frozen_primitive (mp, "endgroup", mp_end_group_command, 0); + +@:endgroup_}{|endgroup| primitive@> +mp_primitive(mp, "everyjob", mp_every_job_command, 0); +@:every_job_}{|everyjob| primitive@> +mp_primitive(mp, "exitif", mp_exit_test_command, 0); +@:exit_if_}{|exitif| primitive@> +mp_primitive(mp, "expandafter", mp_expand_after_command, 0); +@:expand_after_}{|expandafter| primitive@> +mp_primitive(mp, "interim", mp_interim_command, 0); +@:interim_}{|interim| primitive@> +mp_primitive(mp, "let", mp_let_command, 0); +@:let_}{|let| primitive@> +mp_primitive(mp, "newinternal", mp_new_internal_command, 0); +@:new_internal_}{|newinternal| primitive@> +mp_primitive(mp, "of", mp_of_command, 0); +@:of_}{|of| primitive@> +mp_primitive(mp, "randomseed", mp_only_set_command, mp_random_seed_code); +@:mp_random_seed_}{|randomseed| primitive@> +mp_primitive(mp, "maxknotpool", mp_only_set_command, mp_max_knot_pool_code); +@:mp_max_knot_pool_}{|maxknotpool| primitive@> +mp_primitive(mp, "save", mp_save_command, 0); +@:save_}{|save| primitive@> +mp_primitive(mp, "scantokens", mp_scan_tokens_command, 0); +@:scan_tokens_}{|scantokens| primitive@> +mp_primitive(mp, "runscript", mp_runscript_command, 0); +@:run_script_}{|runscript| primitive@> +mp_primitive(mp, "maketext", mp_maketext_command, 0); +@:make_text_}{|maketext| primitive@> +mp_primitive(mp, "shipout", mp_ship_out_command, 0); +@:ship_out_}{|shipout| primitive@> +mp_primitive(mp, "step", mp_step_command, 0); +@:step_}{|step| primitive@> +mp_primitive(mp, "str", mp_str_command, 0); +@:str_}{|str| primitive@> +mp_primitive(mp, "void", mp_void_command, 0); +@:void_}{|void| primitive@> +mp_primitive(mp, "tension", mp_tension_command, 0); +@:tension_}{|tension| primitive@> +mp_primitive(mp, "to", mp_to_command, 0); +@:to_}{|to| primitive@> +mp_primitive(mp, "until", mp_until_command, 0); +@:until_}{|until| primitive@> +mp_primitive(mp, "within", mp_within_command, 0); +@:within_}{|within| primitive@> +mp_primitive(mp, "write", mp_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 mp_add_to_command: return "addto"; +case mp_assignment_command: return ":="; +case mp_at_least_command: return "atleast"; +case mp_begin_group_command: return "begingroup"; +case mp_colon_command: return ":"; +case mp_comma_command: return ","; +case mp_controls_command: return "controls"; +case mp_curl_command: return "curl"; +case mp_delimiters_command: return "delimiters"; +case mp_end_group_command: return "endgroup"; +case mp_every_job_command: return "everyjob"; +case mp_exit_test_command: return "exitif"; +case mp_expand_after_command: return "expandafter"; +case mp_interim_command: return "interim"; +case mp_left_brace_command: return "{"; +case mp_left_bracket_command: return "["; +case mp_let_command: return "let"; +case mp_new_internal_command: return "newinternal"; +case mp_of_command: return "of"; +case mp_path_join_command: return ".."; +case mp_relax_command: return "\\"; +case mp_right_brace_command: return "}"; +case mp_right_bracket_command: return "]"; +case mp_save_command: return "save"; +case mp_scan_tokens_command: return "scantokens"; +case mp_runscript_command: return "runscript"; +case mp_maketext_command: return "maketext"; +case mp_semicolon_command: return ";"; +case mp_ship_out_command: return "shipout"; +case mp_step_command: return "step"; +case mp_str_command: return "str"; +case mp_void_command: return "void"; +case mp_tension_command: return "tension"; +case mp_to_command: return "to"; +case mp_until_command: return "until"; +case mp_within_command: return "within"; +case mp_write_command: return "write"; + +@ We will deal with the other primitives later, at some point in the program +where their |eq_type| and |equiv| values are more meaningful. For example, the +primitives for macro definitions will be loaded when we consider the routines +that define macros. It is easy to find where each particular primitive was +treated by looking in the index at the end; for example, the section where +|"def"| entered |eqtb| is listed under `|def| primitive'. + +@* 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 |mp_string|. + +(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 \quote {type} in a +printer's sense. It's curious that the same word is used in such different ways. + +@d mp_set_value_sym(A,B) do_set_value_sym (mp, (mp_token_node) (A), (B)) +@d mp_set_value_number(A,B) do_set_value_number(mp, (mp_token_node) (A), &(B)) +@d mp_set_value_node(A,B) do_set_value_node (mp, (mp_token_node) (A), (B)) +@d mp_set_value_str(A,B) do_set_value_str (mp, (mp_token_node) (A), (B)) +@d mp_set_value_knot(A,B) do_set_value_knot (mp, (mp_token_node) (A), (B)) + +@<MPlib internal header stuff@>= +typedef struct mp_node_data *mp_token_node; + +@ @c +# define mp_get_value_sym(A) ((mp_token_node) (A))->data.sym +# define mp_get_value_number(A) ((mp_token_node) (A))->data.n +# define mp_get_value_node(A) ((mp_token_node) (A))->data.node +# define mp_get_value_str(A) ((mp_token_node) (A))->data.str +# define mp_get_value_knot(A) ((mp_token_node) (A))->data.p + +inline static void do_set_value_sym (MP mp, mp_token_node A, mp_sym B) +{ + (void) mp; + A->data.sym=(B); +} + +inline static void do_set_value_number (MP mp, mp_token_node A, mp_number *B) +{ + (void) mp; + A->data.p = NULL; + A->data.str = NULL; + A->data.node = NULL; + number_clone(A->data.n, *B); +} + +inline static void do_set_value_str (MP mp, mp_token_node A, mp_string B) +{ + (void) mp; + A->data.p = NULL; + A->data.str = (B); + add_str_ref((B)); + A->data.node = NULL; + number_clone(A->data.n, zero_t); +} + +inline static void do_set_value_node (MP mp, mp_token_node A, mp_node B) +{ + (void) mp; + /* store the value in a large token node */ + A->data.p = NULL; + A->data.str = NULL; + A->data.node = B; + number_clone(A->data.n, zero_t); +} + +inline static void do_set_value_knot (MP mp, mp_token_node A, mp_knot B) +{ + (void) mp; + A->data.p = (B); + A->data.str = NULL; + A->data.node = NULL; + number_clone(A->data.n, zero_t); +} + +@ @<Declarations@>= +inline static void do_set_value_sym (MP mp, mp_token_node A, mp_sym B); +inline static void do_set_value_number (MP mp, mp_token_node A, mp_number *B); +inline static void do_set_value_str (MP mp, mp_token_node A, mp_string B); +inline static void do_set_value_node (MP mp, mp_token_node A, mp_node B); +inline static void do_set_value_knot (MP mp, mp_token_node A, mp_knot B); + +@ @c +static mp_node mp_new_token_node (MP mp) +{ + mp_node p; + if (mp->token_nodes) { + p = mp->token_nodes; + mp->token_nodes = p->link; + mp->num_token_nodes--; + p->link = NULL; + } else { + p = mp_allocate_node(mp, sizeof(mp_node_data)); + new_number(p->data.n); + p->hasnumber = 1; + } + p->type = mp_token_node_type; + return (mp_node) p; +} + +@ @c +static void mp_free_token_node (MP mp, mp_node p) +{ + if (p) { + if (mp->num_token_nodes < max_num_token_nodes) { + p->link = mp->token_nodes; + mp->token_nodes = p; + mp->num_token_nodes++; + } else { + mp->var_used -= sizeof(mp_node_data); + if (mp->math_mode > mp_math_double_mode) { + free_number(((mp_value_node) p)->data.n); + } + mp_memory_free(p); + } + } +} + +@ @<Declarations@>= +static void mp_free_token_node (MP mp, mp_node p); + +@ A numeric token is created by the following trivial routine. + +@c +static mp_node mp_new_num_tok (MP mp, mp_number *v) +{ + mp_node p = mp_new_token_node(mp); + mp_set_value_number(p, *v); + p->type = mp_known_type; + p->name_type = mp_token_operation; + return p; +} + +@ @<Declarations@>= +static void mp_flush_token_list (MP mp, mp_node 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) +{ + while (p != NULL) { + mp_node q = p; /* the node being recycled */ + p = mp_link(p); + switch (mp_type(q)) { + case mp_symbol_node_type: + mp_free_symbolic_node(mp, q); + continue; + case mp_vacuous_type: + case mp_boolean_type: + case mp_known_type: + break; + case mp_string_type: + delete_str_ref(mp_get_value_str(q)); + break; + case mp_unknown_boolean_type: + case mp_unknown_string_type: + case mp_unknown_pen_type: + case mp_unknown_nep_type: + case mp_unknown_path_type: + case mp_unknown_picture_type: + case mp_pen_type: + case mp_nep_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_type: + case mp_proto_dependent_type: + case mp_independent_type: + mp_recycle_value(mp, q); + break; + default: + mp_confusion(mp, "token"); + @:this can't happen token}{\quad token@> + } + mp_free_token_node(mp, q); + } +} + +@ 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.) + +@^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); +static void mp_show_token_list_space (MP mp, mp_node p, mp_node q); + +@ We go for a spacy layout because we have more screen estate today. +@c + +void mp_show_token_list (MP mp, mp_node p, mp_node q) +{ + int cclass = mp_percent_class; + (void) q; + while (p != NULL) { + int c = mp_letter_class; + if (mp_type(p) != mp_symbol_node_type) { + if (mp_name_type(p) == mp_token_operation) { + if (mp_type(p) == mp_known_type) { + if (cclass == mp_digit_class) { + mp_print_chr(mp, ' '); + } + if (number_negative(mp_get_value_number(p))) { + if (cclass == mp_left_bracket_class) { + mp_print_chr(mp, ' '); + } + mp_print_chr(mp, '['); + print_number(mp_get_value_number(p)); + mp_print_chr(mp, ']'); + c = mp_right_bracket_class; + } else { + print_number(mp_get_value_number(p)); + c = mp_digit_class; + } + } else if (mp_type(p) == mp_string_type) { + mp_print_chr(mp, '"'); + mp_print_mp_str(mp, mp_get_value_str(p)); + mp_print_chr(mp, '"'); + c = mp_string_class; + } else { + mp_print_str(mp, " BAD"); + } + } else if ((mp_name_type(p) != mp_capsule_operation) || (mp_type(p) < mp_vacuous_type) || (mp_type(p) > mp_independent_type)) { + mp_print_str(mp, " BAD"); + } else { + mp_print_capsule(mp, p); + c = mp_right_parenthesis_class; + } + } else if (mp_name_type(p) == mp_expr_operation || mp_name_type(p) == mp_suffix_operation || mp_name_type(p) == mp_text_operation) { + int r = mp_get_sym_info(p); + if (mp_name_type(p) == mp_expr_operation) { + mp_print_str(mp, "(EXPR"); + } else if (mp_name_type(p) == mp_suffix_operation) { + mp_print_str(mp, "(SUFFIX"); + } else { + mp_print_str(mp, "(TEXT"); + } + mp_print_int(mp, r); + mp_print_chr(mp, ')'); + c = mp_right_parenthesis_class; + } else { + mp_sym sr = mp_get_sym_sym(p); + if (sr == mp_collective_subscript) { + if (cclass == mp_left_bracket_class) { + mp_print_chr(mp, ' '); + } + mp_print_str(mp, "[]"); + c = mp_right_bracket_class; + } else { + mp_string rr = text(sr); + if (rr == NULL || rr->str == NULL) { + mp_print_str(mp, " NONEXISTENT"); + } else { + c = mp->char_class[(rr->str[0])]; + if (c == cclass) { + switch (c) { + case mp_letter_class: + mp_print_chr(mp, '.'); + break; + case mp_comma_class: + case mp_semicolon_class: + case mp_left_parenthesis_class: + case mp_right_parenthesis_class: + break; + default: + mp_print_chr(mp, ' '); + break; + } + } + mp_print_mp_str(mp, rr); + } + } + } + cclass = c; + p = mp_link(p); + } + return; +} + +void mp_show_token_list_space (MP mp, mp_node p, mp_node q) +{ + (void) q; + while (p != NULL) { + if (mp_type(p) != mp_symbol_node_type) { + if (mp_name_type(p) == mp_token_operation) { + if (mp_type(p) == mp_known_type) { + if (number_negative(mp_get_value_number(p))) { + mp_print_str(mp, "[ "); + print_number(mp_get_value_number(p)); + mp_print_str(mp, " ]"); + } else { + print_number(mp_get_value_number(p)); + } + } else if (mp_type(p) == mp_string_type) { + mp_print_chr(mp, '"'); + mp_print_mp_str(mp, mp_get_value_str(p)); + mp_print_chr(mp, '"'); + } else { + mp_print_str(mp, "BAD"); + } + } else if ((mp_name_type(p) != mp_capsule_operation) || (mp_type(p) < mp_vacuous_type) || (mp_type(p) > mp_independent_type)) { + mp_print_str(mp, "BAD"); + } else { + mp_print_capsule(mp, p); + } + } else if (mp_name_type(p) == mp_expr_operation || mp_name_type(p) == mp_suffix_operation || mp_name_type(p) == mp_text_operation) { + int r = mp_get_sym_info(p); + if (mp_name_type(p) == mp_expr_operation) { + mp_print_str(mp, "(EXPR "); + } else if (mp_name_type(p) == mp_suffix_operation) { + mp_print_str(mp, "(SUFFIX "); + } else { + mp_print_str(mp, "(TEXT "); + } + mp_print_int(mp, r); + mp_print_chr(mp, ')'); + } else { + mp_sym sr = mp_get_sym_sym(p); + if (sr == mp_collective_subscript) { + mp_print_str(mp, "[]"); + } else { + mp_string rr = text(sr); + if (rr == NULL || rr->str == NULL) { + mp_print_str(mp, "NONEXISTENT"); + } else { + mp_print_mp_str(mp, rr); + } + } + } + p = mp_link(p); + if (p) { + mp_print_chr(mp, ' '); + } + } + return; +} + +@ @<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_chr(mp, '('); + mp_print_exp(mp, p, 0); + mp_print_chr(mp, ')'); +} + +@ 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. + +/* reference count preceding a macro definition or picture header */ + +@d mp_get_ref_count(A) mp_get_indep_value(A) +@d mp_set_ref_count(A,B) mp_set_indep_value(A,B) +@d mp_add_mac_ref(A) mp_set_ref_count((A), mp_get_ref_count((A))+1) /* make a new reference to a macro list */ +@d mp_decr_mac_ref(A) mp_set_ref_count((A), mp_get_ref_count((A))-1) /* remove a reference to a macro list */ + +@<Types...@>= +typedef enum mp_macro_info { + mp_general_macro, /* preface to a macro defined with a parameter list */ + mp_primary_macro, /* preface to a macro with a |primary| parameter */ + mp_secondary_macro, /* preface to a macro with a |secondary| parameter */ + mp_tertiary_macro, /* preface to a macro with a |tertiary| parameter */ + mp_expr_macro, /* preface to a macro with an undelimited |expr| parameter */ + mp_of_macro, /* preface to a macro with undelimited `|expr| |x| |of|~|y|' parameters */ + mp_suffix_macro, /* preface to a macro with an undelimited |suffix| parameter */ + mp_text_macro, /* preface to a macro with an undelimited |text| parameter */ + mp_expr_parameter, /* used by |expr| primitive */ + mp_suffix_parameter, /* used by |suffix| primitive */ + mp_text_parameter /* used by |text| primitive */ +} mp_macro_info; + +@ @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 (mp_get_ref_count(p) == 0) { + mp_flush_token_list(mp, p); + } else { + mp_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) +{ + p = mp_link(p); /* bypass the reference count */ + while (mp_name_type(p) != mp_macro_operation) { + mp_node r = mp_link(p); + mp_link(p) = NULL; + mp_show_token_list(mp, p, NULL); + mp_link(p) = r; + p = r; + } + switch (mp_get_sym_info(p)) { + case mp_general_macro: + mp_print_str(mp, "-> "); + break; + @.->@> + case mp_primary_macro: + case mp_secondary_macro: + case mp_tertiary_macro: + mp_print_str(mp, "<"); + mp_print_cmd_mod(mp, mp_parameter_commmand, mp_get_sym_info(p)); + mp_print_str(mp, "> -> "); + break; + case mp_expr_macro: + mp_print_str(mp, "<expr> -> "); + break; + case mp_of_macro: + mp_print_str(mp, "<expr> of <primary> -> "); + break; + case mp_suffix_macro: + mp_print_str(mp, "<suffix> -> "); + break; + case mp_text_macro: + mp_print_str(mp, "<text> -> "); + break; + } + mp_show_token_list(mp, mp_link(p), q); +} + +@* Data structures for variables. + +The variables of \MP\ programs can be simple, like |x|, or they can combine the +structural property 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 potential 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 mp_get_attribute_head(A) mp_do_get_attribute_head(mp, (mp_value_node) (A)) +@d mp_set_attribute_head(A,B) mp_do_set_attribute_head(mp, (mp_value_node) (A),(mp_node) (B)) + +@d mp_get_subscr_head(A) mp_do_get_subscr_head(mp,(mp_value_node) (A)) +@d mp_set_subscr_head(A,B) mp_do_set_subscr_head(mp,(mp_value_node) (A),(mp_node) (B)) + +@<MPlib internal header stuff@>= +typedef struct mp_value_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int padding; + struct mp_node_data *link; + /*specific */ + mp_value_data data; + mp_number subscript; + mp_sym hashloc_; + mp_node parent; + mp_node attr_head; + mp_node subscr_head; +} mp_value_node_data; + +@ @c +static mp_node mp_do_get_attribute_head (MP mp, mp_value_node A) +{ + (void) mp; + return A->attr_head; +} + +static mp_node mp_do_get_subscr_head (MP mp, mp_value_node A) +{ + return A->subscr_head; + (void) mp; +} + +static void mp_do_set_attribute_head (MP mp, mp_value_node A, mp_node d) +{ + (void) mp; + A->attr_head = d; +} + +static void mp_do_set_subscr_head (MP mp, mp_value_node A, mp_node d) +{ + (void) mp; + A->subscr_head = d; +} + +@ @<Declarations@>= +static mp_node mp_do_get_subscr_head (MP mp, mp_value_node A); +static mp_node mp_do_get_attribute_head (MP mp, mp_value_node A); +static void mp_do_set_attribute_head (MP mp, mp_value_node A, mp_node d); +static void mp_do_set_subscr_head (MP mp, mp_value_node A, mp_node d); + +@ It would have been nicer to make |mp_new_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. + +@c +static mp_node mp_new_value_node (MP mp) +{ + mp_value_node p; + if (mp->value_nodes) { + p = (mp_value_node) mp->value_nodes; + mp->value_nodes = p->link; + mp->num_value_nodes--; + p->link = NULL; + } else { + p = mp_allocate_node(mp, sizeof(mp_value_node_data)); + new_number(p->data.n); + new_number(p->subscript); + p->hasnumber = 2; + } + mp_type(p) = mp_value_node_type; + return (mp_node) p; +} + +@ @<Declarations@>= +static mp_node mp_new_value_node (MP mp); + +@ 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 |mp_get_hashloc +(mp_link(p)) > mp_get_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_operation|; 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|, |mp_get_attribute_head(p) = +q|, and |mp_get_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_operation| and |mp_link(q) = q1|, where +|q1| points to an attribute node representing |x[]|. Thus |mp_name_type(q1) = +attr|, |mp_get_hashloc(q1) = mp_collective_subscript = 0|, |mp_get_parent(q1) = +p|, |type(q1) = mp_structured|, |mp_get_attribute_head(q1) = qq|, and +|mp_get_subscr_head(q1) = qq1|; |qq| is a three-word \quote {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|, |mp_get_hashloc(qq)=0|, |mp_get_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|, |mp_get_hashloc(qq1)=mp_collective_subscript|, +|mp_get_parent(qq1) = q1|, and |mp_link(qq1) = qq2|. Since |qq2| represents +|x[]b|, |type(qq2) = mp_unknown_boolean|; also |mp_get_hashloc(qq2) = h(b)|, +|mp_get_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|, |mp_get_attribute_head(r1) = qqq|, +|mp_get_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. + +@c +# define mp_get_hashloc(A) ((mp_value_node)(A))->hashloc_ +# define mp_set_hashloc(A,B) ((mp_value_node)(A))->hashloc_ = B +# define mp_get_parent(A) ((mp_value_node)(A))->parent +# define mp_set_parent(A,B) ((mp_value_node)(A))->parent = B + +@ @c +static mp_value_node mp_get_attribute_node (MP mp) +{ + mp_value_node p = (mp_value_node) mp_new_value_node(mp); + mp_type(p) = mp_attribute_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_attribute_node(mp); +mp_set_hashloc(mp->end_attr, (mp_sym)-1); +mp_set_parent((mp_value_node) mp->end_attr, NULL); + +@ @<Free table...@>= +mp_free_value_node(mp, mp->end_attr); + +@d mp_collective_subscript (void *)0 /* code for the attribute |[]| */ +@d mp_subscript(A) ((mp_value_node)(A))->subscript + +@ @c +static mp_value_node mp_get_subscr_node (MP mp) +{ + mp_value_node p = (mp_value_node) mp_new_value_node(mp); + mp_type(p) = mp_subscript_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_operation| and the second has |name_type = mp_y_part_operation|; the +|link| in the first points back to the node whose |value| points to this +four-word node. + +@d mp_x_part(A) ((mp_pair_node) (A))->x_part /* where the |xpart| is found in a pair node */ +@d mp_y_part(A) ((mp_pair_node) (A))->y_part /* where the |ypart| is found in a pair node */ + +@<MPlib internal header stuff@>= +typedef struct mp_pair_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int padding; + struct mp_node_data *link; + /*specific */ + mp_node x_part; + mp_node y_part; +} mp_pair_node_data; + +typedef struct mp_pair_node_data *mp_pair_node; + +@ @c +static mp_node mp_get_pair_node (MP mp) +{ + mp_node p; + if (mp->pair_nodes) { + p = mp->pair_nodes; + mp->pair_nodes = p->link; + mp->num_pair_nodes--; + p->link = NULL; + } else { + p = mp_allocate_node(mp, sizeof(mp_pair_node_data)); + } + mp_type(p) = mp_pair_node_type; + return (mp_node) p; +} + +@ @c +static void mp_free_pair_node (MP mp, mp_node p) +{ + if (p) { + if (mp->num_pair_nodes < max_num_pair_nodes) { + p->link = mp->pair_nodes; + mp->pair_nodes = p; + mp->num_pair_nodes++; + } else { + mp->var_used -= sizeof(mp_pair_node_data); + mp_memory_free(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); + mp_y_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_y_part(q)); /* sets |type(q)| and |value(q)| */ + mp_name_type(mp_y_part(q)) = mp_y_part_operation; + mp_link(mp_y_part(q)) = p; + mp_x_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_x_part(q)); /* sets |type(q)| and |value(q)| */ + mp_name_type(mp_x_part(q)) = mp_x_part_operation; + mp_link(mp_x_part(q)) = p; + mp_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_operation|, +|y_part_operation|, |mp_xx_part_operation|, |mp_xy_part_operation|, +|mp_yx_part_operation|, and |mp_yy_part_operation|. + +@d mp_tx_part(A) ((mp_transform_node) (A))->tx_part /* where the |xpart| is found in a transform node */ +@d mp_ty_part(A) ((mp_transform_node) (A))->ty_part /* where the |ypart| is found in a transform node */ +@d mp_xx_part(A) ((mp_transform_node) (A))->xx_part /* where the |xxpart| is found in a transform node */ +@d mp_xy_part(A) ((mp_transform_node) (A))->xy_part /* where the |xypart| is found in a transform node */ +@d mp_yx_part(A) ((mp_transform_node) (A))->yx_part /* where the |yxpart| is found in a transform node */ +@d mp_yy_part(A) ((mp_transform_node) (A))->yy_part /* where the |yypart| is found in a transform node */ + +@<MPlib internal header stuff@>= +typedef struct mp_transform_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int padding; + struct mp_node_data *link; + /*specific */ + 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; + +@ @c +static mp_node mp_get_transform_node (MP mp) +{ + mp_transform_node p = (mp_transform_node) mp_allocate_node(mp, sizeof(mp_transform_node_data)); + 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 */ + mp_yy_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_yy_part(q)); /* sets |type(q)| and |value(q)| */ + mp_name_type(mp_yy_part(q)) = mp_yy_part_operation; + mp_link(mp_yy_part(q)) = p; + mp_yx_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_yx_part(q)); /* sets |type(q)| and |value(q)| */ + mp_name_type(mp_yx_part(q)) = mp_yx_part_operation; + mp_link(mp_yx_part(q)) = p; + mp_xy_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_xy_part(q)); /* sets |type(q)| and |value(q)| */ + mp_name_type(mp_xy_part(q)) = mp_xy_part_operation; + mp_link(mp_xy_part(q)) = p; + mp_xx_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_xx_part(q)); /* sets |type(q)| and |value(q)| */ + mp_name_type(mp_xx_part(q)) = mp_xx_part_operation; + mp_link(mp_xx_part(q)) = p; + mp_ty_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_ty_part(q)); /* sets |type(q)| and |value(q)| */ + mp_name_type(mp_ty_part(q)) = mp_y_part_operation; + mp_link(mp_ty_part(q)) = p; + mp_tx_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_tx_part(q)); /* sets |type(q)| and |value(q)| */ + mp_name_type(mp_tx_part(q)) = mp_x_part_operation; + mp_link(mp_tx_part(q)) = p; + mp_set_value_node(p, q); +} + +@ Variables of type |color| have 3~values in 6~words identified by +|mp_red_part_operation|, |mp_green_part_operation|, and |mp_blue_part_operation|. + +@d mp_red_part(A) ((mp_color_node) (A))->red_part /* where the |redpart| is found in a color node */ +@d mp_green_part(A) ((mp_color_node) (A))->green_part /* where the |greenpart| is found in a color node */ +@d mp_blue_part(A) ((mp_color_node) (A))->blue_part /* where the |bluepart| is found in a color node */ +@d mp_grey_part(A) ((mp_color_node) (A))->grey_part /* where the |greypart| is found in a color node */ +@d mp_cyan_part(A) ((mp_color_node) (A))->cyan_part /* where the |cyanpart| is found in a color node */ +@d mp_magenta_part(A) ((mp_color_node) (A))->magenta_part /* where the |magentapart| is found in a color node */ +@d mp_yellow_part(A) ((mp_color_node) (A))->yellow_part /* where the |yellowpart| is found in a color node */ +@d mp_black_part(A) ((mp_color_node) (A))->black_part /* where the |blackpart| is found in a color node */ + +@<MPlib internal header stuff@>= +typedef struct mp_color_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int padding; + struct mp_node_data *link; + /*specific */ + union { + mp_node red_part; + mp_node cyan_part; + }; + union { + mp_node green_part; + mp_node magenta_part; + }; + union { + mp_node blue_part; + mp_node yellow_part; + }; + union { + mp_node grey_part; + mp_node black_part; + }; +} mp_color_node_data; + +typedef struct mp_color_node_data *mp_color_node; + +@ @c +static void mp_init_color_node (MP mp, mp_node p, int type) +{ + mp_node q = (mp_node) mp_allocate_node(mp, sizeof(mp_color_node_data)); + q->link = NULL; + mp_type(p) = type; + switch (type) { + case mp_color_type: + mp_type(q) = mp_color_node_type; + /* */ + mp_red_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_red_part(q)); + mp_name_type(mp_red_part(q)) = mp_red_part_operation; + mp_link(mp_red_part(q)) = p; + /* */ + mp_green_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_green_part(q)); + mp_name_type(mp_green_part(q)) = mp_green_part_operation; + mp_link(mp_green_part(q)) = p; + /* */ + mp_blue_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_blue_part(q)); + mp_name_type(mp_blue_part(q)) = mp_blue_part_operation; + mp_link(mp_blue_part(q)) = p; + break; + case mp_cmykcolor_type: + mp_type(q) = mp_cmykcolor_node_type; + /* */ + mp_cyan_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_cyan_part(q)); + mp_name_type(mp_cyan_part(q)) = mp_cyan_part_operation; + mp_link(mp_cyan_part(q)) = p; + /* */ + mp_magenta_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_magenta_part(q)); + mp_name_type(mp_magenta_part(q)) = mp_magenta_part_operation; + mp_link(mp_magenta_part(q)) = p; + /* */ + mp_yellow_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_yellow_part(q)); + mp_name_type(mp_yellow_part(q)) = mp_yellow_part_operation; + mp_link(mp_yellow_part(q)) = p; + /* */ + mp_black_part(q) = mp_new_value_node(mp); + mp_new_indep(mp, mp_black_part(q)); + mp_name_type(mp_black_part(q)) = mp_black_part_operation; + mp_link(mp_black_part(q)) = p; + break; + } + mp_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 q; + mp_node p = mp_new_value_node(mp); + mp_name_type(p) = mp_capsule_operation; + mp_set_value_number(p, zero_t); /* todo: this was |null| */ + mp_init_transform_node(mp, p); + q = mp_get_value_node(p); + mp_type(mp_tx_part(q)) = mp_known_type; + mp_set_value_number(mp_tx_part(q), zero_t); + mp_type(mp_ty_part(q)) = mp_known_type; + mp_set_value_number(mp_ty_part(q), zero_t); + mp_type(mp_xy_part(q)) = mp_known_type; + mp_set_value_number(mp_xy_part(q), zero_t); + mp_type(mp_yx_part(q)) = mp_known_type; + mp_set_value_number(mp_yx_part(q), zero_t); + mp_type(mp_xx_part(q)) = mp_known_type; + mp_set_value_number(mp_xx_part(q), unity_t); + mp_type(mp_yy_part(q)) = mp_known_type; + mp_set_value_number(mp_yy_part(q), unity_t); + 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 = mp_new_value_node(mp); + mp_type(p) = mp_undefined_type; + mp_name_type(p) = mp_root_operation; + mp_set_value_sym(p, x); + set_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 = NULL; /* a token list that will name the variable's suffix */ + mp_node r = NULL; /* temporary for token list creation */ + while (mp_name_type(p) >= mp_x_part_operation) { + switch (mp_name_type(p)) { + case mp_x_part_operation : mp_print_str(mp, "xpart "); break; + case mp_y_part_operation : mp_print_str(mp, "ypart "); break; + case mp_xx_part_operation : mp_print_str(mp, "xxpart "); break; + case mp_xy_part_operation : mp_print_str(mp, "xypart "); break; + case mp_yx_part_operation : mp_print_str(mp, "yxpart "); break; + case mp_yy_part_operation : mp_print_str(mp, "yypart "); break; + case mp_red_part_operation : mp_print_str(mp, "redpart "); break; + case mp_green_part_operation : mp_print_str(mp, "greenpart "); break; + case mp_blue_part_operation : mp_print_str(mp, "bluepart "); break; + case mp_cyan_part_operation : mp_print_str(mp, "cyanpart "); break; + case mp_magenta_part_operation: mp_print_str(mp, "magentapart "); break; + case mp_yellow_part_operation : mp_print_str(mp, "yellowpart "); break; + case mp_black_part_operation : mp_print_str(mp, "blackpart "); break; + case mp_grey_part_operation : mp_print_str(mp, "greypart "); break; + case mp_capsule_operation : mp_print_fmt(mp, "%%CAPSULE%p", p); return; + default : break; + } + p = mp_link(p); + } + while (mp_name_type(p) > mp_saved_root_operation) { + /* + Ascend one level, pushing a token onto list |q| and replacing |p| by + its parent + */ + if (mp_name_type(p) == mp_subscript_operation) { + r = mp_new_num_tok(mp, &(mp_subscript(p))); + do { + p = mp_link(p); + } while (mp_name_type(p) != mp_attribute_operation); + } else if (mp_name_type(p) == mp_structured_root_operation) { + p = mp_link(p); + goto FOUND; + } else if (mp_name_type(p) != mp_attribute_operation) { + mp_confusion(mp, "variable"); + } else { + r = mp_new_symbolic_node(mp); + /* the hash address */ + mp_set_sym_sym(r, mp_get_hashloc(p)); + } + mp_set_link(r, q); + q = r; + FOUND: + p = mp_get_parent((mp_value_node) p); + + } + /* + 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_new_symbolic_node(mp); + mp_set_sym_sym(r, mp_get_value_sym(p)); + mp_link(r) = q; + if (mp_name_type(p) == mp_saved_root_operation) { + mp_print_str(mp, "(SAVED)"); + } + mp_show_token_list(mp, r, NULL); + mp_flush_token_list(mp, r); +} + +@ The |interesting| function returns |true| if a given variable is not in a +capsule, or if the user wants to trace capsules. + +@c +static int mp_interesting (MP mp, mp_node p) +{ + if (number_positive(internal_value(mp_tracing_capsules_internal))) { + return 1; + } else { + mp_name_type_type t = mp_name_type(p); + if (t >= mp_x_part_operation && t != mp_capsule_operation) { + mp_node tt = mp_get_value_node(mp_link(p)); + switch (t) { + case mp_x_part_operation: t = mp_name_type(mp_x_part (tt)); break; + case mp_y_part_operation: t = mp_name_type(mp_y_part (tt)); break; + case mp_xx_part_operation: t = mp_name_type(mp_xx_part (tt)); break; + case mp_xy_part_operation: t = mp_name_type(mp_xy_part (tt)); break; + case mp_yx_part_operation: t = mp_name_type(mp_yx_part (tt)); break; + case mp_yy_part_operation: t = mp_name_type(mp_yy_part (tt)); break; + case mp_red_part_operation: t = mp_name_type(mp_red_part (tt)); break; + case mp_green_part_operation: t = mp_name_type(mp_green_part (tt)); break; + case mp_blue_part_operation: t = mp_name_type(mp_blue_part (tt)); break; + case mp_cyan_part_operation: t = mp_name_type(mp_cyan_part (tt)); break; + case mp_magenta_part_operation: t = mp_name_type(mp_magenta_part(tt)); break; + case mp_yellow_part_operation: t = mp_name_type(mp_yellow_part (tt)); break; + case mp_black_part_operation: t = mp_name_type(mp_black_part (tt)); break; + case mp_grey_part_operation: t = mp_name_type(mp_grey_part (tt)); break; + default: break; + } + } + return (t != mp_capsule_operation); + } +} + +@ 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 r = NULL; + switch (mp_name_type(p)) { + case mp_root_operation: + { + mp_sym q = mp_get_value_sym(p); + r = mp_new_value_node(mp); + set_equiv_node(q, r); + } + break; + case mp_subscript_operation: + /* Link a new subscript node |r| in place of node |p| */ + { + mp_node q_new; + mp_node q = p; + do { + q = mp_link(q); + } while (mp_name_type(q) != mp_attribute_operation); + q = mp_get_parent((mp_value_node) q); + r = mp->temp_head; + mp_set_link(r, mp_get_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) { + mp_set_subscr_head(q, r); + } else { + mp_set_link(q_new, r); + } + number_clone(mp_subscript(r), mp_subscript(p)); + } + break; + case mp_attribute_operation: + /* + Link a new attribute node |r| in place of node |p| If the + attribute is |collective_subscript|, there are two pointers to + node~|p|, so we must change both of them. + */ + { + mp_value_node rr; + mp_node q = mp_get_parent((mp_value_node) p); + r = mp_get_attribute_head(q); + do { + q = r; + r = mp_link(r); + } while (r != p); + rr = mp_get_attribute_node(mp); + r = (mp_node) rr; + mp_set_link(q, rr); + mp_set_hashloc(rr, mp_get_hashloc(p)); + mp_set_parent(rr, mp_get_parent((mp_value_node) p)); + if (mp_get_hashloc(p) == mp_collective_subscript) { + q = mp->temp_head; + mp_set_link(q, mp_get_subscr_head(mp_get_parent((mp_value_node) p))); + while (mp_link(q) != p) { + q = mp_link(q); + } + if (q == mp->temp_head) { + mp_set_subscr_head(mp_get_parent((mp_value_node) p), (mp_node) rr); + } else { + mp_set_link(q, rr); + } + } + } + break; + default: + mp_confusion(mp, "structure"); + break; + } + if (r) { + mp_value_node q; + mp_set_link(r, mp_link(p)); + mp_set_value_sym(r, mp_get_value_sym(p)); + mp_type(r) = mp_structured_type; + mp_name_type(r) = mp_name_type(p); + mp_set_attribute_head(r, p); + mp_name_type(p) = mp_structured_root_operation; + q = mp_get_attribute_node(mp); + mp_set_link(p, q); + mp_set_subscr_head(r, (mp_node) q); + mp_set_parent(q, r); + mp_type(q) = mp_undefined_type; + mp_name_type(q) = mp_attribute_operation; + mp_set_link(q, mp->end_attr); + mp_set_hashloc(q, mp_collective_subscript); + } + return r; +} + +@ 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|. + +@c +static mp_node mp_find_variable (MP mp, mp_node t) +{ + mp_sym p_sym = mp_get_sym_sym(t); + @^inner loop@> + // if ((eq_type(p_sym) % mp_outer_tag_command) != mp_tag_command) { + if (eq_type(p_sym) != mp_tag_command) { + return NULL; + } else { + mp_node p, q, r, s; /* nodes in the \quote {value} line */ + mp_node pp, qq, rr, ss; /* nodes in the \quote {collective} line */ + t = mp_link(t); + 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 + 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. + */ + if (mp_type(pp) != mp_structured_type) { + if (mp_type(pp) > mp_structured_type) { + return NULL; + } else { + ss = mp_new_structure(mp, pp); + if (p == pp) { + p = ss; + } + pp = ss; + } + } + /* now |type(pp)=mp_structured| */ + if (mp_type(p) != mp_structured_type) { + /* it cannot be |>mp_structured| */ + p = mp_new_structure(mp, p); + /* now |type(p)=mp_structured| */ + } + if (mp_type(t) != mp_symbol_node_type) { + /* + Descend one level for the subscript |value (t)| We want this part + of the program to be reasonably fast, in case there are lots of + subscripts at the same level of the data structure. Therefore we + store an \quote {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. + */ + mp_number nn, save_subscript; /* temporary storage */ + new_number_clone(nn, mp_get_value_number(t)); + pp = mp_link(mp_get_attribute_head(pp)); + /* now |mp_get_hashloc(pp)=mp_collective_subscript| */ + q = mp_link(mp_get_attribute_head(p)); + new_number_clone(save_subscript, mp_subscript(q)); + set_number_to_inf(mp_subscript(q)); + s = mp->temp_head; + mp_set_link(s, mp_get_subscr_head(p)); + do { + r = s; + s = mp_link(s); + } while (number_greater(nn, mp_subscript(s))); + if (number_equal(nn, mp_subscript(s))) { + p = s; + } else { + mp_value_node p1 = mp_get_subscr_node(mp); + if (r == mp->temp_head) { + mp_set_subscr_head(p, (mp_node) p1); + } else { + mp_set_link(r, p1); + } + mp_set_link(p1, s); + number_clone(mp_subscript(p1), nn); + mp_name_type(p1) = mp_subscript_operation; + mp_type(p1) = mp_undefined_type; + p = (mp_node) p1; + } + number_clone(mp_subscript(q), save_subscript); + free_number(save_subscript); + free_number(nn); + } else { + /* Descend one level for the attribute |mp_get_sym_info(t)| */ + mp_sym nn1 = mp_get_sym_sym(t); + ss = mp_get_attribute_head(pp); + do { + rr = ss; + ss = mp_link(ss); + } while (nn1 > mp_get_hashloc(ss)); + if (nn1 < mp_get_hashloc(ss)) { + qq = (mp_node) mp_get_attribute_node(mp); + mp_set_link(rr, qq); + mp_set_link(qq, ss); + mp_set_hashloc(qq, nn1); + mp_name_type(qq) = mp_attribute_operation; + mp_type(qq) = mp_undefined_type; + mp_set_parent((mp_value_node) qq, pp); + ss = qq; + } + if (p == pp) { + p = ss; + pp = ss; + } else { + pp = ss; + s = mp_get_attribute_head(p); + do { + r = s; + s = mp_link(s); + } while (nn1 > mp_get_hashloc(s)); + if (nn1 == mp_get_hashloc(s)) { + p = s; + } else { + q = (mp_node) mp_get_attribute_node(mp); + mp_set_link(r, q); + mp_set_link(q, s); + mp_set_hashloc(q, nn1); + mp_name_type(q) = mp_attribute_operation; + mp_type(q) = mp_undefined_type; + mp_set_parent((mp_value_node) q, p); + p = q; + } + } + } + t = mp_link(t); + } + if (mp_type(pp) >= mp_structured_type) { + if (mp_type(pp) == mp_structured_type) { + pp = mp_get_attribute_head(pp); + } else { + return NULL; + } + } + if (mp_type(p) == mp_structured_type) { + p = mp_get_attribute_head(p); + } + if (mp_type(p) == mp_undefined_type) { + if (mp_type(pp) == mp_undefined_type) { + mp_type(pp) = mp_numeric_type; + mp_set_value_number(pp, zero_t); + } + mp_type(p) = mp_type(pp); + mp_set_value_number(p, zero_t); + } + return p; + } +} + +@ 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 |numeric x[]a[]b| +which recycles all variables of the form |x[i]a[j]b| (and no others), while +|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 = +mp_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, int discard_suffixes) +{ + while (t != NULL) { + if (mp_type(p) != mp_structured_type) { + return; + } else { + /* attribute to match */ + mp_sym n = mp_get_sym_sym(t); + t = mp_link(t); + if (n == mp_collective_subscript) { + mp_node q = mp_get_subscr_head(p); + mp_node r = NULL; + while (mp_name_type(q) == mp_subscript_operation) { + mp_flush_variable(mp, q, t, discard_suffixes); + if (t != NULL) { + r = q; + } else if (mp_type(q) == mp_structured_type) { + r = q; + } else { + if (r == NULL) { + mp_set_subscr_head(p, mp_link(q)); + } else { + mp_set_link(r, mp_link(q)); + } + mp_free_value_node(mp, q); + } + q = r == NULL ? mp_get_subscr_head(p) : mp_link(r); + } + } + p = mp_get_attribute_head(p); + do { + p = mp_link(p); + } while (mp_get_hashloc(p) < n); + if (mp_get_hashloc(p) != n) { + return; + } + } + } + if (discard_suffixes) { + mp_flush_below_variable(mp, p); + } else { + if (mp_type(p) == mp_structured_type) { + p = mp_get_attribute_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) +{ + if (mp_type(p) != mp_structured_type) { + mp_recycle_value(mp, p); /* this sets |type(p)=undefined| */ + } else { + mp_node r; + mp_node q = mp_get_subscr_head(p); + while (mp_name_type(q) == mp_subscript_operation) { + mp_flush_below_variable(mp, q); + r = q; + q = mp_link(q); + mp_free_value_node(mp, r); + } + r = mp_get_attribute_head(p); + q = mp_link(r); + mp_recycle_value(mp, r); + mp_free_value_node(mp, r); + do { + mp_flush_below_variable(mp, q); + r = q; + q = mp_link(q); + mp_free_value_node(mp, r); + } while (q != mp->end_attr); + mp_type(p) = mp_undefined_type; + } +} + +@ 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 int mp_und_type (MP mp, mp_node p) +{ + (void) mp; + switch (mp_type(p)) { + case mp_vacuous_type: + return mp_undefined_type; + case mp_boolean_type: + case mp_unknown_boolean_type: + return mp_unknown_boolean_type; + case mp_string_type: + case mp_unknown_string_type: + return mp_unknown_string_type; + case mp_pen_type: + case mp_unknown_pen_type: + return mp_unknown_pen_type; + case mp_nep_type: + case mp_unknown_nep_type: + return mp_unknown_nep_type; + case mp_path_type: + case mp_unknown_path_type: + return mp_unknown_path_type; + case mp_picture_type: + case mp_unknown_picture_type: + return mp_unknown_picture_type; + 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_type: + case mp_dependent_type: + case mp_proto_dependent_type: + case mp_independent_type: + return mp_numeric_type; + default: + 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, int saving) +{ + mp_node q = equiv_node(p); + if (eq_property(p) > 0) { + mp_check_overload(mp, p); + } + // switch (eq_type(p) % mp_outer_tag_command) { + switch (eq_type(p)) { + case mp_defined_macro_command: + case mp_primary_def_command: + case mp_secondary_def_command: + case mp_tertiary_def_command: + if (!saving) { + mp_delete_mac_ref(mp, q); + } + break; + case mp_tag_command: + if (q != NULL) { + if (saving) { + mp_name_type(q) = mp_saved_root_operation; + } else { + mp_flush_below_variable(mp, q); + mp_free_value_node(mp, q); + } + } + break; + default: + break; + } + set_equiv(p, mp->frozen_undefined->v.data.indep.serial); + set_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 \quote {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 |type| fields. If |p| points to a saved item, then + +\smallskip \hang |p->type = 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_operation| means that |p->value| holds the +former contents of |eqtb[q]| (saved in the |knot| field of the value, which is +otherwise unused for variables). Such save stack entries are generated by |save| +commands. + +\smallskip \hang |p->type = mp_internal_operation| means that |p->value| is a +|mp_internal| to be restored to internal parameter number~|q| (saved in the +|serial| field of the value, which is otherwise unused for internals). 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 { + int type; + int padding; + mp_internal value; + 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 = mp_memory_allocate(sizeof(mp_save_data)); + p->type = 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) +{ + if (mp->save_ptr != NULL) { + mp_save_data *p = mp_memory_allocate(sizeof(mp_save_data)); + p->type = mp_normal_operation; + p->link = mp->save_ptr; + p->value.v.data.indep.scale = eq_type(q); + p->value.v.data.indep.serial = equiv(q); + p->value.v.data.node = equiv_node(q); + p->value.v.data.p = (mp_knot)q; + mp->save_ptr = p; + } + mp_clear_symbol(mp, q, (mp->save_ptr != NULL)); +} + +static void mp_unsave_variable (MP mp) +{ + mp_sym q = (mp_sym)mp->save_ptr->value.v.data.p; + if (number_positive(internal_value(mp_tracing_restores_internal))) { + mp_begin_diagnostic(mp); + mp_print_nl(mp, "{restoring "); + mp_print_mp_str(mp,text(q)); + mp_print_chr(mp, '}'); + mp_end_diagnostic(mp, 0); + } + mp_clear_symbol(mp, q, 0); + set_eq_type(q, mp->save_ptr->value.v.data.indep.scale); + set_equiv(q,mp->save_ptr->value.v.data.indep.serial); + q->v.data.node = mp->save_ptr->value.v.data.node; + // if (eq_type(q) % mp_outer_tag_command == mp_tag_command) { + if (eq_type(q) == mp_tag_command) { + mp_node pp = q->v.data.node; + if (pp != NULL) { + mp_name_type(pp) = mp_root_operation; + } + } +} + +@ 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. + +Todo: check what happens with strings! We need to mess with the ref counter and +there is no need to copy a number when we have a string. + +@c +static void mp_save_internal (MP mp, int q) +{ + if (mp->save_ptr != NULL) { + mp_save_data *p = mp_memory_allocate(sizeof(mp_save_data)); + p->type = mp_internal_operation; + p->link = mp->save_ptr; + p->value = mp->internal[q]; + p->value.v.data.indep.serial = q; + if (internal_run(q) == 1) { + mp->run_internal(mp, 1, q, internal_type(q), internal_name(q)); + } + new_number_clone(p->value.v.data.n, mp->internal[q].v.data.n); + mp->save_ptr = p; + } +} + +static void mp_unsave_internal (MP mp) +{ + int q = mp->save_ptr->value.v.data.indep.serial; + mp_internal saved = mp->save_ptr->value; + if (number_positive(internal_value(mp_tracing_restores_internal))) { + mp_begin_diagnostic(mp); + mp_print_nl(mp, "{restoring "); + mp_print_str(mp, internal_name(q)); + mp_print_chr(mp, '='); + switch (internal_type(q)) { + case mp_known_type: + case mp_numeric_type: + print_number(saved.v.data.n); + break; + case mp_boolean_type: + mp_print_str(mp, number_to_boolean(saved.v.data.n) == mp_true_operation ? "true" : "false"); + break; + case mp_string_type: + { + char *s = mp_str(mp, saved.v.data.str); + mp_print_str(mp, s); + break; + } + default: + mp_confusion(mp, "internal restore"); + break; + } + mp_print_chr(mp, '}'); + mp_end_diagnostic(mp, 0); + } + free_number(mp->internal[q].v.data.n); + if (internal_run(q) == 1) { + mp->run_internal(mp, 2, q, internal_type(q), internal_name(q)); + } + mp->internal[q] = saved; +} + +@ 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 */ + while (mp->save_ptr->type != 0) { + if (mp->save_ptr->type == mp_internal_operation) { + mp_unsave_internal(mp); + } else { + mp_unsave_variable(mp); + } + p = mp->save_ptr->link; + mp_memory_free(mp->save_ptr); + mp->save_ptr = p; + } + p = mp->save_ptr->link; + mp_memory_free(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_next_knot(A) (A)->next /* the next knot in this list */ +@d mp_left_type(A) (A)->left_type /* characterizes the path entering this knot */ +@d mp_right_type(A) (A)->right_type /* characterizes the path leaving this knot */ +@d mp_prev_knot(A) (A)->prev /* the previous knot in this list (only for pens) */ +@d mp_knot_info(A) (A)->info /* temporary info, used during splitting */ + +@<Exported types...@>= +typedef struct mp_knot_data *mp_knot; + +typedef struct mp_knot_data { + mp_number x_coord; /* the |x| coordinate of this knot */ + mp_number y_coord; /* the |y| coordinate of this knot */ + union { + mp_number left_x; /* the |x| coordinate of previous control point */ + mp_number left_curl; /* curl information when entering this knot */ + mp_number left_given; /* given direction when entering this knot */ + }; + union { + mp_number left_y; /* the |y| coordinate of previous control point */ + mp_number left_tension; /* tension information when entering this knot */ + }; + union { + mp_number right_x; /* the |x| coordinate of next control point */ + mp_number right_curl; /* curl information when leaving this knot */ + mp_number right_given; /* given direction when leaving this knot */ + }; + union { + mp_number right_y; /* the |y| coordinate of next control point */ + mp_number right_tension; /* tension information when leaving this knot */ + }; + mp_knot next; + mp_knot prev; + unsigned char left_type; + unsigned char right_type; + unsigned char originator; + unsigned char state; + signed int info; + /* we now have some 3 bytes slack that we can use */ +} mp_knot_data; + +@ @<Exported types...@>= +typedef struct mp_gr_knot_data *mp_gr_knot; + +typedef struct mp_gr_knot_data { + double x_coord; + double y_coord; + double left_x; + double left_y; + double right_x; + double right_y; + mp_gr_knot next; + mp_gr_knot prev; + unsigned char left_type; + unsigned char right_type; + unsigned char originator; + unsigned char state; + signed int info; +} mp_gr_knot_data; + +@ @<MPlib header stuff@>= +typedef enum mp_knot_type { + mp_endpoint_knot, /* |mp_left_type| at path beginning and |mp_right_type| at path end */ + mp_explicit_knot, /* |mp_left_type| or |mp_right_type| when control points are known */ + mp_given_knot, /* |mp_left_type| or |mp_right_type| when a direction is given */ + mp_curl_knot, /* |mp_left_type| or |mp_right_type| when a curl is desired */ + mp_open_knot, /* |mp_left_type| or |mp_right_type| when \MP\ should choose the direction */ + mp_end_cycle_knot +} mp_knot_type; + +@ 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 \quote {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&|x_coord,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. + +@ 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 */ +@d mp_knotstate(A) (A)->state + +@<Exported types@>= +enum mp_knot_originator { + mp_program_code, /* not created by a user */ + mp_metapost_user /* created by a user */ +}; +enum mp_knot_states { + mp_regular_knot, + mp_begin_knot, + mp_end_knot, + mp_single_knot, +}; + +@ 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 = h; + do { + mp_knot q = mp_next_knot(p); + if ((p == NULL) || (q == NULL)) { + mp_print_nl(mp, "???"); + return; /* this won't happen */ + @.???@> + } else { + @<Print information for adjacent knots |p| and |q|@> + DONE1: + p = q; + if (p && ((p != h) || (mp_left_type(h) != mp_endpoint_knot))) { + @<Print two dots, followed by |given| or |curl| if present@> + } + } + } while (p != h); + if (mp_left_type(h) != mp_endpoint_knot) { + mp_print_str(mp, " cycle"); + } +} + +@ @<Print information for adjacent knots...@>= +mp_print_two(mp, &(p->x_coord), &(p->y_coord)); +switch (mp_knotstate(p)) { + case mp_begin_knot: + mp_print_str(mp, " {begin}"); + break; + case mp_end_knot: + mp_print_str(mp, " {end}"); + break; +} +switch (mp_right_type(p)) { + case mp_endpoint_knot: + { + if (mp_left_type(p) == mp_open_knot) { + mp_print_str(mp, " {open?}"); /* can't happen */ + @.open?@> + } + if ((mp_left_type(q) != mp_endpoint_knot) || (q != h)) { + q = NULL; /* force an error */ + } + goto DONE1; + } + break; + case mp_explicit_knot: + { + @<Print control points between |p| and |q|, then |goto done1|@> + } + break; + case mp_open_knot: + { + @<Print information for a curve that begins |open|@> + } + break; + case mp_curl_knot: + case mp_given_knot: + { + @<Print information for a curve that begins |curl| or |given|@> + } + break; + default: + { + mp_print_str(mp, "???"); /* can't happen */ + @.???@> + } + break; +} +if (mp_left_type(q) <= mp_explicit_knot) { + mp_print_str(mp, " .. control ?"); /* can't happen */ + @.control?@> +} else if ((! number_equal(p->right_tension, unity_t)) || (! number_equal(q->left_tension, unity_t))) { + @<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...@>= +mp_number n_sin, n_cos; +new_fraction(n_sin); +new_fraction(n_cos); +mp_print_nl(mp, " .. "); +if (mp_left_type(p) == mp_given_knot) { + n_sin_cos(p->left_given, n_cos, n_sin); + mp_print_str(mp, "{"); + print_number(n_cos); + mp_print_chr(mp, ','); + print_number(n_sin); + mp_print_chr(mp, '}'); +} else if (mp_left_type(p) == mp_curl_knot) { + mp_print_str(mp, "{curl "); + print_number(p->left_curl); + mp_print_chr(mp, '}'); +} +free_number(n_sin); +free_number(n_cos); + +@ @<Print tension between |p| and |q|@>= +mp_number v1; +mp_print_str(mp, " .. tension"); +if (number_negative(p->right_tension)) { + mp_print_str(mp, " atleast"); +} +new_number_abs(v1, p->right_tension); +print_number(v1); +if (! number_equal(p->right_tension, q->left_tension)) { + mp_print_str(mp, " and"); + if (number_negative(q->left_tension)) { + mp_print_str(mp, " atleast"); + } + number_abs_clone(v1, p->left_tension); + print_number(v1); +} +free_number(v1); + +@ @<Print control points between |p| and |q|, then |goto done1|@>= +mp_print_str(mp, " .. controls "); +mp_print_two(mp, &(p->right_x), &(p->right_y)); +mp_print_str(mp, " and "); +if (mp_left_type(q) != mp_explicit_knot) { + mp_print_str(mp, "??"); /* can't happen */ + @.??@> +} else { + mp_print_two(mp, &(q->left_x), &(q->left_y)); +} +goto DONE1; + +@ @<Print information for a curve that begins |open|@>= +if ((mp_left_type(p) != mp_explicit_knot) && (mp_left_type(p) != mp_open_knot)) { + mp_print_str(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_knot) { + mp_print_str(mp, " ??"); /* can't happen */ + @.??@> +} +if (mp_right_type(p) == mp_curl_knot) { + mp_print_str(mp, " {curl"); + print_number(p->right_curl); +} else { + mp_number n_sin, n_cos; + new_fraction(n_sin); + new_fraction(n_cos); + n_sin_cos(p->right_given, n_cos, n_sin); + mp_print_str(mp, " {"); + print_number(n_cos); + mp_print_chr(mp, ','); + print_number(n_sin); + free_number(n_sin); + free_number(n_cos); +} +mp_print_str(mp, "} "); + +@ 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, int nuline); + +@ @c +void mp_print_path (MP mp, mp_knot h, const char *s, int nuline) +{ + mp_print_diagnostic(mp, "Path", s, nuline); + mp_print_ln(mp); + @.Path at line...@> + mp_pr_path(mp, h); + mp_end_diagnostic(mp, 1); +} + +@ @<Declarations@>= +static mp_knot mp_new_knot (MP mp); + +@ @c +static mp_knot mp_new_knot (MP mp) +{ + mp_knot q; + if (mp->knot_nodes) { + q = mp->knot_nodes; + mp->knot_nodes = q->next; + mp->num_knot_nodes--; + } else { + q = mp_memory_clear_allocate(sizeof(struct mp_knot_data)); + } + new_number(q->x_coord); + new_number(q->y_coord); + new_number(q->left_x); + new_number(q->left_y); + new_number(q->right_x); + new_number(q->right_y); + return q; +} + +@ @<Declarations@>= +static mp_gr_knot mp_gr_new_knot (MP mp); + +@ @c +static mp_gr_knot mp_gr_new_knot (MP mp) +{ + mp_gr_knot q = mp_memory_allocate(sizeof(struct mp_gr_knot_data)); + (void) mp; + 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; + if (mp->knot_nodes) { + q = mp->knot_nodes; + mp->knot_nodes = q->next; + mp->num_knot_nodes--; + } else { + q = mp_memory_allocate(sizeof(struct mp_knot_data)); + } + memcpy(q, p, sizeof(struct mp_knot_data)); + if (mp->math_mode > mp_math_double_mode) { + new_number_clone(q->x_coord, p->x_coord); + new_number_clone(q->y_coord, p->y_coord); + new_number_clone(q->left_x, p->left_x); + new_number_clone(q->left_y, p->left_y); + new_number_clone(q->right_x, p->right_x); + new_number_clone(q->right_y, p->right_y); + } + mp_prev_knot(q) = NULL; + mp_next_knot(q) = NULL; + return q; +} + +@ If we want to export a knot node, we can say |export_knot|: + +@c +static mp_gr_knot mp_export_knot (MP mp, mp_knot p) +{ + mp_gr_knot q = mp_gr_new_knot(mp); + q->x_coord = number_to_double(p->x_coord); + q->y_coord = number_to_double(p->y_coord); + q->left_x = number_to_double(p->left_x); + q->left_y = number_to_double(p->left_y); + q->right_x = number_to_double(p->right_x); + q->right_y = number_to_double(p->right_y); + q->left_type = p->left_type; + q->right_type = p->right_type; + q->info = p->info; + q->originator = p->originator; + q->state = p->state; + q->prev = NULL; + q->next = 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) +{ + if (p == NULL) { + return NULL; + } else { + mp_knot q = mp_copy_knot(mp, p); + mp_knot qq = q; + mp_knot pp = mp_next_knot(p); + while (pp != p) { + mp_knot k = mp_copy_knot(mp, pp); + mp_next_knot(qq) = k; + mp_prev_knot(k) = qq; + qq = mp_next_knot(qq); + pp = mp_next_knot(pp); + } + mp_next_knot(qq) = q; + mp_prev_knot(q) = qq; + return q; + } +} + +@ The |export_path| routine makes a clone of a given path +and converts the |value|s therein to |double|s. + +@c +static mp_gr_knot mp_export_path (MP mp, mp_knot p) +{ + if (p == NULL) { + return NULL; + } else { + mp_gr_knot q = mp_export_knot(mp, p); + mp_gr_knot qq = q; + mp_knot pp = mp_next_knot(p); + while (pp != p) { + mp_gr_knot k = mp_export_knot(mp, pp); + mp_prev_knot(k) = qq; + mp_next_knot(qq) = k; + qq = k; + pp = mp_next_knot(pp); + } + mp_prev_knot(q) = qq; + 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_gr_knot mp_export_knot_list (MP mp, mp_knot p) +{ + if (p == NULL) { + return NULL; + } else { + mp_gr_knot q = mp_export_path(mp, p); + return q; + } +} + +@ Similarly, there's a way to copy the {\em 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 = mp_new_knot(mp); /* this will correspond to |p| */ + mp_knot qq = q; + mp_knot pp = p; + while (1) { + mp_right_type(qq) = mp_left_type(pp); + mp_left_type(qq) = mp_right_type(pp); + number_clone(qq->x_coord, pp->x_coord); + number_clone(qq->y_coord, pp->y_coord); + number_clone(qq->right_x, pp->left_x); + number_clone(qq->right_y, pp->left_y); + number_clone(qq->left_x, pp->right_x); + number_clone(qq->left_y, pp->right_y); + mp_originator(qq) = mp_originator(pp); + mp_knotstate(qq) = mp_knotstate(pp); + if (mp_next_knot(pp) == p) { + mp_prev_knot(qq) = q; + mp_next_knot(q) = qq; + mp->path_tail = pp; + return q; + } else { + mp_knot rr = mp_new_knot(mp); + mp_prev_knot(qq) = rr; + 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); +static void mp_toss_knot (MP mp, mp_knot p); +static void mp_free_knot (MP mp, mp_knot p); + +@ Numbers are unions of a scaled (integer), double or pointer. The pointer is +used for e.g.\ decimal numbers. These are structs with a size that is set at +compile time. A decimal number struct is allocated in the new_number function and +all the \METAPOST\ data structures that have number handle clean up and renewal. +Keeping the numbers in the free know list entries is just not worth the effort so +in decimal mode quite a bit of (de/re)allocation goes on. + +@c +static void mp_free_knot (MP mp, mp_knot q) +{ + if (mp->math_mode > mp_math_double_mode) { + free_number(q->x_coord); + free_number(q->y_coord); + free_number(q->left_x); + free_number(q->left_y); + free_number(q->right_x); + free_number(q->right_y); + } + mp_memory_free(q); +} + +static void mp_toss_knot (MP mp, mp_knot q) +{ + if (mp->num_knot_nodes < mp->max_knot_nodes) { + mp_next_knot(q) = mp->knot_nodes; + mp->knot_nodes = q; + mp->num_knot_nodes++; + if (mp->math_mode > mp_math_double_mode) { + free_number(q->x_coord); + free_number(q->y_coord); + free_number(q->left_x); + free_number(q->left_y); + free_number(q->right_x); + free_number(q->right_y); + } + } else { + mp_free_knot(mp, q); + } +} + +static void mp_toss_knot_list (MP mp, mp_knot p) +{ + if (p == NULL) { + return; + } else { + mp_knot q = p; + do { + mp_knot r = mp_next_knot(q); + mp_toss_knot(mp, 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 \quote {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 +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|@> + /* make sure that |arith_error=false| */ + check_arith(); + if (number_positive(internal_value(mp_tracing_choices_internal))) { + mp_print_path(mp, knots, ", before choices", 1); + } + @<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, thenadvance |p| to that breakpoint@> + } while (p != h); + if (number_positive(internal_value(mp_tracing_choices_internal))) { + mp_print_path(mp, knots, ", after choices", 1); + } + if (mp->arith_error) { + @<Report an unexpected problem during the choice-making@> + } +} + +@ @<Internal ...@>= +void mp_make_choices (MP mp, mp_knot knots); + +@ @<Report an unexpected problem during the choice...@>= +mp_back_error( + mp, + "Some number got too big", + "The path that I just computed is out of range. So it will probably look funny.\n" + "Proceed, for a laugh." +); +@.Some number got too big@> +mp_get_x_next(mp); +mp->arith_error = 0; + +@ Two knots in a row with the same coordinates will always be joined by an +explicit \quote {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 (number_equal(p->x_coord, q->x_coord) && number_equal(p->y_coord, q->y_coord) && mp_right_type(p) > mp_explicit_knot) { + mp_right_type(p) = mp_explicit_knot; + if (mp_left_type(p) == mp_open_knot) { + mp_left_type(p) = mp_curl_knot; + set_number_to_unity(p->left_curl); + } + mp_left_type(q) = mp_explicit_knot; + if (mp_right_type(q) == mp_open_knot) { + mp_right_type(q) = mp_curl_knot; + set_number_to_unity(q->right_curl); + } + number_clone(p->right_x, p->x_coord); + number_clone(q->left_x, p->x_coord); + number_clone(p->right_y, p->y_coord); + number_clone(q->left_y, p->y_coord); + } + 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_knot) { + break; + } else if (mp_right_type(h) != mp_open_knot) { + break; + } else { + h = mp_next_knot(h); + if (h == knots) { + mp_left_type(h) = mp_end_cycle_knot; + 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_knot) { + while ((mp_left_type(q) == mp_open_knot) && (mp_right_type(q) == mp_open_knot)) { + q = mp_next_knot(q); + } + @<Fill in the control information between consecutive breakpoints |p| and |q|@> +} else if (mp_right_type(p) == mp_endpoint_knot) { + @<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|@>= +number_clone(p->right_x, p->x_coord); +number_clone(p->right_y, p->y_coord); +number_clone(q->left_x, q->x_coord); +number_clone(q->left_y, q->y_coord); + +@ 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 \quote {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 \quote {velocity ratios} at the +beginning and end of the curve, while $\theta_k$ and $\phi\k$ are the +corresponding \quote {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 \quote {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|5\over4|A_k$ and +$C_k|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 \quote {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...@>= +@<FillInAllocate@> +@<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@> +@<FillInDeallocate@> +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 */ +int path_padding; /* be nice */ + +mp_number *delta_x; +mp_number *delta_y; +mp_number *delta; /* knot differences */ +mp_number *psi; /* turning angles */ + +@ @<Dealloc variables@>= +for (int k = 0; k<mp->path_size; k++) { + free_number(mp->delta_x[k]); + free_number(mp->delta_y[k]); + free_number(mp->delta[k]); + free_number(mp->psi[k]); +} +mp_memory_free(mp->delta_x); +mp_memory_free(mp->delta_y); +mp_memory_free(mp->delta); +mp_memory_free(mp->psi); + +@ @<Other local variables for |make_choices|@>= +int k, n; /* current and final knot numbers */ +mp_knot s, t; /* registers for list traversal */ + +@ @<FillInAllocate@>= +mp_number sine, cosine; /* trig functions of various angles */ +mp_number arg1, arg2, r1, r2; +mp_number delx, dely; /* directions where |open| meets |explicit| */ +new_fraction(sine); +new_fraction(cosine); +new_number(arg1); +new_number(arg2); +new_fraction(r1); +new_fraction(r2); +new_number(delx); +new_number(dely); + +@ @<FillInDeallocate@>= +free_number(sine); +free_number(cosine); +free_number(arg1); +free_number(arg2); +free_number(r1); +free_number(r2); +free_number(delx); +free_number(dely); + +@ @<Calculate the turning angles...@>= +{ + RESTART: + k = 0; + s = p; + n = mp->path_size; + do { + t = mp_next_knot(s); + set_number_from_subtraction(mp->delta_x[k], t->x_coord, s->x_coord); + set_number_from_subtraction(mp->delta_y[k], t->y_coord, s->y_coord); + pyth_add(mp->delta[k], mp->delta_x[k], mp->delta_y[k]); + if (k > 0) { + make_fraction(r1, mp->delta_y[k - 1], mp->delta[k - 1]); + number_clone(sine, r1); + make_fraction(r2, mp->delta_x[k - 1], mp->delta[k - 1]); + number_clone(cosine, r2); + take_fraction(r1, mp->delta_x[k], cosine); + take_fraction(r2, mp->delta_y[k], sine); + set_number_from_addition(arg1, r1, r2); + take_fraction(r1, mp->delta_y[k], cosine); + take_fraction(r2, mp->delta_x[k], sine); + set_number_from_subtraction(arg2, r1, r2); + n_arg(mp->psi[k], arg1, arg2 ); + } + ++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 */ + } else if (s == q) { + n = k; + } + } while (! ((k >= n) && (mp_left_type(s) != mp_end_cycle_knot))); + if (k == n) { + set_number_to_zero(mp->psi[k]); + } else { + number_clone(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_knot) { + set_number_from_subtraction(delx, q->right_x, q->x_coord); + set_number_from_subtraction(dely, q->right_y, q->y_coord); + if (number_zero(delx) && number_zero(dely)) { + mp_left_type(q) = mp_curl_knot; + set_number_to_unity(q->left_curl); + } else { + mp_left_type(q) = mp_given_knot; + n_arg(q->left_given, delx, dely); + } + } + if ((mp_right_type(p) == mp_open_knot) && (mp_left_type(p) == mp_explicit_knot)) { + set_number_from_subtraction(delx, p->x_coord, p->left_x); + set_number_from_subtraction(dely, p->y_coord, p->left_y); + if (number_zero(delx) && number_zero(dely)) { + mp_right_type(p) = mp_curl_knot; + set_number_to_unity(p->right_curl); + } else { + mp_right_type(p) = mp_given_knot; + n_arg(p->right_given, 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...@>= +mp_number *theta; /* values of $\theta_k$ */ +mp_number *uu; /* values of $u_k$ */ +mp_number *vv; /* values of $v_k$ */ +mp_number *ww; /* values of $w_k$ */ + +@ @<Dealloc variables@>= +for (int k = 0; k < mp->path_size; k++) { + free_number(mp->theta[k]); + free_number(mp->uu[k]); + free_number(mp->vv[k]); + free_number(mp->ww[k]); +} +mp_memory_free(mp->theta); +mp_memory_free(mp->uu); +mp_memory_free(mp->vv); +mp_memory_free(mp->ww); + +@ @<Declarations@>= +static void mp_reallocate_paths (MP mp, int l); + +@ @c +void mp_reallocate_paths (MP mp, int l) +{ + mp->delta_x = mp_memory_reallocate(mp->delta_x, (size_t) (l + 1) * sizeof(mp_number)); + mp->delta_y = mp_memory_reallocate(mp->delta_y, (size_t) (l + 1) * sizeof(mp_number)); + mp->delta = mp_memory_reallocate(mp->delta, (size_t) (l + 1) * sizeof(mp_number)); + mp->psi = mp_memory_reallocate(mp->psi, (size_t) (l + 1) * sizeof(mp_number)); + mp->theta = mp_memory_reallocate(mp->theta, (size_t) (l + 1) * sizeof(mp_number)); + mp->uu = mp_memory_reallocate(mp->uu, (size_t) (l + 1) * sizeof(mp_number)); + mp->vv = mp_memory_reallocate(mp->vv, (size_t) (l + 1) * sizeof(mp_number)); + mp->ww = mp_memory_reallocate(mp->ww, (size_t) (l + 1) * sizeof(mp_number)); + for (int k = mp->path_size; k<l; k++) { + new_number(mp->delta_x[k]); + new_number(mp->delta_y[k]); + new_number(mp->delta[k]); + new_angle(mp->psi[k]); + new_angle(mp->theta[k]); + new_fraction(mp->uu[k]); + new_angle(mp->vv[k]); + new_fraction(mp->ww[k]); + } + 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, int n); + +@ @c +void mp_solve_choices (MP mp, mp_knot p, mp_knot q, int n) +{ + int k = 0; /* current knot number */ + mp_knot r = 0; + mp_knot s = p; + mp_number ff; + new_fraction(ff); + while (1) { + mp_knot 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_knot: + case mp_open_knot: + @<Set up the 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_knot: + @<Set up the equation for a curl at $\theta_n$ and |goto found|@> + break; + case mp_given_knot: + @<Calculate the given value of $\theta_n$ and |goto found|@> + break; + } + } + r = s; + s = t; + ++k; + } +FOUND: + @<Finish choosing angles and assigning control points@> + free_number(ff); +} + +@ 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_knot: + if (mp_left_type(t) == mp_given_knot) { + @<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_knot: + if (mp_left_type(t) == mp_curl_knot) { + @<Reduce to simple case of straight line and |return|@> + } else { + @<Set up the equation for a curl at $\theta_0$@> + } + break; + case mp_open_knot: + set_number_to_zero(mp->uu[0]); + set_number_to_zero(mp->vv[0]); + number_clone(mp->ww[0], fraction_one_t); + /* this begins a cycle */ + break; +} + +@ 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. + +@ @<Set up the equation to match mock curvatures...@>= +{ + mp_number aa, bb, cc, acc; /* temporary registers */ + mp_number dd, ee; /* likewise, but |scaled| */ + new_fraction(aa); + new_fraction(bb); + new_fraction(cc); + new_fraction(acc); + new_number(dd); + new_number(ee); + @<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)$@> + take_fraction(mp->uu[k], ff, bb); + @<Calculate the values of $v_k$ and $w_k$@> + if (mp_left_type(s) == mp_end_cycle_knot) { + @<Adjust $\theta_n$ to equal $\theta_0$ and |goto found|@> + } + free_number(aa); + free_number(bb); + free_number(cc); + free_number(acc); + free_number(dd); + free_number(ee); +} + +@ 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|=...@>= +{ + mp_number absval; + new_number_abs(absval, r->right_tension); + if (number_equal(absval, unity_t)) { + number_clone(aa, fraction_half_t); + number_clone(dd, mp->delta[k]); + number_double(dd); + } else { + mp_number arg1, arg2, ret; + new_number(arg1); + new_number_abs(arg2, r->right_tension); + number_multiply_int(arg2, 3); + number_subtract(arg2, unity_t); + make_fraction(aa, unity_t, arg2); + number_abs_clone(arg2, r->right_tension); + new_fraction(ret); + make_fraction(ret, unity_t, arg2); + set_number_from_subtraction(arg1, fraction_three_t, ret); + take_fraction(arg2, mp->delta[k], arg1); + number_clone(dd, arg2); + free_number(ret); + free_number(arg1); + free_number(arg2); + } + number_abs_clone(absval, t->left_tension); + if (number_equal(absval, unity_t)) { + number_clone(bb, fraction_half_t); + number_clone(ee, mp->delta[k - 1]); + number_double(ee); + } else { + mp_number arg1, arg2, ret; + new_number(arg1); + new_number_abs(arg2, t->left_tension); + number_multiply_int(arg2, 3); + number_subtract(arg2, unity_t); + make_fraction(bb, unity_t, arg2); + number_abs_clone(arg2, t->left_tension); + new_fraction(ret); + make_fraction(ret, unity_t, arg2); + set_number_from_subtraction(arg1, fraction_three_t, ret); + take_fraction(ee, mp->delta[k - 1], arg1); + free_number(ret); + free_number(arg1); + free_number(arg2); + } + free_number(absval); +} +{ + mp_number r1; + new_number(r1); + take_fraction(r1, mp->uu[k - 1], aa); + set_number_from_subtraction(cc, fraction_one_t, r1); + free_number(r1); +} + +@ 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)$@>= +{ + mp_number rt, lt; + mp_number arg2; + new_number_clone(arg2, dd); + take_fraction(dd, arg2, cc); + new_number_abs(lt, s->left_tension); + new_number_abs(rt, s->right_tension); + if (! number_equal(lt, rt)) { + /* $\beta_k^{-1}\ne\alpha_k^{-1}$ */ + mp_number r1; + new_number(r1); + if (number_less(lt, rt)) { + /* $\alpha_k^2/\beta_k^2$ */ + make_fraction(r1, lt, rt); + take_fraction(ff, r1, r1); + number_clone(r1, dd); + take_fraction(dd, r1, ff); + } else { + /* $\beta_k^2/\alpha_k^2$ */ + make_fraction(r1, rt, lt); + take_fraction(ff, r1, r1); + number_clone(r1, ee); + take_fraction(ee, r1, ff); + } + free_number(r1); + } + free_number(rt); + free_number(lt); + set_number_from_addition(arg2, dd, ee); + make_fraction(ff, ee, arg2); + free_number(arg2); +} + +@ 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 \quote {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$@>= +take_fraction(acc, mp->psi[k + 1], mp->uu[k]); +number_negate(acc); +if (mp_right_type(r) == mp_curl_knot) { + mp_number r1, arg2; + new_fraction(r1); + new_number(arg2); + set_number_from_subtraction(arg2, fraction_one_t, ff); + take_fraction(r1, mp->psi[1], arg2); + set_number_to_zero(mp->ww[k]); + set_number_from_subtraction(mp->vv[k], acc, r1); + free_number(r1); + free_number(arg2); +} else { + mp_number arg1, r1; + new_fraction(r1); + new_number(arg1); + set_number_from_subtraction(arg1, fraction_one_t, ff); + /* this is $B_k/(C_k+B_k-u_{k-1}A_k)<5$ */ + make_fraction(ff, arg1, cc); + free_number(arg1); + take_fraction(r1, mp->psi[k], ff); + number_subtract(acc, r1); + number_clone(r1, ff); + /* this is $A_k/(C_k+B_k-u_{k-1}A_k)$ */ + take_fraction(ff, r1, aa); + take_fraction(r1, mp->vv[k - 1], ff); + set_number_from_subtraction(mp->vv[k], acc, r1 ); + if (number_zero(mp->ww[k - 1])) { + set_number_to_zero(mp->ww[k]); + } else { + take_fraction(mp->ww[k], mp->ww[k - 1], ff); + number_negate(mp->ww[k]); + } + free_number(r1); +} + +@ 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|@>= +mp_number arg2, r1; +new_number(arg2); +new_number(r1); +set_number_to_zero(aa); +number_clone(bb, fraction_one_t); /* we have |k=n| */ +do { + --k; + if (k == 0) { + k = n; + } + take_fraction(r1, aa, mp->uu[k]); + set_number_from_subtraction(aa, mp->vv[k], r1); + take_fraction(r1, bb, mp->uu[k]); + set_number_from_subtraction(bb, mp->ww[k], r1); +} while (k != n); +/* now $\theta_n=|aa|+|bb|\cdot\theta_n$ */ +set_number_from_subtraction(arg2, fraction_one_t, bb); +make_fraction(r1, aa, arg2); +number_clone(aa, r1); +number_clone(mp->theta[n], aa); +number_clone(mp->vv[0], aa); +for (k = 1; k < n; k++) { + take_fraction(r1, aa, mp->ww[k]); + number_add(mp->vv[k], r1); +} +free_number(arg2); +free_number(r1); +free_number(aa); +free_number(bb); +free_number(cc); +free_number(acc); +free_number(dd); +free_number(ee); +goto FOUND; + +@ @c +static void mp_reduce_angle (MP mp, mp_number *a) +{ + mp_number abs_a; + new_number_abs(abs_a, *a); + if (number_greater(abs_a, one_eighty_deg_t)) { + if (number_positive(*a)) { + number_subtract(*a, three_sixty_deg_t); + } else { + number_add(*a, three_sixty_deg_t); + } + } + free_number(abs_a); +} + +@ @<Declarations@>= +static void mp_reduce_angle (MP mp, mp_number *a); + +@ @<Calculate the given value of $\theta_n$...@>= +{ + mp_number narg; + new_angle(narg); + n_arg(narg, mp->delta_x[n - 1], mp->delta_y[n - 1]); + set_number_from_subtraction(mp->theta[n], s->left_given, narg); + free_number(narg); + mp_reduce_angle(mp, &mp->theta[n]); + goto FOUND; +} + +@ @<Set up the equation for a given value of $\theta_0$@>= +{ + mp_number narg; + new_angle(narg); + n_arg(narg, mp->delta_x[0], mp->delta_y[0]); + set_number_from_subtraction(mp->vv[0], s->right_given, narg); + free_number(narg); + mp_reduce_angle(mp, &mp->vv[0]); + set_number_to_zero(mp->uu[0]); + set_number_to_zero(mp->ww[0]); +} + +@ @<Set up the equation for a curl at $\theta_0$@>= +{ + mp_number lt, rt, cc; /* tension values */ + new_number_clone(cc, s->right_curl); + new_number_abs(lt, t->left_tension); + new_number_abs(rt, s->right_tension); + if (number_unity(rt) && number_unity(lt)) { + mp_number arg1, arg2; + new_number_clone(arg1, cc); + new_number_clone(arg2, cc); + number_double(arg1); + number_add(arg1, unity_t); + number_add(arg2, two_t); + make_fraction(mp->uu[0], arg1, arg2); + free_number(arg1); + free_number(arg2); + } else { + mp_curl_ratio(mp, &mp->uu[0], &cc, &rt, <); + } + take_fraction(mp->vv[0], mp->psi[1], mp->uu[0]); + number_negate(mp->vv[0]); + set_number_to_zero(mp->ww[0]); + free_number(rt); + free_number(lt); + free_number(cc); +} + +@ @<Set up the equation for a curl at $\theta_n$...@>= +{ + mp_number lt, rt, cc; /* tension values */ + new_number_clone(cc, s->left_curl); + new_number_abs(lt, s->left_tension); + new_number_abs(rt, r->right_tension); + if (number_unity(rt) && number_unity(lt)) { + mp_number arg1, arg2; + new_number_clone(arg1, cc); + new_number_clone(arg2, cc); + number_double(arg1); + number_add(arg1, unity_t); + number_add(arg2, two_t); + make_fraction(ff, arg1, arg2); + free_number(arg1); + free_number(arg2); + } else { + mp_curl_ratio(mp, &ff, &cc, <, &rt); + } + { + mp_number arg1, arg2, r1; + new_fraction(r1); + new_fraction(arg1); + new_number(arg2); + take_fraction(arg1, mp->vv[n - 1], ff); + take_fraction(r1, ff, mp->uu[n - 1]); + set_number_from_subtraction(arg2, fraction_one_t, r1); + make_fraction(mp->theta[n], arg1, arg2); + number_negate(mp->theta[n]); + free_number(r1); + free_number(arg1); + free_number(arg2); + } + free_number(rt); + free_number(lt); + free_number(cc); + 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 void mp_curl_ratio (MP mp, mp_number *ret, mp_number *gamma, mp_number *a_tension, mp_number *b_tension); + +@ @c +void mp_curl_ratio (MP mp, mp_number *ret, mp_number *gamma_orig, mp_number *a_tension, mp_number *b_tension) +{ + mp_number alpha, beta, gamma, num, denom, ff; /* registers */ + mp_number arg1; + new_number(arg1); + new_fraction(alpha); + new_fraction(beta); + new_fraction(gamma); + new_fraction(ff); + new_fraction(denom); + new_fraction(num); + make_fraction(alpha, unity_t, *a_tension); + make_fraction(beta, unity_t, *b_tension); + number_clone(gamma, *gamma_orig); + if (number_lessequal(alpha, beta)) { + make_fraction(ff, alpha, beta); + number_clone(arg1, ff); + take_fraction(ff, arg1, arg1); + number_clone(arg1, gamma); + take_fraction(gamma, arg1, ff); + convert_fraction_to_scaled(beta); + take_fraction(denom, gamma, alpha); + number_add(denom, three_t); + } else { + make_fraction(ff, beta, alpha); + number_clone(arg1, ff); + take_fraction(ff, arg1, arg1); + take_fraction(arg1, beta, ff); + convert_fraction_to_scaled(arg1); + number_clone(beta, arg1); + take_fraction(denom, gamma, alpha); + set_number_from_div(arg1, ff, twelvebits_3); + number_add(denom, arg1); + } + number_subtract(denom, beta); + set_number_from_subtraction(arg1, fraction_three_t, alpha); + take_fraction(num, gamma, arg1); + number_add(num, beta); + number_clone(arg1, denom); + number_double(arg1); + number_double(arg1); /* arg1 = 4*denom */ + if (number_greaterequal(num, arg1)) { + number_clone(*ret, fraction_four_t); + } else { + make_fraction(*ret, num, denom); + } + free_number(alpha); + free_number(beta); + free_number(gamma); + free_number(num); + free_number(denom); + free_number(ff); + free_number(arg1); +} + +@ We're in the home stretch now. + +@<Finish choosing angles and assigning control points@>= +{ + mp_number r1; + new_number(r1); + for (k = n - 1; k >= 0; k--) { + take_fraction(r1, mp->theta[k + 1], mp->uu[k]); + set_number_from_subtraction(mp->theta[k], mp->vv[k], r1); + } + free_number(r1); +} +s = p; +k = 0; +{ + mp_number arg; + new_number(arg); + do { + mp_knot t = mp_next_knot(s); + n_sin_cos(mp->theta[k], mp->ct, mp->st); + number_negated_clone(arg, mp->psi[k + 1]); + number_subtract(arg, mp->theta[k + 1]); + n_sin_cos(arg, mp->cf, mp->sf); + mp_set_controls (mp, s, t, k); + ++k; + s = t; + } while (k != n); + free_number(arg); +} + +@ 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...@>= +mp_number st; +mp_number ct; +mp_number sf; +mp_number cf; /* sines and cosines */ + +@ @<Initialize table...@>= +new_fraction(mp->st); +new_fraction(mp->ct); +new_fraction(mp->sf); +new_fraction(mp->cf); + +@ @<Dealloc ...@>= +free_number(mp->st); +free_number(mp->ct); +free_number(mp->sf); +free_number(mp->cf); + +@ @<Declarations@>= +static void mp_set_controls (MP mp, mp_knot p, mp_knot q, int k); + +@ @c +void mp_set_controls (MP mp, mp_knot p, mp_knot q, int k) +{ + mp_number rr, ss; /* velocities, divided by thrice the tension */ + mp_number lt, rt; /* tensions */ + mp_number sine; /* $\sin(\theta+\phi)$ */ + mp_number tmp; + mp_number r1, r2; + new_number(tmp); + new_number(r1); + new_number(r2); + new_number_abs(lt, q->left_tension); + new_number_abs(rt, p->right_tension); + new_fraction(sine); + new_fraction(rr); + new_fraction(ss); + velocity(rr, mp->st, mp->ct, mp->sf, mp->cf, rt); + velocity(ss, mp->sf, mp->cf, mp->st, mp->ct, lt); + if (number_negative(p->right_tension) || number_negative(q->left_tension)) { + @<Decrease the velocities, if necessary, to stay inside the bounding triangle@> + } + take_fraction(r1, mp->delta_x [k], mp->ct); + take_fraction(r2, mp->delta_y [k], mp->st); + number_subtract(r1, r2); + take_fraction(tmp, r1, rr); + set_number_from_addition(p->right_x, p->x_coord, tmp); + take_fraction(r1, mp->delta_y[k], mp->ct); + take_fraction(r2, mp->delta_x[k], mp->st); + number_add(r1, r2); + take_fraction(tmp, r1, rr); + set_number_from_addition(p->right_y, p->y_coord, tmp); + take_fraction(r1, mp->delta_x[k], mp->cf); + take_fraction(r2, mp->delta_y[k], mp->sf); + number_add(r1, r2); + take_fraction(tmp, r1, ss); + set_number_from_subtraction(q->left_x, q->x_coord, tmp); + take_fraction(r1, mp->delta_y[k], mp->cf); + take_fraction(r2, mp->delta_x[k], mp->sf); + number_subtract(r1, r2); + take_fraction(tmp, r1, ss); + set_number_from_subtraction(q->left_y, q->y_coord, tmp); + mp_right_type(p) = mp_explicit_knot; + mp_left_type(q) = mp_explicit_knot; + free_number(tmp); + free_number(r1); + free_number(r2); + free_number(lt); + free_number(rt); + free_number(rr); + free_number(ss); + free_number(sine); +} + +@ 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 +\quote {bounding triangle.} + +@<Decrease the velocities, if necessary...@>= +if ((number_nonnegative(mp->st) && number_nonnegative(mp->sf)) || (number_nonpositive(mp->st) && number_nonpositive(mp->sf))) { + mp_number r1, r2, arg1; + new_fraction(r1); + new_fraction(r2); + new_number_abs(arg1, mp->st); + take_fraction(r1, arg1, mp->cf); + number_abs_clone(arg1, mp->sf); + take_fraction(r2, arg1, mp->ct); + set_number_from_addition(sine, r1, r2); + if (number_positive(sine)) { + set_number_from_addition(arg1, fraction_one_t, unity_t); /* safety factor */ + number_clone(r1, sine); + take_fraction(sine, r1, arg1); + if (number_negative(p->right_tension)) { + number_abs_clone(arg1, mp->sf); + if (ab_vs_cd(arg1, fraction_one_t, rr, sine) < 0) { + number_abs_clone(arg1, mp->sf); + make_fraction(rr, arg1, sine); + } + } + if (number_negative(q->left_tension)) { + number_abs_clone(arg1, mp->st); + if (ab_vs_cd(arg1, fraction_one_t, ss, sine) < 0) { + number_abs_clone(arg1, mp->st); + make_fraction(ss, arg1, sine); + } + } + } + free_number(arg1); + free_number(r1); + free_number(r2); +} + +@ Only the simple cases remain to be handled. + +@<Reduce to simple case of two givens and |return|@>= +{ + mp_number arg1; + mp_number narg; + new_angle(narg); + n_arg(narg, mp->delta_x[0], mp->delta_y[0]); + new_number(arg1); + set_number_from_subtraction(arg1, p->right_given, narg); + n_sin_cos(arg1, mp->ct, mp->st); + set_number_from_subtraction(arg1, q->left_given, narg); + n_sin_cos(arg1, mp->cf, mp->sf); + number_negate(mp->sf); + mp_set_controls (mp, p, q, 0); + free_number(narg); + free_number(arg1); + free_number(ff); + return; +} + +@ @<Reduce to simple case of straight line and |return|@>= +{ + mp_number lt, rt; /* tension values */ + mp_right_type(p) = mp_explicit_knot; + mp_left_type(q) = mp_explicit_knot; + new_number_abs(lt, q->left_tension); + new_number_abs(rt, p->right_tension); + if (number_unity(rt)) { + mp_number arg2; + new_number(arg2); + if (number_nonnegative(mp->delta_x[0])) { + set_number_from_addition(arg2, mp->delta_x[0], epsilon_t); + } else { + set_number_from_subtraction(arg2, mp->delta_x[0], epsilon_t); + } + number_int_div(arg2, 3); + set_number_from_addition(p->right_x, p->x_coord, arg2); + if (number_nonnegative(mp->delta_y[0])) { + set_number_from_addition(arg2, mp->delta_y[0], epsilon_t); + } else { + set_number_from_subtraction(arg2, mp->delta_y[0], epsilon_t); + } + number_int_div(arg2, 3); + set_number_from_addition(p->right_y, p->y_coord, arg2); + free_number(arg2); + } else { + mp_number arg2, r1; + new_fraction(r1); + new_number_clone(arg2, rt); + number_multiply_int(arg2, 3); + make_fraction(ff, unity_t, arg2); /* $\alpha/3$ */ + free_number(arg2); + take_fraction(r1, mp->delta_x[0], ff); + set_number_from_addition(p->right_x, p->x_coord, r1); + take_fraction(r1, mp->delta_y[0], ff); + set_number_from_addition(p->right_y, p->y_coord, r1); + } + if (number_unity(lt)) { + mp_number arg2; + new_number(arg2); + if (number_nonnegative(mp->delta_x[0])) { + set_number_from_addition(arg2, mp->delta_x[0], epsilon_t); + } else { + set_number_from_subtraction(arg2, mp->delta_x[0], epsilon_t); + } + number_int_div(arg2, 3); + set_number_from_subtraction(q->left_x, q->x_coord, arg2); + if (number_nonnegative(mp->delta_y[0])) { + set_number_from_addition(arg2, mp->delta_y[0], epsilon_t); + } else { + set_number_from_subtraction(arg2, mp->delta_y[0], epsilon_t); + } + number_int_div(arg2, 3); + set_number_from_subtraction(q->left_y, q->y_coord, arg2); + free_number(arg2); + } else { + mp_number arg2, r1; + new_fraction(r1); + new_number_clone(arg2, lt); + number_multiply_int(arg2, 3); + make_fraction(ff, unity_t, arg2); /* $\beta/3$ */ + free_number(arg2); + take_fraction(r1, mp->delta_x[0], ff); + set_number_from_subtraction(q->left_x, q->x_coord, r1); + take_fraction(r1, mp->delta_y[0], ff); + set_number_from_subtraction(q->left_y, q->y_coord, r1); + free_number(r1); + } + free_number(ff); + free_number(lt); + free_number(rt); + return; +} + +@ Various subroutines that are useful for the new (1.770) exported api for +solving path choices + +@c +# define TOO_LARGE(a) (fabs((a))>4096.0) +# define PI 3.1415926535897932384626433832795028841971 + +static int out_of_range (MP mp, double a) +{ + (void) mp; + mp_number t; + new_number_from_double(mp, t, fabs(a)); + if (number_greaterequal(t, inf_t)) { + free_number(t); + return 1; + } else { + free_number(t); + return 0; + } +} + +static int mp_link_knotpair (MP mp, mp_knot p, mp_knot q) +{ + (void) mp; + if (p == NULL || q == NULL) { + return 0; + } else { + mp_prev_knot(q) = p; + mp_next_knot(p) = q; + set_number_from_double(p->right_tension, 1.0); + if (mp_right_type(p) == mp_endpoint_knot) { + mp_right_type(p) = mp_open_knot; + } + set_number_from_double(q->left_tension, 1.0); + if (mp_left_type(q) == mp_endpoint_knot) { + mp_left_type(q) = mp_open_knot; + } + return 1; + } +} + +static int mp_link_knotpair_xy (MP mp, mp_knot p, mp_knot q) +{ + (void) mp; + if (p == NULL || q == NULL) { + return 0; + } else { + mp_prev_knot(q) = p; + mp_next_knot(p) = q; + return 1; + } +} + +int mp_close_path_cycle (MP mp, mp_knot p, mp_knot q) +{ + return mp_link_knotpair(mp, p, q); +} + +int mp_close_path (MP mp, mp_knot q, mp_knot first) +{ + if (q == NULL || first == NULL) { + return 0; + } else { + mp_prev_knot(first) = q; + mp_next_knot(q) = first; + mp_right_type(q) = mp_endpoint_knot; + set_number_from_double(q->right_tension, 1.0); + mp_left_type(first) = mp_endpoint_knot; + set_number_from_double(first->left_tension, 1.0); + return 1; + } +} + +mp_knot mp_create_knot (MP mp) +{ + mp_knot q = mp_new_knot(mp); + mp_left_type(q) = mp_endpoint_knot; + mp_right_type(q) = mp_endpoint_knot; + return q; +} + +int mp_set_knot (MP mp, mp_knot p, double x, double y) +{ + if (p == NULL) { + return 0; + } else if (out_of_range(mp, x)) { + return 0; + } else if (out_of_range(mp, y)) { + return 0; + } else { + set_number_from_double(p->x_coord, x); + set_number_from_double(p->y_coord, y); + return 1; + } +} + +mp_knot mp_append_knot (MP mp, mp_knot p, double x, double y) +{ + mp_knot q = mp_create_knot(mp); + if (q == NULL) { + return NULL; + } else if (! mp_set_knot(mp, q, x, y)) { + mp_memory_free(q); + return NULL; + } else if (p == NULL) { + return q; + } else if (mp_link_knotpair(mp, p, q)) { + return q; + } else { + mp_memory_free(q); + return NULL; + } +} + +mp_knot mp_append_knot_xy (MP mp, mp_knot p, double x, double y) +{ + mp_knot q = mp_create_knot(mp); + if (q == NULL) { + return NULL; + } else if (! mp_set_knot(mp, q, x, y)) { + mp_memory_free(q); + return NULL; + } else if (p == NULL) { + return q; + } else if (mp_link_knotpair_xy(mp, p, q)) { + mp_right_type(p) = mp_explicit_knot; + mp_left_type(p) = mp_explicit_knot; + return q; + } else { + mp_memory_free(q); + return NULL; + } +} + +int mp_set_knot_curl (MP mp, mp_knot q, double value) /* same as mp_set_knot_right_curl */ +{ + if (q == NULL) { + return 0; + } else if (TOO_LARGE(value)) { + return 0; + } else { + mp_right_type(q) = mp_curl_knot; + set_number_from_double(q->right_curl, value); + if (mp_left_type(q) == mp_open_knot) { + mp_left_type(q) = mp_curl_knot; + set_number_from_double(q->left_curl, value); + } + return 1; + } +} + +int mp_set_knot_left_curl (MP mp, mp_knot q, double value) +{ + if (q == NULL) { + return 0; + } else if (TOO_LARGE(value)) { + return 0; + } else { + mp_left_type(q) = mp_curl_knot; + set_number_from_double(q->left_curl, value); + if (mp_right_type(q) == mp_open_knot) { + mp_right_type(q) = mp_curl_knot; + set_number_from_double(q->right_curl, value); + } + return 1; + } +} + +int mp_set_knot_right_curl (MP mp, mp_knot q, double value) +{ + if (q == NULL) { + return 0; + } else if (TOO_LARGE(value)) { + return 0; + } else { + mp_right_type(q) = mp_curl_knot; + set_number_from_double(q->right_curl, value); + if (mp_left_type(q) == mp_open_knot) { + mp_left_type(q) = mp_curl_knot; + set_number_from_double(q->left_curl, value); + } + return 1; + } +} + +int mp_set_knot_simple_curl (MP mp, mp_knot q) +{ + if (q == NULL) { + return 0; + } else { + /* no need for double */ + mp_right_type(q) = mp_curl_knot; + set_number_from_double(q->right_curl, 1.0); + mp_left_type(q) = mp_curl_knot; + set_number_from_double(q->left_curl, 1.0); + return 1; + } +} + +int mp_set_knotpair_curls (MP mp, mp_knot p, mp_knot q, double t1, double t2) +{ + if (p == NULL || q == NULL) { + return 0; + } else if (mp_set_knot_curl(mp, p, t1)) { + return mp_set_knot_curl(mp, q, t2); + } else { + return 0; + } +} + +int mp_set_knotpair_tensions (MP mp, mp_knot p, mp_knot q, double t1, double t2) +{ + if (p == NULL || q == NULL) { + return 0; + } else if (TOO_LARGE(t1)) { + return 0; + } else if (TOO_LARGE(t2)) { + return 0; + } else if ((fabs(t1) < 0.75)) { + return 0; + } else if ((fabs(t2) < 0.75)) { + return 0; + } else { + set_number_from_double(p->right_tension, t1); + set_number_from_double(q->left_tension, t2); + return 1; + } +} + +int mp_set_knot_left_tension (MP mp, mp_knot p, double t1) +{ + if (p == NULL) { + return 0; + } else if (TOO_LARGE(t1)) { + return 0; + } else if ((fabs(t1) < 0.75)) { + return 0; + } else { + set_number_from_double(p->left_tension, t1); + return 1; + } +} + +int mp_set_knot_right_tension (MP mp, mp_knot p, double t1) +{ + if (p == NULL) { + return 0; + } else if (TOO_LARGE(t1)) { + return 0; + } else if ((fabs(t1) < 0.75)) { + return 0; + } else { + set_number_from_double(p->right_tension, t1); + return 1; + } +} + +int mp_set_knotpair_controls (MP mp, mp_knot p, mp_knot q, double x1, double y1, double x2, double y2) +{ + if (p == NULL || q == NULL) { + return 0; + } else if (out_of_range(mp, x1)) { + return 0; + } else if (out_of_range(mp, y1)) { + return 0; + } else if (out_of_range(mp, x2)) { + return 0; + } else if (out_of_range(mp, y2)) { + return 0; + } else { + mp_right_type(p) = mp_explicit_knot; + set_number_from_double(p->right_x, x1); + set_number_from_double(p->right_y, y1); + mp_left_type(q) = mp_explicit_knot; + set_number_from_double(q->left_x, x2); + set_number_from_double(q->left_y, y2); + return 1; + } +} + +int mp_set_knot_left_control (MP mp, mp_knot p, double x1, double y1) +{ + if (p == NULL) { + return 0; + } else if (out_of_range(mp, x1)) { + return 0; + } else if (out_of_range(mp, y1)) { + return 0; + } else { + mp_left_type(p) = mp_explicit_knot; + set_number_from_double(p->left_x, x1); + set_number_from_double(p->left_y, y1); + return 1; + } +} + +int mp_set_knot_right_control (MP mp, mp_knot p, double x1, double y1) +{ + if (p == NULL) { + return 0; + } else if (out_of_range(mp, x1)) { + return 0; + } else if (out_of_range(mp, y1)) { + return 0; + } else { + mp_right_type(p) = mp_explicit_knot; + set_number_from_double(p->right_x, x1); + set_number_from_double(p->right_y, y1); + return 1; + } +} + +int mp_set_knot_direction (MP mp, mp_knot q, double x, double y) +{ + if (q == NULL) { + return 0; + } else if (TOO_LARGE(x)) { + return 0; + } else if (TOO_LARGE(y)) { + return 0; + } else { + double value = 0; + if (!(x == 0 && y == 0)) { + value = atan2(y, x) * (180.0 / PI) * 16.0; + } + mp_right_type(q) = mp_given_knot; + set_number_from_double(q->right_curl, value); + if (mp_left_type(q) == mp_open_knot) { + mp_left_type(q) = mp_given_knot; + set_number_from_double(q->left_curl, value); + } + return 1; + } +} + +int mp_set_knotpair_directions (MP mp, mp_knot p, mp_knot q, double x1, double y1, double x2, double y2) +{ + if (p == NULL || q == NULL) { + return 0; + } else if (mp_set_knot_direction(mp,p, x1, y1)) { + return mp_set_knot_direction(mp,q, x2, y2); + } else { + return 0; + } +} + +@ @c +static int path_needs_fixing(mp_knot source) +{ + mp_knot sourcehead = source; + do { + source = source->next; + } while (source && source != sourcehead); + if (! source) { + return 1; + } else { + return 0; + } +} + +int mp_solve_path (MP mp, mp_knot first) +{ + if (first == NULL) { + return 0; + } else if (path_needs_fixing(first)) { + return 0; + } else { + int saved_arith_error = mp->arith_error; + int retval = 1; + jmp_buf *saved_jump_buf = mp->jump_buf; + mp->jump_buf = mp_memory_allocate(sizeof(jmp_buf)); + if (mp->jump_buf == NULL || setjmp(*(mp->jump_buf)) != 0) { + return 0; + } else { + mp->arith_error = 0; + mp_make_choices(mp, first); + if (mp->arith_error) { + retval = 0; + } + mp->arith_error = saved_arith_error; + mp_memory_free(mp->jump_buf); + mp->jump_buf = saved_jump_buf; + return retval; + } + } +} + +void mp_free_path (MP mp, mp_knot p) +{ + mp_toss_knot_list(mp, p); +} + +@ @<Exported function headers@>= +int mp_close_path_cycle (MP mp, mp_knot p, mp_knot q); +int mp_close_path (MP mp, mp_knot q, mp_knot first); +mp_knot mp_create_knot (MP mp); +int mp_set_knot (MP mp, mp_knot p, double x, double y); +mp_knot mp_append_knot (MP mp, mp_knot p, double x, double y); +mp_knot mp_append_knot_xy (MP mp, mp_knot p, double x, double y); +int mp_set_knot_curl (MP mp, mp_knot q, double value); +int mp_set_knot_left_curl (MP mp, mp_knot q, double value); +int mp_set_knot_right_curl (MP mp, mp_knot q, double value); +int mp_set_knot_simple_curl (MP mp, mp_knot q); +int mp_set_knotpair_curls (MP mp, mp_knot p, mp_knot q, double t1, double t2) ; +int mp_set_knotpair_tensions (MP mp, mp_knot p, mp_knot q, double t1, double t2) ; +int mp_set_knot_left_tension (MP mp, mp_knot p, double t1); +int mp_set_knot_right_tension (MP mp, mp_knot p, double t1); +int mp_set_knot_left_control (MP mp, mp_knot p, double t1, double t2); +int mp_set_knot_right_control (MP mp, mp_knot p, double t1, double t2); +int mp_set_knotpair_controls (MP mp, mp_knot p, mp_knot q, double x1, double y1, double x2, double y2) ; +int mp_set_knot_direction (MP mp, mp_knot q, double x, double y) ; +int mp_set_knotpair_directions (MP mp, mp_knot p, mp_knot q, double x1, double y1, double x2, double y2) ; +int mp_solve_path (MP mp, mp_knot first); +void mp_free_path (MP mp, mp_knot p); + +@ Simple accessors for |mp_knot|. + +@c +double mp_number_as_double (MP mp, mp_number n) { + (void) mp; + return number_to_double(n); +} + +@ @<Exported function headers@>= +double mp_number_as_double (MP mp, mp_number n); + +@* 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|1\over2|$ and ${1\over2}\L t\L1$. + +@ Here is a routine that computes the $x$ or $y$ coordinate of the point on a +cubic corresponding to the |fraction| value~|t|. + +@c +static void mp_eval_cubic (MP mp, mp_number *r, mp_knot p, mp_knot q, int c, mp_number *t) +{ + mp_number x1, x2, x3; /* intermediate values */ + new_number(x1); + new_number(x2); + new_number(x3); + if (c == mp_x_code) { + set_number_from_of_the_way(x1, *t, p->x_coord, p->right_x); + set_number_from_of_the_way(x2, *t, p->right_x, q->left_x); + set_number_from_of_the_way(x3, *t, q->left_x, q->x_coord); + } else { + set_number_from_of_the_way(x1, *t, p->y_coord, p->right_y); + set_number_from_of_the_way(x2, *t, p->right_y, q->left_y); + set_number_from_of_the_way(x3, *t, q->left_y, q->y_coord); + } + set_number_from_of_the_way(x1, *t, x1, x2); + set_number_from_of_the_way(x2, *t, x2, x3); + set_number_from_of_the_way(*r, *t, x1, x2); + free_number(x1); + free_number(x2); + free_number(x3); +} + +@ 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, /* 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...@>= +/* the result of procedures that compute bounding box information */ +mp_number bbmin[mp_y_code + 1]; +mp_number bbmax[mp_y_code + 1]; + +@ @<Initialize table ...@>= +for (int i = 0; i <= mp_y_code; i++) { + new_number(mp->bbmin[i]); + new_number(mp->bbmax[i]); +} + +@ @<Dealloc...@>= +for (int i = 0; i <= mp_y_code; i++) { + free_number(mp->bbmin[i]); + free_number(mp->bbmax[i]); +} + +@ 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, int c) +{ + int wavy; /* whether we need to look for extremes */ + mp_number del1, del2, del3, del, dmax; /* proportional to the control points of a quadratic derived from a cubic */ + mp_number t, tt; /* where a quadratic crosses zero */ + mp_number x; /* a value that |bbmin[c]| and |bbmax[c]| must accommodate */ + new_fraction(t); + new_fraction(tt); + if (c == mp_x_code) { + new_number_clone(x, q->x_coord); + } else { + new_number_clone(x, q->y_coord); + } + new_number(del1); + new_number(del2); + new_number(del3); + new_number(del); + new_number(dmax); + @<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@> + @<Check the control points against the bounding box and set |wavy:=1| if any of them lie outside@> + if (wavy) { + if (c == mp_x_code) { + set_number_from_subtraction(del1, p->right_x, p->x_coord); + set_number_from_subtraction(del2, q->left_x, p->right_x); + set_number_from_subtraction(del3, q->x_coord, q->left_x); + } else { + set_number_from_subtraction(del1, p->right_y, p->y_coord); + set_number_from_subtraction(del2, q->left_y, p->right_y); + set_number_from_subtraction(del3, q->y_coord, q->left_y); + } + @<Scale up |del1|, |del2|, and |del3| for greater accuracy; also set |del| to the first nonzero element of |(del1,del2,del3)|@> + if (number_negative(del)) { + number_negate(del1); + number_negate(del2); + number_negate(del3); + } + crossing_point(t, del1, del2, del3); + if (number_less(t, fraction_one_t)) { + @<Test the extremes of the cubic against the bounding box@> + } + } + free_number(del3); + free_number(del2); + free_number(del1); + free_number(del); + free_number(dmax); + free_number(x); + free_number(t); + free_number(tt); +} + +@ @<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@>= +if (number_less(x, mp->bbmin[c])) { + number_clone(mp->bbmin[c], x); +} +if (number_greater(x, mp->bbmax[c])) { + number_clone(mp->bbmax[c], x); +} + +@ @<Check the control points against the bounding box and set...@>= +wavy = 1; +if (c == mp_x_code) { + if (number_lessequal(mp->bbmin[c], p->right_x) && number_lessequal(p->right_x, mp->bbmax[c])) { + if (number_lessequal(mp->bbmin[c], q->left_x) && number_lessequal(q->left_x, mp->bbmax[c])) { + wavy = 0; + } + } +} else { + if (number_lessequal(mp->bbmin[c], p->right_y) && number_lessequal(p->right_y, mp->bbmax[c])) { + if (number_lessequal(mp->bbmin[c], q->left_y) && number_lessequal(q->left_y, mp->bbmax[c])) { + wavy = 0; + } + } +} + +@ 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 (number_nonzero(del1)) { + number_clone(del, del1); +} else if (number_nonzero(del2)) { + number_clone(del, del2); +} else { + number_clone(del, del3); +} +if (number_nonzero(del)) { + mp_number absval1; + new_number(absval1); + number_abs_clone(dmax, del1); + number_abs_clone(absval1, del2); + if (number_greater(absval1, dmax)) { + number_clone(dmax, absval1); + } + number_abs_clone(absval1, del3); + if (number_greater(absval1, dmax)) { + number_clone(dmax, absval1); + } + while (number_less(dmax, fraction_half_t)) { + number_double(dmax); + number_double(del1); + number_double(del2); + number_double(del3); + } + free_number(absval1); +} + +@ 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@>= +{ + mp_eval_cubic(mp, &x, p, q, c, &t); + @<Adjust |bbmin[c]| and |bbmax[c]| to accommodate |x|@> + set_number_from_of_the_way(del2, t, del2, del3); + /* now |0,del2,del3| represent the derivative on the remaining interval */ + if (number_positive(del2)) { + set_number_to_zero(del2); + } + { + mp_number arg2, arg3; + new_number(arg2); + new_number(arg3); + number_negated_clone(arg2, del2); + number_negated_clone(arg3, del3); + crossing_point(tt, zero_t, arg2, arg3); + free_number(arg2); + free_number(arg3); + } + if (number_less(tt, fraction_one_t)) { + /* Test the second extreme against the bounding box. */ + mp_number arg; + new_number(arg); + set_number_from_of_the_way(arg, t, tt, fraction_one_t); + mp_eval_cubic(mp, &x, p, q, c, &arg); + free_number(arg); + @<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 = h; + number_clone(mp_minx, h->x_coord); + number_clone(mp_miny, h->y_coord); + number_clone(mp_maxx, mp_minx); + number_clone(mp_maxy, mp_miny); + do { + if (mp_right_type(p) == mp_endpoint_knot) { + return; + } else { + mp_knot 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); +} + +static void mp_path_xbox (MP mp, mp_knot h) +{ + mp_knot p = h; + number_clone(mp_minx, h->x_coord); + number_clone(mp_maxx, mp_minx); + set_number_to_zero(mp_miny); + set_number_to_zero(mp_maxy); + do { + if (mp_right_type(p) == mp_endpoint_knot) { + return; + } else { + mp_knot q = mp_next_knot(p); + mp_bound_cubic(mp, p, q, mp_x_code); + p = q; + } + } while (p != h); +} + +static void mp_path_ybox (MP mp, mp_knot h) +{ + mp_knot p = h; + set_number_to_zero(mp_minx); + set_number_to_zero(mp_maxx); + number_clone(mp_miny, h->y_coord); + number_clone(mp_maxy, mp_miny); + do { + if (mp_right_type(p) == mp_endpoint_knot) { + return; + } else { + mp_knot q = mp_next_knot(p); + 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 \quote {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 \quote {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 +\quote {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 void mp_arc_test (MP mp, + mp_number *ret, mp_number *dx0, mp_number *dy0, mp_number *dx1, + mp_number *dy1, mp_number *dx2, mp_number *dy2, mp_number *v0, + mp_number *v02, mp_number *v2, mp_number *a_goal, mp_number *tol_orig +) +{ + int simple; /* are the control points confined to a $90^\circ$ sector? */ + mp_number dx01, dy01, dx12, dy12, dx02, dy02; /* bisection results */ + mp_number v002, v022; /* twice the velocity magnitudes at $t={1\over4}$ and $t={3\over4}$ */ + mp_number arc; /* best arc length estimate before recursion */ + mp_number arc1; /* arc length estimate for the first half */ + mp_number simply; + mp_number tol; + new_number(arc ); + new_number(arc1); + new_number(dx01); + new_number(dy01); + new_number(dx12); + new_number(dy12); + new_number(dx02); + new_number(dy02); + new_number(v002); + new_number(v022); + new_number(simply); + new_number_clone(tol, *tol_orig); + @<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@> + set_number_half_from_addition(simply, *v0, *v2); + number_negate(simply); + number_add(simply, arc); + number_subtract(simply, *v02); + number_abs(simply); + if (simple && number_lessequal(simply, tol)) { + if (number_less(arc, *a_goal)){ + number_clone(*ret, 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@> + } + DONE: + free_number(arc); + free_number(arc1); + free_number(dx01); + free_number(dy01); + free_number(dx12); + free_number(dy12); + free_number(dx02); + free_number(dy02); + free_number(v002); + free_number(v022); + free_number(simply); + free_number(tol); +} + +@ 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@>= +mp_number a_new, a_aux; /* the sum of these gives the |a_goal| */ +mp_number a, b; /* results of recursive calls */ +mp_number half_v02; /* |half(v02)|, a recursion argument */ +new_number(a_new); +new_number(a_aux); +new_number(half_v02); +@<Set |a_new| and |a_aux| so their sum is |2*a_goal| and |a_new| is as large as possible@> +{ + mp_number half_tol; + new_number_clone(half_tol, tol); + number_half(half_tol); + number_add(tol, half_tol); + free_number(half_tol); +} +number_clone(half_v02, *v02); +number_half(half_v02); +new_number(a); +mp_arc_test(mp, &a, dx0, dy0, &dx01, &dy01, &dx02, &dy02, v0, &v002, &half_v02, &a_new, &tol); +if (number_negative(a)) { + set_number_to_unity(*ret); + number_double(*ret); /* two */ + number_subtract(*ret, a); /* two - a */ + number_half(*ret); + number_negate(*ret); /* -half(two - a) */ +} else { + @<Update |a_new| to reduce |a_new+a_aux| by |a|@> + new_number(b); + mp_arc_test(mp, &b, &dx02, &dy02, &dx12, &dy12, dx2, dy2, &half_v02, &v022, v2, &a_new, &tol); + if (number_negative(b)) { + mp_number tmp ; + new_number(tmp); + number_negated_clone(tmp, b); + number_half(tmp); + number_negate(tmp); + number_clone(*ret, tmp); + set_number_to_unity(tmp); + number_half(tmp); + number_subtract(*ret, tmp); /* (-(half(-b)) - 1/2) */ + free_number(tmp); + } else { + set_number_from_subtraction(*ret, b, a); + number_half(*ret); + set_number_from_addition(*ret, a, *ret); /* (a + half(b - a)) */ + } + free_number(b); +} +free_number(half_v02); +free_number(a_aux); +free_number(a_new); +free_number(a); + +@ @<Set |a_new| and |a_aux| so their sum is |2*a_goal| and |a_new| is...@>= +set_number_to_inf(a_aux); +number_subtract(a_aux, *a_goal); +if (number_greater(*a_goal, a_aux)) { + set_number_from_subtraction(a_aux, *a_goal, a_aux); + set_number_to_inf(a_new); +} else { + set_number_from_addition(a_new, *a_goal, *a_goal); + set_number_to_zero(a_aux); +} + +@ 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 (number_greater(a, a_aux)) { + number_subtract(a_aux, a); + number_add(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|,...@>= +set_number_half_from_addition(dx01, *dx0, *dx1); +set_number_half_from_addition(dx12, *dx1, *dx2); +set_number_half_from_addition(dx02, dx01, dx12); +set_number_half_from_addition(dy01, *dy0, *dy1); +set_number_half_from_addition(dy12, *dy1, *dy2); +set_number_half_from_addition(dy02, 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|;...@>= +{ + mp_number tmp, arg1, arg2 ; + new_number(tmp); + new_number(arg1); + new_number(arg2); + set_number_half_from_addition(arg1, *dx0, dx02); + number_add(arg1, dx01); + set_number_half_from_addition(arg2, *dy0, dy02); + number_add(arg2, dy01); + pyth_add(v002, arg1, arg2); + + set_number_half_from_addition(arg1, dx02, *dx2); + number_add(arg1, dx12); + set_number_half_from_addition(arg2, dy02, *dy2); + number_add(arg2, dy12); + pyth_add(v022, arg1, arg2); + free_number(arg1); + free_number(arg2); + + number_clone(tmp, *v02); + number_add_scaled(tmp, 2); + number_half(tmp); + + set_number_half_from_addition(arc1, *v0, tmp); + number_subtract(arc1, v002); + number_half(arc1); + set_number_from_addition(arc1, v002, arc1); + + set_number_half_from_addition(arc, *v2, tmp); + number_subtract(arc, v022); + number_half(arc); + set_number_from_addition(arc, v022, arc); + + /* reuse |tmp| for the next |if| test: */ + set_number_to_inf(tmp); + number_subtract(tmp,arc1); + if (number_less(arc, tmp)) { + free_number(tmp); + number_add(arc, arc1); + } else { + free_number(tmp); + mp->arith_error = 1; + if (number_infinite(*a_goal)) { + set_number_to_inf(*ret); + } else { + set_number_to_unity(*ret); + number_double(*ret); + number_negate(*ret); /* -two */ + } + goto DONE; + } +} + +@ @<Test if the control points are confined to one quadrant or rotating...@>= +simple = (number_nonnegative(*dx0) && number_nonnegative(*dx1) && number_nonnegative(*dx2)) + || (number_nonpositive(*dx0) && number_nonpositive(*dx1) && number_nonpositive(*dx2)); +if (simple) { + simple = (number_nonnegative(*dy0) && number_nonnegative(*dy1) && number_nonnegative(*dy2)) + || (number_nonpositive(*dy0) && number_nonpositive(*dy1) && number_nonpositive(*dy2)); +} +if (!simple) { + simple = (number_greaterequal(*dx0, *dy0) && number_greaterequal(*dx1, *dy1) && number_greaterequal(*dx2, *dy2)) + || (number_lessequal (*dx0, *dy0) && number_lessequal (*dx1, *dy1) && number_lessequal (*dx2, *dy2)); + if (simple) { + mp_number neg_dx0, neg_dx1, neg_dx2; + new_number(neg_dx0); + new_number(neg_dx1); + new_number(neg_dx2); + number_negated_clone(neg_dx0, *dx0); + number_negated_clone(neg_dx1, *dx1); + number_negated_clone(neg_dx2, *dx2); + simple = (number_greaterequal(neg_dx0, *dy0) && number_greaterequal(neg_dx1, *dy1) && number_greaterequal(neg_dx2, *dy2)) + || (number_lessequal (neg_dx0, *dy0) && number_lessequal (neg_dx1, *dy1) && number_lessequal (neg_dx2, *dy2)); + free_number(neg_dx0); + free_number(neg_dx1); + free_number(neg_dx2); + } +} + +@ 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...@>= +mp_number tmp; +mp_number tmp2; +mp_number tmp3; +mp_number tmp4; +mp_number tmp5; +new_number_clone(tmp, *v02); +new_number(tmp2); +new_number(tmp3); +new_number(tmp4); +new_number(tmp5); +number_add_scaled(tmp, 2); +number_half(tmp); +number_half(tmp); /* (v02+2) / 4 */ +if (number_lessequal(*a_goal, arc1)) { + number_clone(tmp2, *v0); + number_half(tmp2); + set_number_from_subtraction(tmp3, arc1, tmp2); + number_subtract(tmp3, tmp); + mp_solve_rising_cubic(mp, &tmp5, &tmp2, &tmp3, &tmp, a_goal); + number_half(tmp5); + set_number_to_unity(tmp3); + number_subtract(tmp5, tmp3); + number_subtract(tmp5, tmp3); + number_clone(*ret, tmp5); +} else { + number_clone(tmp2, *v2); + number_half(tmp2); + set_number_from_subtraction(tmp3, arc, arc1); + number_subtract(tmp3, tmp); + number_subtract(tmp3, tmp2); + set_number_from_subtraction(tmp4, *a_goal, arc1); + mp_solve_rising_cubic(mp, &tmp5, &tmp, &tmp3, &tmp2, &tmp4); + number_half(tmp5); + set_number_to_unity(tmp2); + set_number_to_unity(tmp3); + number_half(tmp2); + number_subtract(tmp2, tmp3); + number_subtract(tmp2, tmp3); + set_number_from_addition(*ret, tmp2, tmp5); +} +free_number(tmp); +free_number(tmp2); +free_number(tmp3); +free_number(tmp4); +free_number(tmp5); + +@ 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 void mp_solve_rising_cubic (MP mp, mp_number *ret, mp_number *a_orig, mp_number *b_orig, mp_number *c_orig, mp_number *x_orig); + +@ @c +void mp_solve_rising_cubic (MP mp, mp_number *ret, mp_number *a_orig, mp_number *b_orig, mp_number *c_orig, mp_number *x_orig) +{ + mp_number abc; + mp_number a, b, c, x; /* local versions of arguments */ + mp_number ab, bc, ac; /* bisection results */ + mp_number t; /* $2^k+q$ where unscaled answer is in $[q2^{-k},(q+1)2^{-k})$ */ + mp_number xx; /* temporary for updating |x| */ + mp_number neg_x; /* temporary for an |if| */ + if (number_negative(*a_orig) || number_negative(*c_orig)) { + mp_confusion(mp, "rising cubic"); + @:this can't happen rising?}{\quad rising?@> + } + new_number(t); + new_number(abc); + new_number_clone(a, *a_orig); + new_number_clone(b, *b_orig); + new_number_clone(c, *c_orig); + new_number_clone(x, *x_orig); + new_number(ab); + new_number(bc); + new_number(ac); + new_number(xx); + new_number(neg_x); + set_number_from_addition(abc, a, b); + number_add(abc, c); + if (number_nonpositive(x)) { + set_number_to_zero(*ret); + } else if (number_greaterequal(x, abc)) { + set_number_to_unity(*ret); + } else { + number_clone(t, epsilon_t); + @<Rescale if necessary to make sure |a|, |b|, and |c| are all less than |EL_GORDO div 3|@> + do { + number_add(t, t); + @<Subdivide the B\'ezier quadratic defined by |a|, |b|, |c|@> + number_clone(xx,x); + number_subtract(xx, a); + number_subtract(xx, ab); + number_subtract(xx, ac); + number_negated_clone(neg_x, x); + if (number_less(xx, neg_x)) { + number_double(x); + number_clone(b, ab); + number_clone(c, ac); + } else { + number_add(x, xx); + number_clone(a, ac); + number_clone(b, bc); + number_add(t, epsilon_t); + } + } while (number_less(t, unity_t)); + set_number_from_subtraction(*ret, t, unity_t); + } + free_number(abc); + free_number(t); + free_number(a); + free_number(b); + free_number(c); + free_number(ab); + free_number(bc); + free_number(ac); + free_number(xx); + free_number(x); + free_number(neg_x); +} + +@ @<Subdivide the B\'ezier quadratic defined by |a|, |b|, |c|@>= +set_number_half_from_addition(ab, a, b); +set_number_half_from_addition(bc, b, c); +set_number_half_from_addition(ac, ab, bc); + +@ The upper bound on |a|, |b|, and |c|: + +@d one_third_inf_t mp->math->md_one_third_inf_t + +@<Rescale if necessary to make sure |a|, |b|, and |c| are all less than...@>= +while (number_greater(a, one_third_inf_t) || number_greater(b, one_third_inf_t) || number_greater(c, one_third_inf_t)) { + number_half(a); + number_half(b); + number_half(c); + number_half(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|. + +@c +static void mp_do_arc_test (MP mp, + mp_number *ret, mp_number *dx0, mp_number *dy0, mp_number *dx1, + mp_number *dy1, mp_number *dx2, mp_number *dy2, mp_number *a_goal +) +{ + mp_number v0, v1, v2; /* length of each $({\it dx},{\it dy})$ pair */ + mp_number v02; /* twice the norm of the quadratic at $t={1\over2}$ */ + new_number(v0); + new_number(v1); + new_number(v2); + pyth_add(v0, *dx0, *dy0); + pyth_add(v1, *dx1, *dy1); + pyth_add(v2, *dx2, *dy2); + if ((number_greaterequal(v0, fraction_four_t)) || (number_greaterequal(v1, fraction_four_t)) || (number_greaterequal(v2, fraction_four_t))) { + mp->arith_error = 1; + if (number_infinite(*a_goal)) { + set_number_to_inf(*ret); + } else { + set_number_to_unity(*ret); + number_double(*ret); + number_negate(*ret); + } + } else { + mp_number arg1, arg2; + new_number(v02); + new_number(arg1); + new_number(arg2); + set_number_half_from_addition(arg1, *dx0, *dx2); + number_add(arg1, *dx1); + set_number_half_from_addition(arg2, *dy0, *dy2); + number_add(arg2, *dy1); + pyth_add(v02, arg1, arg2); + free_number(arg1); + free_number(arg2); + mp_arc_test(mp, ret, dx0, dy0, dx1, dy1, dx2, dy2, &v0, &v02, &v2, a_goal, &arc_tol_k); + free_number(v02); + } + free_number(v0); + free_number(v1); + free_number(v2); +} + +@ Now it is easy to find the arc length of an entire path. + +@c +static void mp_get_arc_length (MP mp, mp_number *ret, mp_knot h) +{ + mp_number a; /* current arc length */ + mp_number a_tot; /* total arc length */ + mp_number arg1, arg2, arg3, arg4, arg5, arg6; + mp_number arcgoal; + mp_knot p = h; /* for traversing the path */ + new_number(a_tot); + new_number(arg1); + new_number(arg2); + new_number(arg3); + new_number(arg4); + new_number(arg5); + new_number(arg6); + new_number(a); + new_number(arcgoal); + set_number_to_inf(arcgoal); + while (mp_right_type(p) != mp_endpoint_knot) { + mp_knot q = mp_next_knot(p); + @<Add arclength of path segment@> + if (q == h) { + break; + } else { + p = q; + } + } + free_number(arcgoal); + free_number(a); + free_number(arg1); + free_number(arg2); + free_number(arg3); + free_number(arg4); + free_number(arg5); + free_number(arg6); + check_arith(); + number_clone(*ret, a_tot); + free_number(a_tot); +} + +static void mp_get_subarc_length (MP mp, mp_number *ret, mp_knot h, mp_number *first, mp_number *last) +{ + mp_number a; + mp_number a_tot, a_cnt; + mp_number arg1, arg2, arg3, arg4, arg5, arg6; + mp_number arcgoal; + mp_knot p = h; + new_number(a_tot); + new_number(a_cnt); + new_number(arg1); + new_number(arg2); + new_number(arg3); + new_number(arg4); + new_number(arg5); + new_number(arg6); + new_number(a); + new_number(arcgoal); + set_number_to_inf(arcgoal); + while (mp_right_type(p) != mp_endpoint_knot) { + mp_knot q = mp_next_knot(p); + if (number_greaterequal(a_cnt, *last)) { + break; + } else if (number_greaterequal(a_cnt, *first)) { + @<Add arclength of path segment@> + } + if (q == h) { + break; + } else { + p = q; + number_add(a_cnt, unity_t); + } + } + free_number(arcgoal); + free_number(a); + free_number(arg1); + free_number(arg2); + free_number(arg3); + free_number(arg4); + free_number(arg5); + free_number(arg6); + check_arith(); + number_clone(*ret, a_tot); + free_number(a_cnt); + free_number(a_tot); +} + +@<Add arclength of path segment@>= +set_number_from_subtraction(arg1, p->right_x, p->x_coord); +set_number_from_subtraction(arg2, p->right_y, p->y_coord); +set_number_from_subtraction(arg3, q->left_x, p->right_x); +set_number_from_subtraction(arg4, q->left_y, p->right_y); +set_number_from_subtraction(arg5, q->x_coord, q->left_x); +set_number_from_subtraction(arg6, q->y_coord, q->left_y); +mp_do_arc_test(mp, &a, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &arcgoal); +slow_add(a_tot, a, 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 mp_knot mp_get_arc_time(MP mp, mp_number *ret, mp_knot h, mp_number *arc0_orig, int local) +{ + if (number_negative(*arc0_orig)) { + @<Deal with a negative |arc0_orig| value and |return|@> + } else { + mp_knot p, q, k; /* for traversing the path */ + mp_number t_tot; /* accumulator for the result */ + mp_number t; /* the result of |do_arc_test| */ + mp_number arc, arc0; /* portion of |arc0| not used up so far */ + mp_number arg1, arg2, arg3, arg4, arg5, arg6; /* |do_arc_test| arguments */ + new_number(t_tot); + new_number_clone(arc0, *arc0_orig); + if (number_infinite(arc0)) { + number_add_scaled(arc0, -1); + } + new_number_clone(arc, arc0); + p = h; + k = h; + new_number(arg1); + new_number(arg2); + new_number(arg3); + new_number(arg4); + new_number(arg5); + new_number(arg6); + new_number(t); + while ((mp_right_type(p) != mp_endpoint_knot) && number_positive(arc)) { + k = p; + q = mp_next_knot(p); + set_number_from_subtraction(arg1, p->right_x, p->x_coord); + set_number_from_subtraction(arg2, p->right_y, p->y_coord); + set_number_from_subtraction(arg3, q->left_x, p->right_x); + set_number_from_subtraction(arg4, q->left_y, p->right_y); + set_number_from_subtraction(arg5, q->x_coord, q->left_x); + set_number_from_subtraction(arg6, q->y_coord, q->left_y); + mp_do_arc_test(mp, &t, &arg1, &arg2, &arg3, &arg4, &arg5, &arg6, &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:=1| and |goto done| on overflow@> + } + p = q; + } + check_arith(); + if (local) { + number_add(t, two_t); + number_clone(*ret, t); + } else { + number_clone(*ret, t_tot); + } + h = k; + RETURN: + free_number(t_tot); + free_number(t); + free_number(arc); + free_number(arc0); + free_number(arg1); + free_number(arg2); + free_number(arg3); + free_number(arg4); + free_number(arg5); + free_number(arg6); + } + return h; +} + +@ @<Update |arc| and |t_tot| after |do_arc_test| has just returned |t|@>= +if (number_negative(t)) { + number_add(t_tot, t); + number_add(t_tot, two_t); + set_number_to_zero(arc); +} else { + number_add(t_tot, unity_t); + number_subtract(arc, t); +} + +@ @<Deal with a negative |arc0_orig| value and |return|@>= +if (mp_left_type(h) == mp_endpoint_knot) { + set_number_to_zero(*ret); +} else { + mp_number neg_arc0; + mp_knot p = mp_htap_ypoc(mp, h); + new_number(neg_arc0); + number_negated_clone(neg_arc0, *arc0_orig); + mp_get_arc_time(mp, ret, p, &neg_arc0, 0); + number_negate(*ret); + mp_toss_knot_list(mp, p); + free_number(neg_arc0); +} +check_arith(); + +@ @<Update |t_tot| and |arc| to avoid going around the cyclic...@>= +if (number_positive(arc)) { + mp_number n, n1, d1, v1; + new_number(n); + new_number(n1); + new_number(d1); + new_number(v1); + + set_number_from_subtraction(d1, arc0, arc); /* d1 = arc0 - arc */ + set_number_from_div(n1, arc, d1); /* n1 = (arc / d1) */ + number_clone(n, n1); + set_number_from_mul(n1, n1, d1); /* n1 = (n1 * d1) */ + number_subtract(arc, n1); /* arc = arc - n1 */ + + number_clone(d1, inf_t); /* reuse d1 */ + number_clone(v1, n); /* v1 = n */ + number_add(v1, epsilon_t); /* v1 = n1+1 */ + set_number_from_div(d1, d1, v1); /* |d1 = EL_GORDO / v1| */ + if (number_greater(t_tot, d1)) { + mp->arith_error = 1; + check_arith(); + set_number_to_inf(*ret); + free_number(n); + free_number(n1); + free_number(d1); + free_number(v1); + goto RETURN; + } + set_number_from_mul(t_tot, t_tot, v1); + free_number(n); + free_number(n1); + free_number(d1); + free_number(v1); +} + +@* 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 [\quote {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 |left_x|, +|left_y|, |right_x|, or |right_y| fields either but we leave these alone so that +certain procedures can operate on both pens and paths. In particular, pens can be +copied using |copy_path| and recycled using |toss_knot_list|. + +@ 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 mp_copy_pen(mp,A) mp_make_pen(mp, mp_copy_path(mp, (A)),0) + +@c +static mp_knot mp_make_pen (MP mp, mp_knot h, int need_hull) +{ + mp_knot q = h; + /* this can go ... we are already double linked */ + do { + mp_knot 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 mp_pen_is_elliptical(A) ((A)==mp_next_knot((A))) + +@ @c +static mp_knot mp_get_pen_circle (MP mp, mp_number *diam) +{ + mp_knot h = mp_new_knot(mp); /* the knot node to return */ + mp_next_knot(h) = h; + mp_prev_knot(h) = h; + mp_originator(h) = mp_program_code; + mp_knotstate(h) = mp_regular_knot; + set_number_to_zero(h->x_coord); + set_number_to_zero(h->y_coord); + number_clone(h->left_x, *diam); + set_number_to_zero(h->left_y); + set_number_to_zero(h->right_x); + number_clone(h->right_y, *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 |left_x|, |left_y|, |right_x|, and |right_y| fields. + + +@<Make sure |h| isn't confused with an elliptical pen@>= +if (mp_pen_is_elliptical(h)) { + number_clone(h->left_x, h->x_coord); + number_clone(h->left_y, h->y_coord); + number_clone(h->right_x, h->x_coord); + number_clone(h->right_y, h->y_coord); +} + +@ 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) +{ + if (mp_pen_is_elliptical(h)) { + @<Print the elliptical pen |h|@> + } else { + mp_knot p = h; + do { + /* Advance |p| making sure the links are OK and |return| if there is a problem. */ + mp_knot q = mp_next_knot(p); + mp_print_two(mp, &(p->x_coord), &(p->y_coord)); + mp_print_nl(mp, " .. "); + if ((q == NULL) || (mp_prev_knot(q) != p)) { + mp_print_nl(mp, "???"); + return; /* this won't happen */ + @.???@> + } + p = q; + } while (p != h); + mp_print_str(mp, "cycle"); + } +} + +@ @<Print the elliptical pen |h|@>= +{ + mp_number v1; + new_number(v1); + mp_print_str(mp, "pencircle transformed ("); + print_number(h->x_coord); + mp_print_chr(mp, ','); + print_number(h->y_coord); + mp_print_chr(mp, ','); + set_number_from_subtraction(v1, h->left_x, h->x_coord); + print_number(v1); + mp_print_chr(mp, ','); + set_number_from_subtraction(v1, h->right_x, h->x_coord); + print_number(v1); + mp_print_chr(mp, ','); + set_number_from_subtraction(v1, h->left_y, h->y_coord); + print_number(v1); + mp_print_chr(mp, ','); + set_number_from_subtraction(v1, h->right_y, h->y_coord); + print_number(v1); + mp_print_chr(mp, ')'); + free_number(v1); +} + +@ 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, int nuline); + +@ @c +void mp_print_pen (MP mp, mp_knot h, const char *s, int nuline) { + mp_print_diagnostic(mp, "Pen", s, nuline); + mp_print_ln(mp); + @.Pen at line...@> + mp_pr_pen(mp, h); + mp_end_diagnostic(mp, 1); +} + +@ 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) +{ + if (mp_pen_is_elliptical(h)) { + @<Make the elliptical pen |h| into a path@> + } else { + mp_knot p = h; + do { + mp_left_type(p) = mp_explicit_knot; + mp_right_type(p) = mp_explicit_knot; + number_clone(p->left_x, p->x_coord); + number_clone(p->left_y, p->y_coord); + number_clone(p->right_x, p->x_coord); + number_clone(p->right_y, p->y_coord); + p = mp_next_knot(p); + } while (p != h); + } +} + +@ We need an eight knot path to get a good approximation to an ellipse. + +@<Make the elliptical pen |h| into a path@>= +mp_knot p; /* for traversing the knot list */ +mp_number center_x, center_y; /* translation parameters for an elliptical pen */ +mp_number width_x, width_y; /* the effect of a unit change in $x$ */ +mp_number height_x, height_y; /* the effect of a unit change in $y$ */ +mp_number dx, dy; /* the vector from knot |p| to its right control point */ +new_number(width_x); +new_number(width_y); +new_number(height_x); +new_number(height_y); +new_number(dx); +new_number(dy); +new_number_clone(center_x, h->x_coord); +new_number_clone(center_y, h->y_coord); +set_number_from_subtraction(width_x, h->left_x, center_x); +set_number_from_subtraction(width_y, h->left_y, center_y); +set_number_from_subtraction(height_x, h->right_x, center_x); +set_number_from_subtraction(height_y, h->right_y, center_y); +p = h; +for (int 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_prev_knot(h) = p; + mp_next_knot(p) = h; + } else { + mp_knot k = mp_new_knot(mp); + mp_prev_knot(k) = p; + mp_next_knot(p) = k; + } + p = mp_next_knot(p); +} +free_number(dx); +free_number(dy); +free_number(center_x); +free_number(center_y); +free_number(width_x); +free_number(width_y); +free_number(height_x); +free_number(height_y); + +@ 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. With |kk| we track |k| advancing $270^\circ$ around the ring (cf. $\sin +\theta = \cos (\theta+270)$). + +@<Initialize |p| as the |k|th knot of a circle of unit diameter,...@>= +/* This is the body of a loop with variable k: */ +int kk = (k + 6) % 8; +mp_number r1, r2; +new_fraction(r1); +new_fraction(r2); +take_fraction(r1, mp->half_cos[k], width_x); +take_fraction(r2, mp->half_cos[kk], height_x); +number_add(r1, r2); +set_number_from_addition(p->x_coord, center_x, r1); +take_fraction(r1, mp->half_cos[k], width_y); +take_fraction(r2, mp->half_cos[kk], height_y); +number_add(r1, r2); +set_number_from_addition(p->y_coord, center_y, r1); +take_fraction(r1, mp->d_cos[kk], width_x); +take_fraction(r2, mp->d_cos[k], height_x); +number_negated_clone(dx, r1); +number_add(dx, r2); +take_fraction(r1, mp->d_cos[kk], width_y); +take_fraction(r2, mp->d_cos[k], height_y); +number_negated_clone(dy, r1); +number_add(dy, r2); +set_number_from_addition(p->right_x, p->x_coord, dx); +set_number_from_addition(p->right_y, p->y_coord, dy); +set_number_from_subtraction(p->left_x, p->x_coord, dx); +set_number_from_subtraction(p->left_y, p->y_coord, dy); +free_number(r1); +free_number(r2); +mp_left_type(p) = mp_explicit_knot; +mp_right_type(p) = mp_explicit_knot; +mp_originator(p) = mp_program_code; +mp_knotstate(p) = mp_regular_knot; + +@ @<Glob...@>= +mp_number half_cos[8]; /* ${1\over2}\cos(45k)$ */ +mp_number 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...@>= +for (int k = 0; k <= 7; k++) { + new_fraction(mp->half_cos[k]); + new_fraction(mp->d_cos[k]); +} + +number_clone(mp->half_cos[0], fraction_half_t); +number_clone(mp->half_cos[1], twentysixbits_sqrt2_t); +number_clone(mp->half_cos[2], zero_t); +number_clone(mp->d_cos[0], twentyeightbits_d_t); +number_clone(mp->d_cos[1], twentysevenbits_sqrt2_d_t); +number_clone(mp->d_cos[2], zero_t); + +for (int k = 3; k <= 4; k++) { + number_negated_clone(mp->half_cos[k], mp->half_cos[4 - k]); + number_negated_clone(mp->d_cos[k], mp->d_cos[4 - k]); +} + +for (int k = 5; k <= 7; k++) { + number_clone(mp->half_cos[k], mp->half_cos[8 - k]); + number_clone(mp->d_cos[k], mp->d_cos[8 - k]); +} + +@ @<Dealloc...@>= +for (int k = 0; k <= 7; k++) { + free_number(mp->half_cos[k]); + free_number(mp->d_cos[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) +{ + if (mp_pen_is_elliptical(h)) { + return h; + } else { + /* 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 */ + mp_number dx, dy; /* a temporary pointer */ + new_number(dx); + new_number(dy); + @<Set |l| to the leftmost knot in polygon~|h|@> + @<Set |r| to the rightmost knot in polygon~|h|@> + if (l != r) { + mp_knot 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@> + } + free_number(dx); + free_number(dy); + return l; + } +} + +@<Declarations@>= +void mp_simplify_path (MP mp, mp_knot h); + +@ @c +void mp_simplify_path (MP mp, mp_knot h) +{ + mp_knot p = h; + (void) mp; + do { + p->left_x = p->x_coord; + p->left_y = p->y_coord; + p->right_x = p->x_coord; + p->right_y = p->y_coord; + p = mp_next_knot(p); + } while (p != h); +} + +@ 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 (number_lessequal(p->x_coord, l->x_coord) && (number_less(p->x_coord, l->x_coord) || number_less(p->y_coord, l->y_coord))) { + 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 (number_greaterequal(p->x_coord, r->x_coord) && (number_greater(p->x_coord, r->x_coord) || number_greater(p->y_coord, r->y_coord))) { + r = p; + } + p = mp_next_knot(p); +} + +@ @<Find any knots on the path from |l| to |r| above the |l|-|r| line...@>= +{ + mp_number arg1, arg2; + new_number(arg1); + new_number(arg2); + set_number_from_subtraction(dx, r->x_coord, l->x_coord); + set_number_from_subtraction(dy, r->y_coord, l->y_coord); + p = mp_next_knot(l); + while (p != r) { + q = mp_next_knot(p); + set_number_from_subtraction(arg1, p->y_coord, l->y_coord); + set_number_from_subtraction(arg2, p->x_coord, l->x_coord); + if (ab_vs_cd(dx, arg1, dy, arg2) > 0) { + mp_move_knot(mp, p, r); + } + p = q; + } + free_number(arg1); + free_number(arg2); +} + +@ 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...@>= +{ + mp_number arg1, arg2; + new_number(arg1); + new_number(arg2); + p = s; + while (p != l) { + q = mp_next_knot(p); + set_number_from_subtraction(arg1, p->y_coord, l->y_coord); + set_number_from_subtraction(arg2, p->x_coord, l->x_coord); + if (ab_vs_cd(dx, arg1, dy, arg2) < 0) { + mp_move_knot(mp, p, l); + } + p = q; + } + free_number(arg1); + free_number(arg2); +} + +@ 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 (number_greater(q->x_coord, p->x_coord)) { + q = mp_prev_knot(q); + } + while (number_equal(q->x_coord, p->x_coord)) { + if (number_greater(q->y_coord, p->y_coord)) { + 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 (number_less(q->x_coord, p->x_coord)) { + q = mp_prev_knot(q); + } + while (number_equal(q->x_coord, p->x_coord)) { + if (number_less(q->y_coord, p->y_coord)) { + 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...@>= +mp_number arg1, arg2; +new_number(arg1); +new_number(arg2); +p = l; +q = mp_next_knot(l); +while (1) { + set_number_from_subtraction(dx, q->x_coord, p->x_coord); + set_number_from_subtraction(dy, q->y_coord, p->y_coord); + p = q; + q = mp_next_knot(q); + if (p == l) { + break; + } else if (p != r) { + set_number_from_subtraction(arg1, q->y_coord, p->y_coord); + set_number_from_subtraction(arg2, q->x_coord, p->x_coord); + if (ab_vs_cd(dx, arg1, dy, arg2) <= 0) { + /* Remove knot |p| and back up |p| and |q| but don't go past |l|. */ + s = mp_prev_knot(p); + mp_memory_free(p); + mp_next_knot(s) = q; + mp_prev_knot(q) = s; + if (s == l) { + p = s; + } else { + p = mp_prev_knot(s); + q = s; + } + } + } +} +free_number(arg1); +free_number(arg2); + +@ 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, mp_number *x_orig, mp_number *y_orig, mp_knot h) +{ + if (mp_pen_is_elliptical(h)) { + mp_fraction xx, yy; /* untransformed offset for an elliptical pen */ + mp_number wx, wy, hx, hy; /* the transformation matrix for an elliptical pen */ + mp_fraction d; /* a temporary register */ + new_fraction(xx); + new_fraction(yy); + new_number(wx); + new_number(wy); + new_number(hx); + new_number(hy); + new_fraction(d); + @<Find the offset for |(x,y)| on the elliptical pen~|h|@> + free_number(xx); + free_number(yy); + free_number(wx); + free_number(wy); + free_number(hx); + free_number(hy); + free_number(d); + } else { + mp_knot p, q; /* consecutive knots */ + mp_number arg1, arg2; + new_number(arg1); + new_number(arg2); + q = h; + do { + p = q; + q = mp_next_knot(q); + set_number_from_subtraction(arg1, q->x_coord, p->x_coord); + set_number_from_subtraction(arg2, q->y_coord, p->y_coord); + } while (ab_vs_cd(arg1, *y_orig, arg2, *x_orig) < 0); + do { + p = q; + q = mp_next_knot(q); + set_number_from_subtraction(arg1, q->x_coord, p->x_coord); + set_number_from_subtraction(arg2, q->y_coord, p->y_coord); + } while (ab_vs_cd(arg1, *y_orig, arg2, *x_orig) > 0); + number_clone(mp->cur_x, p->x_coord); + number_clone(mp->cur_y, p->y_coord); + free_number(arg1); + free_number(arg2); + } +} + +@ @<Glob...@>= +mp_number cur_x; +mp_number cur_y; /* all-purpose return value registers */ + +@ @<Initialize table entries@>= +new_number(mp->cur_x); +new_number(mp->cur_y); + +@ @<Dealloc...@>= +free_number(mp->cur_x); +free_number(mp->cur_y); + +@ @<Find the offset for |(x,y)| on the elliptical pen~|h|@>= +if (number_zero(*x_orig) && number_zero(*y_orig)) { + number_clone(mp->cur_x, h->x_coord); + number_clone(mp->cur_y, h->y_coord); +} else { + mp_number x, y, abs_x, abs_y; + new_number_clone(x, *x_orig); + new_number_clone(y, *y_orig); + @<Find the non-constant part of the transformation for |h|@> + new_number_abs(abs_x, x); + new_number_abs(abs_y, y); + while (number_less(abs_x, fraction_half_t) && number_less(abs_y, fraction_half_t)) { + number_double(x); + number_double(y); + number_abs_clone(abs_x, x); + number_abs_clone(abs_y, y); + } + @<Make |(xx,yy)| the offset on the untransformed |pencircle| for the untransformed version of |(x,y)|@> + { + mp_number r1, r2; + new_fraction(r1); + new_fraction(r2); + take_fraction(r1, xx, wx); + take_fraction(r2, yy, hx); + number_add(r1, r2); + set_number_from_addition(mp->cur_x, h->x_coord, r1); + take_fraction(r1, xx, wy); + take_fraction(r2, yy, hy); + number_add(r1, r2); + set_number_from_addition(mp->cur_y, h->y_coord, r1); + free_number(r1); + free_number(r2); + } + free_number(abs_x); + free_number(abs_y); + free_number(x); + free_number(y); +} + +@ @<Find the non-constant part of the transformation for |h|@>= +set_number_from_subtraction(wx, h->left_x, h->x_coord); +set_number_from_subtraction(wy, h->left_y, h->y_coord); +set_number_from_subtraction(hx, h->right_x, h->x_coord); +set_number_from_subtraction(hy, h->right_y, h->y_coord); + +@ @<Make |(xx,yy)| the offset on the untransformed |pencircle| for the...@>= +{ + mp_number r1, r2, arg1; + new_number(arg1); + new_fraction(r1); + new_fraction(r2); + take_fraction(r1, x, hy); + number_negated_clone(arg1, hx); + take_fraction(r2, y, arg1); + number_add(r1, r2); + number_negate(r1); + number_clone(yy, r1); + number_negated_clone(arg1, wy); + take_fraction(r1, x, arg1); + take_fraction(r2, y, wx); + number_add(r1, r2); + number_clone(xx, r1); + free_number(arg1); + free_number(r1); + free_number(r2); +} +pyth_add(d, xx, yy); +if (number_positive(d)) { + mp_number ret; + new_fraction(ret); + make_fraction(ret, xx, d); + number_half(ret); + number_clone(xx, ret); + make_fraction(ret, yy, d); + number_half(ret); + number_clone(yy, ret); + free_number(ret); +} + +@ 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) +{ + if (mp_pen_is_elliptical(h)) { + mp_number arg1, arg2; + new_number(arg1); + new_fraction(arg2); + number_clone(arg2, fraction_one_t); + mp_find_offset(mp, &arg1, &arg2, h); + number_clone(mp_maxx, mp->cur_x); + number_clone(mp_minx, h->x_coord); + number_double(mp_minx); + number_subtract(mp_minx, mp->cur_x); + number_negate(arg2); + mp_find_offset(mp, &arg2, &arg1, h); + number_clone(mp_maxy, mp->cur_y); + number_clone(mp_miny, h->y_coord); + number_double(mp_miny); + number_subtract(mp_miny, mp->cur_y); + free_number(arg1); + free_number(arg2); + } else { + mp_knot p = mp_next_knot(h); /* for scanning the knot list */ + number_clone(mp_minx, h->x_coord); + number_clone(mp_maxx, mp_minx); + number_clone(mp_miny, h->y_coord); + number_clone(mp_maxy, mp_miny); + while (p != h) { + if (number_less(p->x_coord, mp_minx)) { + number_clone(mp_minx, p->x_coord); + } + if (number_less(p->y_coord, mp_miny)) { + number_clone(mp_miny, p->y_coord); + } + if (number_greater(p->x_coord, mp_maxx)) { + number_clone(mp_maxx, p->x_coord); + } + if (number_greater(p->y_coord, mp_maxy)) { + number_clone(mp_maxy, p->y_coord); + } + p = mp_next_knot(p); + } + } +} + +@* Numerical values. + +This first set goes into the header + +@<MPlib internal header stuff@>= +@d mp_fraction mp_number +@d mp_angle mp_number + +@d new_number(A) mp->math->md_allocate(mp, &(A), mp_scaled_type) +@d new_fraction(A) mp->math->md_allocate(mp, &(A), mp_fraction_type) +@d new_angle(A) mp->math->md_allocate(mp, &(A), mp_angle_type) + +@d new_number_clone(A,B) mp->math->md_allocate_clone(mp, &(A), mp_scaled_type, &(B)) +@d new_fraction_clone(A,B) mp->math->md_allocate_clone(mp, &(A), mp_fraction_type, &(B)) +@d new_angle_clone(A,B) mp->math->md_allocate_clone(mp, &(A), mp_angle_type, &(B)) + +@d new_number_from_double(mp,A,B) mp->math->md_allocate_double(mp, &(A), B) +@d new_number_abs(A,B) mp->math->md_allocate_abs(mp, &(A), mp_scaled_type, &(B)) + +@d free_number(A) mp->math->md_free(mp, &(A)) + +@d set_precision() mp->math->md_set_precision(mp) +@d free_math() mp->math->md_free_math(mp) +@d scan_numeric_token(A) mp->math->md_scan_numeric(mp,A) +@d scan_fractional_token(A) mp->math->md_scan_fractional(mp,A) +@d set_number_from_of_the_way(A,t,B,C) mp->math->md_from_oftheway(mp,&(A),&(t),&(B),&(C)) +@d set_number_from_int(A,B) mp->math->md_from_int(&(A),B) +@d set_number_from_scaled(A,B) mp->math->md_from_scaled(&(A),B) +@d set_number_from_boolean(A,B) mp->math->md_from_boolean(&(A),B) +@d set_number_from_double(A,B) mp->math->md_from_double(&(A),B) +@d set_number_from_addition(A,B,C) mp->math->md_from_addition(&(A),&(B),&(C)) +@d set_number_half_from_addition(A,B,C) mp->math->md_half_from_addition(&(A),&(B),&(C)) +@d set_number_from_subtraction(A,B,C) mp->math->md_from_subtraction(&(A),&(B),&(C)) +@d set_number_half_from_subtraction(A,B,C) mp->math->md_half_from_subtraction(&(A),&(B),&(C)) +@d set_number_from_div(A,B,C) mp->math->md_from_div(&(A),&(B),&(C)) +@d set_number_from_mul(A,B,C) mp->math->md_from_mul(&(A),&(B),&(C)) +@d number_int_div(A,C) mp->math->md_from_int_div(&(A),&(A),C) +@d set_number_from_int_mul(A,B,C) mp->math->md_from_int_mul(&(A),&(B),C) + +@d set_number_to_unity(A) mp->math->md_clone(&(A), &unity_t) +@d set_number_to_zero(A) mp->math->md_clone(&(A), &zero_t) +@d set_number_to_inf(A) mp->math->md_clone(&(A), &inf_t) +@d set_number_to_negative_inf(A) mp->math->md_clone(&(A), &negative_inf_t) +@d old_set_number_to_neg_inf(A) do { set_number_to_inf(A); number_negate(A); } while (0) + +@d init_randoms(A) mp->math->md_init_randoms(mp,A) +@d print_number(A) mp->math->md_print(mp,&(A)) +@d number_tostring(A) mp->math->md_tostring(mp,&(A)) +@d make_scaled(R,A,B) mp->math->md_make_scaled(mp,&(R),&(A),&(B)) +@d take_scaled(R,A,B) mp->math->md_take_scaled(mp,&(R),&(A),&(B)) +@d make_fraction(R,A,B) mp->math->md_make_fraction(mp,&(R),&(A),&(B)) +@d take_fraction(R,A,B) mp->math->md_take_fraction(mp,&(R),&(A),&(B)) +@d pyth_add(R,A,B) mp->math->md_pyth_add(mp,&(R),&(A),&(B)) +@d pyth_sub(R,A,B) mp->math->md_pyth_sub(mp,&(R),&(A),&(B)) +@d power_of(R,A,B) mp->math->md_power_of(mp,&(R),&(A),&(B)) +@d n_arg(R,A,B) mp->math->md_n_arg(mp,&(R),&(A),&(B)) +@d m_log(R,A) mp->math->md_m_log(mp,&(R),&(A)) +@d m_exp(R,A) mp->math->md_m_exp(mp,&(R),&(A)) +@d m_unif_rand(R,A) mp->math->md_m_unif_rand(mp,&(R),&(A)) +@d m_norm_rand(R) mp->math->md_m_norm_rand(mp,&(R)) +@d velocity(R,A,B,C,D,E) mp->math->md_velocity(mp,&(R),&(A),&(B),&(C),&(D),&(E)) +@d ab_vs_cd(A,B,C,D) mp->math->md_ab_vs_cd(&(A),&(B),&(C),&(D)) +@d crossing_point(R,A,B,C) mp->math->md_crossing_point(mp,&(R),&(A),&(B),&(C)) +@d n_sin_cos(A,S,C) mp->math->md_sin_cos(mp,&(A),&(S),&(C)) +@d square_rt(A,S) mp->math->md_sqrt(mp,&(A),&(S)) +@d slow_add(R,A,B) mp->math->md_slow_add(mp,&(R),&(A),&(B)) +@d round_unscaled(A) mp->math->md_round_unscaled(&(A)) +@d floor_scaled(A) mp->math->md_floor_scaled(&(A)) +@d fraction_to_round_scaled(A) mp->math->md_fraction_to_round_scaled(&(A)) +@d number_to_int(A) mp->math->md_to_int(&(A)) +@d number_to_boolean(A) mp->math->md_to_boolean(&(A)) +@d number_to_scaled(A) mp->math->md_to_scaled(&(A)) +@d number_to_double(A) mp->math->md_to_double(&(A)) +@d number_negate(A) mp->math->md_negate(&(A)) +@d number_add(A,B) mp->math->md_add(&(A),&(B)) +@d number_subtract(A,B) mp->math->md_subtract(&(A),&(B)) +@d number_half(A) mp->math->md_half(&(A)) +@d number_double(A) mp->math->md_do_double(&(A)) +@d number_add_scaled(A,B) mp->math->md_add_scaled(&(A),B) +@d number_multiply_int(A,B) mp->math->md_multiply_int(&(A),B) +@d number_divide_int(A,B) mp->math->md_divide_int(&(A),B) +@d number_abs(A) mp->math->md_abs(&(A)) +@d number_modulo(A,B) mp->math->md_modulo(&(A),&(B)) +@d number_nonequalabs(A,B) mp->math->md_nonequalabs(&(A),&(B)) +@d number_odd(A) mp->math->md_odd(&(A)) +@d number_equal(A,B) mp->math->md_equal(&(A),&(B)) +@d number_greater(A,B) mp->math->md_greater(&(A),&(B)) +@d number_less(A,B) mp->math->md_less(&(A),&(B)) +@d number_clone(A,B) mp->math->md_clone(&(A),&(B)) +@d number_negated_clone(A,B) mp->math->md_negated_clone(&(A),&(B)) +@d number_abs_clone(A,B) mp->math->md_abs_clone(&(A),&(B)) +@d number_swap(A,B) mp->math->md_swap(&(A),&(B)); +@d convert_scaled_to_angle(A) mp->math->md_scaled_to_angle(&(A)); +@d convert_angle_to_scaled(A) mp->math->md_angle_to_scaled(&(A)); +@d convert_fraction_to_scaled(A) mp->math->md_fraction_to_scaled(&(A)); +@d convert_scaled_to_fraction(A) mp->math->md_scaled_to_fraction(&(A)); + +@d number_zero(A) number_equal(A, zero_t) +@d number_infinite(A) number_equal(A, inf_t) +@d number_unity(A) number_equal(A, unity_t) +@d number_negative(A) number_less(A, zero_t) +@d number_nonnegative(A) (! number_negative(A)) +@d number_positive(A) number_greater(A, zero_t) +@d number_nonpositive(A) (! number_positive(A)) +@d number_nonzero(A) (! number_zero(A)) +@d number_greaterequal(A,B) (! number_less(A,B)) +@d number_lessequal(A,B) (! number_greater(A,B)) + +@* 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 header stuff@>= +enum mp_graphical_object_code { + mp_unset_code, + mp_fill_code, + mp_stroked_code, + mp_start_clip_code, /* |type| of a node that starts clipping */ + mp_start_group_code, /* |type| of a node that gives a |setgroup| path */ + mp_start_bounds_code, /* |type| of a node that gives a |setbounds| path */ + mp_stop_clip_code, /* |type| of a node that stops clipping */ + mp_stop_group_code, /* |type| of a node that stops grouping */ + mp_stop_bounds_code, /* |type| of a node that stops |setbounds| */ + 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|, |linejoin| and |miterlimit| give the relevant information. + +We can actually be more sparse: |color_model|, |line_join| and |pen_type| can be +chars: a todo. + +We don't save that much by distinguishing between a stroke and a fill object and +we can save some code when we make then the same. Todo: use char for some. + +@<MPlib internal header stuff@>= +typedef struct mp_shape_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int stacking; + struct mp_node_data *link; + /*common */ + mp_string pre_script; + mp_string post_script; + union { + mp_number red; + mp_number cyan; + }; + union { + mp_number green; + mp_number magenta; + }; + union { + mp_number blue; + mp_number yellow; + }; + union { + mp_number black; + mp_number grey; + }; + /*specific to paths */ + mp_knot path; + mp_knot pen; + mp_node dash; + mp_number dashscale; + mp_number miterlimit; + unsigned char color_model; + unsigned char linejoin; + unsigned char linecap; + unsigned char pen_type; +} mp_shape_node_data; + +typedef struct mp_shape_node_data *mp_shape_node; + +@d mp_path_ptr(A) (A)->path +@d mp_pen_ptr(A) (A)->pen +@d mp_dash_ptr(A) ((mp_shape_node) (A))->dash +@d mp_line_cap(A) ((mp_shape_node) (A))->linecap +@d mp_line_join(A) ((mp_shape_node) (A))->linejoin +@d mp_miterlimit(A) ((mp_shape_node) (A))->miterlimit + +@d mp_set_linecap(A,B) ((mp_shape_node) (A))->linecap = (short) (B) +@d mp_set_linejoin(A,B) ((mp_shape_node) (A))->linejoin = (short) (B) + +@d mp_pre_script(A) ((mp_shape_node) (A))->pre_script +@d mp_post_script(A) ((mp_shape_node) (A))->post_script +@d mp_color_model(A) ((mp_shape_node) (A))->color_model +@d mp_stacking(A) ((mp_shape_node) (A))->stacking +@d mp_pen_type(A) ((mp_shape_node) (A))->pen_type + +@d mp_cyan_color(A) ((mp_shape_node) (A))->cyan +@d mp_magenta_color(A) ((mp_shape_node) (A))->magenta +@d mp_yellow_color(A) ((mp_shape_node) (A))->yellow +@d mp_black_color(A) ((mp_shape_node) (A))->black +@d mp_red_color(A) ((mp_shape_node) (A))->red +@d mp_green_color(A) ((mp_shape_node) (A))->green +@d mp_blue_color(A) ((mp_shape_node) (A))->blue +@d mp_gray_color(A) ((mp_shape_node) (A))->grey +@d mp_grey_color(A) ((mp_shape_node) (A))->grey + +@ Make a shape node. A fill node is a cyclic path |p|. A stroked path is a 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. + +@c +static mp_node mp_new_shape_node (MP mp, mp_knot p, int type) +{ + mp_shape_node t = mp_allocate_node(mp, sizeof(mp_shape_node_data)); + mp_type(t) = type; + mp_path_ptr(t) = p; + mp_pen_ptr(t) = NULL; /* |NULL| means don't use a pen */ + mp_dash_ptr(t) = NULL; + new_number(t->red); + new_number(t->green); + new_number(t->blue); + new_number(t->black); + new_number(t->miterlimit); + new_number(t->dashscale); + set_number_to_unity(t->dashscale); + mp_color_model(t) = mp_uninitialized_model; + mp_pen_type(t) = 0; + mp_pre_script(t) = NULL; + mp_post_script(t) = NULL; + /* Set the |linejoin| and |miterlimit| fields in object |t| */ + if (number_greater(internal_value(mp_linejoin_internal), unity_t)) { + t->linejoin = mp_beveled_linejoin_code; + } else if (number_positive(internal_value(mp_linejoin_internal))) { + t->linejoin = mp_rounded_linejoin_code; + } else { + t->linejoin = mp_mitered_linejoin_code; + } + t->stacking = round_unscaled(internal_value(mp_stacking_internal)); + if (number_less(internal_value(mp_miterlimit_internal), unity_t)) { + set_number_to_unity(t->miterlimit); + } else { + number_clone(t->miterlimit, internal_value(mp_miterlimit_internal)); + } + if (number_greater(internal_value(mp_linecap_internal), unity_t)) { + t->linecap = mp_squared_linecap_code; + } else if (number_positive(internal_value(mp_linecap_internal))) { + t->linecap = mp_rounded_linecap_code; + } else { + t->linecap = mp_butt_linecap_code; + } + return (mp_node) t; +} + +@ @c +static mp_edge_header_node mp_free_shape_node (MP mp, mp_shape_node p) +{ + mp_edge_header_node e = NULL; + mp_toss_knot_list(mp, mp_path_ptr(p)); + if (mp_pen_ptr(p) != NULL) { + mp_toss_knot_list(mp, mp_pen_ptr(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_edge_header_node) mp_dash_ptr(p); + free_number(p->red); + free_number(p->green); + free_number(p->blue); + free_number(p->black); + free_number(p->miterlimit); + free_number(p->dashscale); + mp_free_node(mp, (mp_node) p, sizeof(mp_shape_node_data)); + return e ; +} + +@ 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 |dashscale|. 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 +void mp_sqrt_det (MP mp, mp_number *ret, mp_number *a_orig, mp_number *b_orig, mp_number *c_orig, mp_number *d_orig) +{ + mp_number a, b, c, d; + mp_number maxabs; /* $max(|a|,|b|,|c|,|d|)$ */ + unsigned s = 64; /* amount by which the result of |square_rt| needs to be scaled */ + mp_number tmp; + new_number_clone(a, *a_orig); + new_number_clone(b, *b_orig); + new_number_clone(c, *c_orig); + new_number_clone(d, *d_orig); + /* Initialize |maxabs| */ + new_number_abs(maxabs, a); + new_number_abs(tmp, b); + if (number_greater(tmp, maxabs)) { + number_clone(maxabs, tmp); + } + number_abs_clone(tmp, c); + if (number_greater(tmp, maxabs)) { + number_clone(maxabs, tmp); + } + number_abs_clone(tmp, d); + if (number_greater(tmp, maxabs)) { + number_clone(maxabs, tmp); + } + free_number(tmp); + while ((number_less(maxabs, fraction_one_t)) && (s > 1)) { + number_double(a); + number_double(b); + number_double(c); + number_double(d); + number_double(maxabs); + s = s/2; + } + { + mp_number r1, r2; + new_fraction(r1); + new_fraction(r2); + take_fraction(r1, a, d); + take_fraction(r2, b, c); + number_subtract(r1, r2); + number_abs(r1); + square_rt(*ret, r1); + number_multiply_int(*ret, s); + free_number(r1); + free_number(r2); + } + free_number(a); + free_number(b); + free_number(c); + free_number(d); + free_number(maxabs); +} + +static void mp_get_pen_scale (MP mp, mp_number *ret, mp_knot p) +{ + if (p == NULL) { + set_number_to_zero(*ret); + } else { + mp_number a, b, c, d; + new_number(a); + new_number(b); + new_number(c); + new_number(d); + set_number_from_subtraction(a, p->left_x, p->x_coord); + set_number_from_subtraction(b, p->right_x, p->x_coord); + set_number_from_subtraction(c, p->left_y, p->y_coord); + set_number_from_subtraction(d, p->right_y, p->y_coord); + mp_sqrt_det(mp, ret, &a, &b, &c, &d); + free_number(a); + free_number(b); + free_number(c); + free_number(d); + } +} + +@ @<Declarations@>= +static void mp_sqrt_det (MP mp, mp_number *ret, mp_number *a_orig, mp_number *b_orig, mp_number *c_orig, mp_number *d_orig); + +@ 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_ptr| gives the relevant path, then +there is the list of objects to clip or bound followed by a closing node. + +@d mp_has_color(A) (mp_type((A))<mp_start_clip_node_type) /* does a graphical object have color fields? */ +@d mp_has_script(A) (mp_type((A))<=mp_start_bounds_node_type) /* does a graphical object have color fields? */ +@d mp_has_pen(A) (mp_type((A))<=mp_stroked_node_type) /* does a graphical object have a |mp_pen_ptr| field? */ + +@d mp_is_start_or_stop(A) (mp_type((A))>=mp_start_clip_node_type) +@d mp_is_stop(A) (mp_type((A))>=mp_stop_clip_node_type) + +@<MPlib internal header stuff@>= +typedef struct mp_start_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int stacking; + struct mp_node_data *link; + /*specific */ + mp_string pre_script; + mp_string post_script; + mp_knot path; +} mp_start_node_data; + +typedef struct mp_start_node_data *mp_start_node; + +typedef struct mp_stop_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int stacking; + struct mp_node_data *link; + /*specific */ +} mp_stop_node_data; + +typedef struct mp_stop_node_data *mp_stop_node; + +@ Make a node of type |c| where |p| is the clipping or |setbounds| path. + +@c +static mp_node mp_new_bounds_node (MP mp, mp_knot p, int c) +{ + switch (c) { + case mp_start_clip_node_type: + case mp_start_group_node_type: + case mp_start_bounds_node_type: + { + mp_start_node t = (mp_start_node) mp_allocate_node(mp, sizeof(mp_start_node_data)); + mp_type(t) = c; + t->path = p; + t->link = NULL; + t->stacking = round_unscaled(internal_value(mp_stacking_internal)); + mp_pre_script(t) = NULL; + mp_post_script(t) = NULL; + return (mp_node) t; + } + break; + case mp_stop_clip_node_type: + case mp_stop_group_node_type: + case mp_stop_bounds_node_type: + { + mp_stop_node t = (mp_stop_node) mp_allocate_node(mp, sizeof(mp_stop_node_data)); + mp_type(t) = c; + t->link = NULL; + t->stacking = round_unscaled(internal_value(mp_stacking_internal)); + return (mp_node) t; + } + break; + default: + /* maybe some message */ + break; + } + return NULL; +} + +@ @c +static void mp_free_start_node (MP mp, mp_start_node p) +{ + mp_toss_knot_list(mp, mp_path_ptr(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, (mp_node) p, sizeof(mp_start_node_data)); +} + +static void mp_free_stop_node (MP mp, mp_stop_node p) +{ + mp_free_node(mp, (mp_node) p, sizeof(mp_stop_node_data)); +} + +@ 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 mp_get_dash_list(A) (mp_dash_node) (((mp_dash_node) (A))->link) /* in an edge header this points to the first dash node */ +@d mp_set_dash_list(A,B) ((mp_dash_node) (A))->link = (mp_node) ((B)) /* in an edge header this points to the first dash node */ + +@<MPlib internal header stuff@>= +typedef struct mp_dash_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int padding; + struct mp_node_data *link; + /*specific */ + mp_number start_x; /* the starting $x$~coordinate in a dash node */ + mp_number stop_x; /* the ending $x$~coordinate in a dash node */ + mp_number dash_y; /* $y$ value for the dash list in an edge header */ + mp_node dash_info; +} mp_dash_node_data; + +@ @<Types...@>= +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_node) mp->null_dash, sizeof(mp_dash_node_data)); + +@c +static mp_dash_node mp_get_dash_node (MP mp) +{ + mp_dash_node p = (mp_dash_node) mp_allocate_node(mp, sizeof(mp_dash_node_data)); + p->hasnumber = 0; + new_number(p->start_x); + new_number(p->stop_x); + new_number(p->dash_y); + mp_type(p) = mp_dash_node_type; + return 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 mp_bblast(A) ((mp_edge_header_node) (A))->bblast /* last item considered in bounding box computation */ +@d mp_edge_list(A) ((mp_edge_header_node) (A))->list /* where the object list begins in an edge header */ + +@<MPlib internal header stuff@>= +typedef struct mp_edge_header_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int padding; + struct mp_node_data *link; + /*specific */ + mp_number start_x; + mp_number stop_x; + mp_number dash_y; + mp_node dash_info; + mp_number minx; + mp_number miny; + mp_number maxx; + mp_number maxy; + mp_node bblast; + int bbtype; /* tells how bounding box data depends on |truecorners| */ + int ref_count; /* explained below */ + mp_node list; + mp_node obj_tail; /* explained below */ +} mp_edge_header_node_data; + +typedef struct mp_edge_header_node_data *mp_edge_header_node; + +typedef enum mp_bound_codes { + mp_no_bounds_code, /* |bbtype| value when bounding box data is valid for all |truecorners| values */ + mp_bounds_set_code, /* |bbtype| value when bounding box data is for |truecorners|${}\le 0$ */ + mp_bounds_unset_code, /* |bbtype| value when bounding box data is for |truecorners|${}>0$ */ +} mp_bound_codes; + +@ @c +static void mp_init_bbox (MP mp, mp_edge_header_node h) +{ + /* Initialize the bounding box information in edge structure |h| */ + (void) mp; + mp_bblast(h) = mp_edge_list(h); + h->bbtype = mp_no_bounds_code; + set_number_to_inf(h->minx); + set_number_to_inf(h->miny); + set_number_to_negative_inf(h->maxx); + set_number_to_negative_inf(h->maxy); +} + +@ 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 mp_obj_tail(A) ((mp_edge_header_node) (A))->obj_tail /* points to the last entry in the object list */ +@d mp_edge_ref_count(A) ((mp_edge_header_node) (A))->ref_count + +@ @c +static mp_edge_header_node mp_get_edge_header_node (MP mp) +{ + mp_edge_header_node p = (mp_edge_header_node) mp_allocate_node(mp, sizeof(mp_edge_header_node_data)); + mp_type(p) = mp_edge_header_node_type; + new_number(p->start_x); + new_number(p->stop_x); + new_number(p->dash_y); + new_number(p->minx); + new_number(p->miny); + new_number(p->maxx); + new_number(p->maxy); + p->list = mp_new_token_node(mp); /* or whatever, just a need a link handle */ + return p; +} + +static void mp_init_edges (MP mp, mp_edge_header_node h) +{ + /* initialize an edge header to NULL values */ + mp_set_dash_list(h, mp->null_dash); + mp_obj_tail(h) = mp_edge_list(h); + mp_link(mp_edge_list(h)) = NULL; + mp_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 mp_add_edge_ref(mp,A) mp_edge_ref_count((A)) += 1 + +@d mp_delete_edge_ref(mp,A) { + if (mp_edge_ref_count((A)) == 0) { + mp_toss_edges(mp, (mp_edge_header_node) (A)); + } else { + mp_edge_ref_count((A)) -= 1; + } +} + +@<Declarations@>= +static void mp_flush_dash_list (MP mp, mp_edge_header_node h); +static mp_edge_header_node mp_toss_gr_object (MP mp, mp_node p); +static void mp_toss_edges (MP mp, mp_edge_header_node h); + +@ @c +void mp_toss_edges (MP mp, mp_edge_header_node h) +{ + mp_node q; /* pointers that scan the list being recycled */ + mp_edge_header_node r; /* an edge structure that object |p| refers to */ + mp_flush_dash_list(mp, h); + q = mp_link(mp_edge_list(h)); + while (q != NULL) { + mp_node p = q; + q = mp_link(q); + r = mp_toss_gr_object(mp, p); + if (r != NULL) { + mp_delete_edge_ref(mp, r); + } + } + free_number(h->start_x); + free_number(h->stop_x); + free_number(h->dash_y); + free_number(h->minx); + free_number(h->miny); + free_number(h->maxx); + free_number(h->maxy); + mp_free_token_node(mp, h->list); + mp_free_node(mp, (mp_node) h, sizeof(mp_edge_header_node_data)); +} + +void mp_flush_dash_list (MP mp, mp_edge_header_node h) +{ + mp_dash_node q = mp_get_dash_list(h); + while (q != mp->null_dash) { + mp_dash_node p = q; + q = (mp_dash_node) mp_link(q); + mp_free_node(mp, (mp_node) p, sizeof(mp_dash_node_data)); + } + mp_set_dash_list(h, mp->null_dash); +} + +mp_edge_header_node mp_toss_gr_object (MP mp, mp_node p) +{ + /* returns an edge structure that needs to be dereferenced */ + switch (mp_type(p)) { + case mp_fill_node_type: + case mp_stroked_node_type: + return mp_free_shape_node(mp, (mp_shape_node) p); + case mp_start_clip_node_type: + case mp_start_group_node_type: + case mp_start_bounds_node_type: + mp_free_start_node(mp, (mp_start_node) p); + return NULL; + case mp_stop_clip_node_type: + case mp_stop_group_node_type: + case mp_stop_bounds_node_type: + mp_free_stop_node(mp, (mp_stop_node) p); + return NULL; + default: + return NULL; + } +} + +@ If we use |add_edge_ref| to \quote {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_edge_header_node mp_private_edges (MP mp, mp_edge_header_node h) +{ + /* make a private copy of the edge structure headed by |h| */ + if (mp_edge_ref_count(h) == 0) { + return h; + } else { + mp_edge_header_node hh; /* the edge header for the new copy */ + mp_dash_node p, pp; /* pointers for copying the dash list */ + mp_edge_ref_count(h) -= 1; + hh = (mp_edge_header_node) mp_copy_objects (mp, mp_link(mp_edge_list(h)), NULL); + @<Copy the dash list from |h| to |hh|@> + @<Copy the bounding box information from |h| to |hh| and make |mp_bblast(hh)| point into the new object list@> + return hh; + } +} + +@ Here we use the fact that |mp_get_dash_list(hh)=mp_link(hh)|. @^data structure +assumptions@> + +@<Copy the dash list from |h| to |hh|@>= +pp = (mp_dash_node) hh; +p = mp_get_dash_list(h); +while ((p != mp->null_dash)) { + mp_link(pp) = (mp_node) mp_get_dash_node(mp); + pp = (mp_dash_node) mp_link(pp); + number_clone(pp->start_x, p->start_x); + number_clone(pp->stop_x, p->stop_x); + p = (mp_dash_node) mp_link(p); +} +mp_link(pp) = (mp_node) mp->null_dash; +number_clone(hh->dash_y, h->dash_y); + +@ |h| is an edge structure + +@c +static mp_dash_object *mp_export_dashes (MP mp, mp_shape_node q, mp_number *w) +{ + mp_dash_node h = (mp_dash_node) mp_dash_ptr(q); + if (h == NULL || mp_get_dash_list(h) == mp->null_dash) { + return NULL; + } else { + mp_dash_object *d; + mp_dash_node p; + mp_number scf; /* scale factor */ + mp_number dashoff; + double *dashes = NULL; + int num_dashes = 1; + new_number(scf); + p = mp_get_dash_list(h); + mp_get_pen_scale(mp, &scf, mp_pen_ptr(q)); + if (number_zero(scf)) { + if (number_zero(*w)) { + number_clone(scf, q->dashscale); + } else { + free_number(scf); + return NULL; + } + } else { + mp_number ret; + new_number(ret); + make_scaled(ret, *w, scf); + take_scaled(scf, ret, q->dashscale); + free_number(ret); + } + number_clone(*w, scf); + d = mp_allocate_dash(mp); + set_number_from_addition(mp->null_dash->start_x, p->start_x, h->dash_y); + { + mp_number ret, arg1; + new_number(ret); + new_number(arg1); + new_number(dashoff); + while (p != mp->null_dash) { + dashes = mp_memory_reallocate(dashes, (size_t) (num_dashes + 2) * sizeof(double)); + set_number_from_subtraction(arg1, p->stop_x, p->start_x); + take_scaled(ret, arg1, scf); + dashes[(num_dashes - 1)] = number_to_double(ret); + set_number_from_subtraction(arg1, ((mp_dash_node) mp_link(p))->start_x, p->stop_x); + take_scaled(ret, arg1, scf); + dashes[(num_dashes)] = number_to_double(ret); + dashes[(num_dashes + 1)] = -1.0; /* terminus */ + num_dashes += 2; + p = (mp_dash_node) mp_link(p); + } + d->array = dashes; + mp_dash_offset(mp, &dashoff, h); + take_scaled(ret, dashoff, scf); + d->offset = number_to_double(ret); + free_number(ret); + free_number(arg1); + } + free_number(dashoff); + free_number(scf); + return d; + } +} + +@ @<Copy the bounding box information from |h| to |hh|...@>= +number_clone(hh->minx, h->minx); +number_clone(hh->miny, h->miny); +number_clone(hh->maxx, h->maxx); +number_clone(hh->maxy, h->maxy); +hh->bbtype = h->bbtype; +p = (mp_dash_node) mp_edge_list(h); +pp = (mp_dash_node) mp_edge_list(hh); +while ((p != (mp_dash_node) mp_bblast(h))) { + if (p == NULL) { + mp_confusion(mp, "boundingbox last"); + @:this can't happen bblast}{\quad bblast@> + } else { + p = (mp_dash_node) mp_link(p); + pp = (mp_dash_node) mp_link(pp); + } +} +mp_bblast(hh) = (mp_node) 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_edge_header_node mp_copy_objects (MP mp, mp_node p, mp_node q); + +@ @c +mp_edge_header_node mp_copy_objects (MP mp, mp_node p, mp_node q) { + mp_node pp; /* the last newly copied object */ + int k = 0; /* temporary register */ + mp_edge_header_node hh = mp_get_edge_header_node(mp); /* the new edge header */ + mp_set_dash_list(hh, mp->null_dash); + mp_edge_ref_count(hh) = 0; + pp = mp_edge_list(hh); + while (p != q) { + @<Make |mp_link(pp)| point to a copy of object |p|, and update |p| and |pp|@> + } + mp_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_fill_node_type: + case mp_stroked_node_type: + k = sizeof(mp_shape_node_data); + break; + case mp_start_clip_node_type: + case mp_start_group_node_type: + case mp_start_bounds_node_type: + k = sizeof(mp_start_node_data); + break; + case mp_stop_clip_node_type: + case mp_stop_group_node_type: + case mp_stop_bounds_node_type: + k = sizeof(mp_stop_node_data); + break; + default: + break; + } + mp_link(pp) = mp_allocate_node(mp, (size_t) k); /* |gr_object| */ + 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_fill_node_type: + case mp_stroked_node_type: + { + mp_shape_node tt = (mp_shape_node) pp; + mp_shape_node t = (mp_shape_node) p; + new_number_clone(tt->red, t->red); + new_number_clone(tt->green, t->green); + new_number_clone(tt->blue, t->blue); + new_number_clone(tt->black, t->black); + new_number_clone(tt->miterlimit, t->miterlimit); + new_number_clone(tt->dashscale, t->dashscale); + mp_path_ptr(tt) = mp_copy_path(mp, mp_path_ptr(t)); + 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_ptr(t) != NULL) { + mp_pen_ptr(tt) = mp_copy_pen(mp, mp_pen_ptr(t)); + } + if (mp_dash_ptr(p) != NULL) { + mp_add_edge_ref(mp, mp_dash_ptr(pp)); + } + } + break; + case mp_start_clip_node_type: + case mp_start_group_node_type: + case mp_start_bounds_node_type: + { + mp_start_node tt = (mp_start_node) pp; + mp_start_node t = (mp_start_node) p; + mp_path_ptr(tt) = mp_copy_path(mp, mp_path_ptr(t)); + 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)); + } + } + break; + case mp_stop_clip_node_type: + case mp_stop_group_node_type: + case mp_stop_bounds_node_type: + break; + default: + 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 \quote {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. + +@c +static mp_node mp_skip_1component (MP mp, mp_node p) +{ + int lev = 0; /* current nesting level */ + (void) mp; + do { + if (mp_is_start_or_stop (p)) { + if (mp_is_stop(p)) { + --lev; + } else { + ++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, int nuline); + +@ @c +void mp_print_edges (MP mp, mp_node h, const char *s, int nuline) +{ + mp_node p = mp_edge_list(h); /* a graphical object to be printed */ + mp_number scf; /* a scale factor for the dash pattern */ + new_number(scf); + mp_print_diagnostic(mp, "Edge structure", s, nuline); + 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_str(mp, "[unknown object type!]"); + break; + } + } + mp_print_nl(mp, "End edges"); + if (p != mp_obj_tail(h)) { + mp_print_str(mp, "?"); + @.End edges?@> + } + mp_end_diagnostic(mp, 1); + free_number(scf); +} + +@ @<Cases for printing graphical object node |p|@>= +case mp_fill_node_type: + mp_print_str(mp, "Filled contour "); + mp_print_obj_color (mp, p); + mp_print_chr(mp, ':'); + mp_print_ln(mp); + mp_pr_path(mp, mp_path_ptr((mp_shape_node) p)); + mp_print_ln(mp); + if ((mp_pen_ptr((mp_shape_node) p) != NULL)) { + @<Print join type for graphical object |p|@> + mp_print_str(mp, " with pen"); + mp_print_ln(mp); + mp_pr_pen(mp, mp_pen_ptr((mp_shape_node) p)); + } + break; + +@ @<Print join type for graphical object |p|@>= +switch (((mp_shape_node) p)->linejoin) { + case mp_mitered_linejoin_code: + mp_print_str(mp, "mitered joins limited "); + print_number(((mp_shape_node) p)->miterlimit); + break; + case mp_rounded_linejoin_code: + mp_print_str(mp, "round joins"); + break; + case mp_beveled_linejoin_code: + mp_print_str(mp, "beveled joins"); + break; + default: + mp_print_str(mp, "?? joins"); + break; + @.??@> +} + +@ For stroked nodes, we need to print |linecap_val(p)| as well. + +@<Print join and cap types for stroked node |p|@>= +switch (((mp_shape_node) p)->linecap) { + case mp_butt_linecap_code: + mp_print_str(mp, "butt"); + break; + case mp_rounded_linecap_code: + mp_print_str(mp, "round"); + break; + case mp_squared_linecap_code: + mp_print_str(mp, "square"); + break; + default: + mp_print_str(mp, "??"); + break; + @.??@> +} +mp_print_str(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) +{ + mp_shape_node p0 = (mp_shape_node) p; + switch (mp_color_model(p)) { + case mp_grey_model: + if (number_positive(p0->grey)) { + mp_print_str(mp, "greyed "); + mp_print_chr(mp, '('); + print_number(p0->grey); + mp_print_chr(mp, ')'); + }; + break; + case mp_cmyk_model: + if (number_positive(p0->cyan) || number_positive(p0->magenta) + || number_positive(p0->yellow) || number_positive(p0->black)) { + mp_print_str(mp, "processcolored "); + mp_print_chr(mp, '('); + print_number(p0->cyan); + mp_print_chr(mp, ','); + print_number(p0->magenta); + mp_print_chr(mp, ','); + print_number(p0->yellow); + mp_print_chr(mp, ','); + print_number(p0->black); + mp_print_chr(mp, ')'); + }; + break; + case mp_rgb_model: + if (number_positive(p0->red) || number_positive(p0->green) || number_positive(p0->blue)) { + mp_print_str(mp, "colored "); + mp_print_chr(mp, '('); + print_number(p0->red); + mp_print_chr(mp, ','); + print_number(p0->green); + mp_print_chr(mp, ','); + print_number(p0->blue); + mp_print_chr(mp, ')'); + } + break; + default: + break; + } +} + +@ @<Cases for printing graphical object node |p|@>= +case mp_stroked_node_type: + mp_print_str(mp, "Filled pen stroke "); + mp_print_obj_color (mp, p); + mp_print_chr(mp, ':'); + mp_print_ln(mp); + mp_pr_path(mp, mp_path_ptr((mp_shape_node) p)); + if (mp_dash_ptr(p) != NULL) { + @<Finish printing the dash pattern that |p| refers to@> + } + mp_print_ln(mp); + @<Print join and cap types for stroked node |p|@> + mp_print_str(mp, " with pen"); + mp_print_ln(mp); + if (mp_pen_ptr((mp_shape_node) p) == NULL) { + mp_print_str(mp, "???"); /* shouldn't happen */ + @.???@> + } else { + mp_pr_pen(mp, mp_pen_ptr((mp_shape_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_ptr| 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@>= +mp_dash_node ppd, hhd; +int ok_to_dash = mp_pen_is_elliptical(mp_pen_ptr((mp_shape_node) p)); +mp_print_nl(mp, "dashed ("); +if (! ok_to_dash) { + set_number_to_unity(scf); +} else { + number_clone(scf, ((mp_shape_node) p)->dashscale); +} +hhd = (mp_dash_node) mp_dash_ptr(p); +ppd = mp_get_dash_list(hhd); +if ((ppd == mp->null_dash) || number_negative(hhd->dash_y)) { + mp_print_str(mp, " ??"); +} else { + mp_number dashoff; + mp_number ret, arg1; + new_number(ret); + new_number(arg1); + new_number(dashoff); + set_number_from_addition(mp->null_dash->start_x, ppd->start_x, hhd->dash_y ); + while (ppd != mp->null_dash) { + mp_print_str(mp, "on "); + set_number_from_subtraction(arg1, ppd->stop_x, ppd->start_x); + take_scaled(ret, arg1, scf); + print_number( ret); + mp_print_str(mp, " off "); + set_number_from_subtraction(arg1, ((mp_dash_node) mp_link(ppd))->start_x, ppd->stop_x); + take_scaled(ret, arg1, scf); + print_number(ret); + ppd = (mp_dash_node) mp_link(ppd); + if (ppd != mp->null_dash) { + mp_print_chr(mp, ' '); + } + } + mp_print_str(mp, ") shifted "); + mp_dash_offset(mp, &dashoff, hhd); + take_scaled(ret, dashoff, scf); + number_negate(ret); + print_number(ret); + free_number(dashoff); + free_number(ret); + free_number(arg1); + if (!ok_to_dash || number_zero(hhd->dash_y)) { + mp_print_str(mp, " (this will be ignored)"); + } +} + +@ @<Declarations@>= +static void mp_dash_offset (MP mp, mp_number *x, mp_dash_node h); + +@ @c +void mp_dash_offset (MP mp, mp_number *x, mp_dash_node h) +{ + if (mp_get_dash_list(h) == mp->null_dash || number_negative(h->dash_y)) { + mp_confusion(mp, "dash offset"); + @:this can't happen dash0}{\quad dash0@> + } else if (number_zero(h->dash_y)) { + set_number_to_zero(*x); + } else { + number_clone(*x, (mp_get_dash_list(h))->start_x); + number_modulo(*x, h->dash_y); + number_negate(*x); + if (number_negative(*x)) { + number_add(*x, h->dash_y); + } + } +} + +@ @<Cases for printing graphical object node |p|@>= +case mp_start_clip_node_type: + mp_print_str(mp, "clipping path:"); + goto COMMONSTART; +case mp_start_group_node_type: + mp_print_str(mp, "setgroup path:"); + goto COMMONSTART; +case mp_start_bounds_node_type: + mp_print_str(mp, "setbounds path:"); + COMMONSTART: + mp_print_ln(mp); + mp_pr_path(mp, mp_path_ptr((mp_start_node) p)); + break; +case mp_stop_clip_node_type: + mp_print_str(mp, "stop clipping"); + break; +case mp_stop_group_node_type: + mp_print_str(mp, "stop group"); + break; +case mp_stop_bounds_node_type: + mp_print_str(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_edge_header_node mp_make_dashes (MP mp, mp_edge_header_node h) +{ + if (mp_get_dash_list(h) != mp->null_dash) { + return h; + } else { + /* 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_ptr(p)| */ + mp_dash_node d, dd; /* pointers used to create the dash list */ + mp_number y0; + @<Other local variables in |make_dashes|@> + new_number(y0); /* the initial $y$ coordinate */ + p0 = NULL; + p = mp_link(mp_edge_list(h)); + while (p != NULL) { + if (mp_type(p) != mp_stroked_node_type) { + @<Complain that the edge structure contains a node of the wrong type and |goto not_found|@> + } + pp = mp_path_ptr((mp_shape_node) p); + if (p0 == NULL) { + p0 = p; + number_clone(y0, pp->y_coord); + } + @<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 (mp_get_dash_list(h) == mp->null_dash) { + goto NOT_FOUND; /* No error message */ + } else { + @<Scan |mp_get_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@> + free_number(y0); + return h; + } + NOT_FOUND: + free_number(y0); + @<Flush the dash list, recycle |h| and return |NULL|@> + } +} + +@ @<Complain that the edge structure contains a node of the wrong type...@>= +mp_back_error( + mp, + "Picture is too complicated to use as a dash pattern", + "When you say 'dashed p', picture p should not contain any text, filled regions,\n" + "or clipping paths. This time it did so I'll just make it a solid line instead." +); +mp_get_x_next(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) +{ + mp_back_error( + mp, + "Picture is too complicated to use as a dash pattern", + "When you say 'dashed p', every path in p should be monotone in x and there must\n" + "be no overlapping. This failed so I'll just make it a solid line instead." + ); + mp_get_x_next(mp); +} + +@ We stash |p| in |dash_info(d)| if |mp_dash_ptr(p)<>0| so that subsequent +processing can handle the case where the pen stroke |p| is itself dashed. + +@d mp_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_knot); +} +d = (mp_dash_node) mp_get_dash_node(mp); +if (mp_dash_ptr(p) == NULL) { + mp_dash_info(d) = NULL; +} else { + mp_dash_info(d) = p; +} +if (number_less(pp->x_coord, rr->x_coord)) { + number_clone(d->start_x, pp->x_coord); + number_clone(d->stop_x, rr->x_coord); +} else { + number_clone(d->start_x, rr->x_coord); + number_clone(d->stop_x, pp->x_coord); +} + +@ 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|...@>= +{ + mp_number x0, x1, x2, x3; /* $x$ coordinates of the segment from |qq| to |rr| */ + new_number_clone(x0, qq->x_coord); + new_number_clone(x1, qq->right_x); + new_number_clone(x2, rr->left_x); + new_number_clone(x3, rr->x_coord); + if (number_greater(x0, x1) || number_greater(x1, x2) || number_greater(x2, x3)) { + if (number_less(x0, x1) || number_less(x1, x2) || number_less(x2, x3)) { + mp_number a1, a2, a3, a4; + int test; + new_number(a1); + new_number(a2); + new_number(a3); + new_number(a4); + set_number_from_subtraction(a1, x2, x1); + set_number_from_subtraction(a2, x2, x1); + set_number_from_subtraction(a3, x1, x0); + set_number_from_subtraction(a4, x3, x2); + test = ab_vs_cd(a1, a2, a3, a4); + free_number(a1); + free_number(a2); + free_number(a3); + free_number(a4); + if (test > 0) { + mp_x_retrace_error(mp); + free_number(x0); + free_number(x1); + free_number(x2); + free_number(x3); + goto NOT_FOUND; + } + } + } + if (number_greater(pp->x_coord, x0) || number_greater(x0, x3)) { + if (number_less(pp->x_coord, x0) || number_less(x0, x3)) { + mp_x_retrace_error(mp); + free_number(x0); + free_number(x1); + free_number(x2); + free_number(x3); + goto NOT_FOUND; + } + } + free_number(x0); + free_number(x1); + free_number(x2); + free_number(x3); +} + +@ @<Make sure |p| and |p0| are the same color and |goto not_found|...@>= +if (! number_equal(((mp_shape_node) p)->red, ((mp_shape_node) p0)->red) + || ! number_equal(((mp_shape_node) p)->black, ((mp_shape_node) p0)->black) + || ! number_equal(((mp_shape_node) p)->green, ((mp_shape_node) p0)->green) + || ! number_equal(((mp_shape_node) p)->blue, ((mp_shape_node) p0)->blue) + ) { + mp_back_error( + mp, + "Picture is too complicated to use as a dash pattern", + "When you say 'dashed p', everything in picture p should be the same color. I\n" + "can't handle your color changes so I'll just make it a solid line instead." + ); + mp_get_x_next(mp); + goto NOT_FOUND; +} + +@ @<Insert |d| into the dash list and |goto not_found| if there is an error@>= +number_clone(mp->null_dash->start_x, d->stop_x); +dd = (mp_dash_node) h; /* this makes |mp_link(dd)=mp_get_dash_list(h)| */ +while (number_less(((mp_dash_node) mp_link(dd))->start_x, d->stop_x)) { + dd = (mp_dash_node) mp_link(dd); +} +if ((dd != (mp_dash_node) h) && number_greater(dd->stop_x, d->start_x)) { + mp_x_retrace_error(mp); + goto NOT_FOUND; +} +mp_link(d) = mp_link(dd); +mp_link(dd) = (mp_node) d; + +@ @<Set |dash_y(h)| and merge the first and last dashes if necessary@>= +d = mp_get_dash_list(h); +while (mp_link(d) != (mp_node) mp->null_dash) { + d = (mp_dash_node) mp_link(d); +} +dd = mp_get_dash_list(h); +set_number_from_subtraction(h->dash_y, d->stop_x, dd->start_x); +{ + mp_number absval; + new_number(absval); + number_abs_clone(absval, y0); + if (number_greater(absval, h->dash_y) ) { + number_clone(h->dash_y, absval); + } else if (d != dd) { + mp_set_dash_list(h, mp_link(dd)); + set_number_from_addition(d->stop_x, dd->stop_x, h->dash_y); + mp_free_node(mp, (mp_node) dd, sizeof(mp_dash_node_data)); + } + free_number(absval); +} + +@ We get here when the argument is a NULL picture or when there is an error. +Recovering from an error involves making |mp_get_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); +mp_delete_edge_ref(mp, 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 |mp_get_dash_list(h)| and deal with any dashes that are themselves dashed@>= +{ +mp_number hsf; /* the dash pattern from |hh| gets scaled by this */ +new_number(hsf); +d = (mp_dash_node) h; /* now |mp_link(d)=mp_get_dash_list(h)| */ +while (mp_link(d) != (mp_node) mp->null_dash) { + ds = mp_dash_info(mp_link(d)); + if (ds == NULL) { + d = (mp_dash_node) mp_link(d); + } else { + hh = (mp_edge_header_node) mp_dash_ptr(ds); + number_clone(hsf, ((mp_shape_node) ds)->dashscale); + if (hh == NULL) { + mp_confusion(mp, "dash pattern"); + @:this can't happen dash0}{\quad dash1@> + return NULL; + } else if (number_zero(((mp_dash_node) hh)->dash_y )) { + d = (mp_dash_node) mp_link(d); + } else if (mp_get_dash_list (hh) == NULL) { + mp_confusion(mp, "dash list"); + @:this can't happen dash1}{\quad dash1@> + return NULL; + } else { + @<Replace |mp_link(d)| by a dashed version as determined by edge header |hh| and scale factor |ds|@> + } + } +} +free_number(hsf); +} + +@ @<Other local variables in |make_dashes|@>= +mp_dash_node dln; /* |mp_link(d)| */ +mp_edge_header_node hh; /* an edge header that tells how to break up |dln| */ +mp_node ds; /* the stroked node from which |hh| and |hsf| are derived */ + +@ @<Replace |mp_link(d)| by a dashed version as determined by edge header...@>= +mp_number xoff; /* added to $x$ values in |mp_get_dash_list(hh)| to match |dln| */ +mp_number dashoff; +mp_number r1, r2; +new_number(r1); +new_number(r2); +dln = (mp_dash_node) mp_link(d); +dd = mp_get_dash_list(hh); +new_number(xoff); +new_number(dashoff); +mp_dash_offset(mp, &dashoff, (mp_dash_node) hh); +take_scaled(r1, hsf, dd->start_x); +take_scaled(r2, hsf, dashoff); +number_add(r1, r2); +set_number_from_subtraction(xoff, dln->start_x, r1); +free_number(dashoff); +take_scaled(r1, hsf, dd->start_x); +take_scaled(r2, hsf, hh->dash_y); +set_number_from_addition(mp->null_dash->start_x, r1, r2); +number_clone(mp->null_dash->stop_x, mp->null_dash->start_x); +@<Advance |dd| until finding the first dash that overlaps |dln| when offset by |xoff|@> +while (number_lessequal(dln->start_x, dln->stop_x)) { + @<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_dash_node) mp_link(dd); + take_scaled(r1, hsf, dd->start_x); + set_number_from_addition(dln->start_x , xoff, r1); +} +free_number(xoff); +free_number(r1); +free_number(r2); +mp_link(d) = mp_link(dln); +mp_free_node(mp, (mp_node) dln, sizeof(mp_dash_node_data)); + +@ 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...@>= +{ + mp_number r1; + new_number(r1); + take_scaled(r1, hsf, dd->stop_x); + number_add(r1, xoff); + while (number_less(r1, dln->start_x)) { + dd = (mp_dash_node) mp_link(dd); + take_scaled(r1, hsf, dd->stop_x); + number_add(r1, xoff); + } + free_number(r1); +} + +@ @<If |dd| has `fallen off the end', back up to the beginning and fix...@>= +if (dd == mp->null_dash) { + mp_number ret; + new_number(ret); + dd = mp_get_dash_list(hh); + take_scaled(ret, hsf, hh->dash_y); + number_add(xoff, ret); + free_number(ret); +} + +@ 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...@>= +{ + mp_number r1; + new_number(r1); + take_scaled(r1, hsf, dd->start_x); + number_add(r1, xoff); + if (number_lessequal(r1, dln->stop_x)) { + mp_link(d) = (mp_node) mp_get_dash_node(mp); + d = (mp_dash_node) mp_link(d); + mp_link(d) = (mp_node) dln; + take_scaled(r1, hsf, dd->start_x ); + number_add(r1, xoff); + if (number_greater(dln->start_x, r1)) { + number_clone(d->start_x, dln->start_x); + } else { + number_clone(d->start_x, r1); + } + take_scaled(r1, hsf, dd->stop_x); + number_add(r1, xoff); + if (number_less(dln->stop_x, r1)) { + number_clone(d->stop_x, dln->stop_x ); + } else { + number_clone(d->stop_x, r1); + } + } + free_number(r1); +} + +@ 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_edge_header_node h) +{ + if (number_less(mp_minx, h->minx)) { + number_clone(h->minx, mp_minx); + } + if (number_less(mp_miny, h->miny)) { + number_clone(h->miny, mp_miny); + } + if (number_greater(mp_maxx, h->maxx)) { + number_clone(h->maxx, mp_maxx); + } + if (number_greater(mp_maxy, h->maxy)) { + number_clone(h->maxy, 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_edge_header_node h) +{ + if (mp_right_type(p) != mp_endpoint_knot) { + mp_fraction dx, dy; /* a unit vector in the direction out of the path at~|p| */ + mp_number d; /* a factor for adjusting the length of |(dx,dy)| */ + mp_number z; /* a coordinate being tested against the bounding box */ + mp_number xx, yy; /* the extreme pen vertex in the |(dx,dy)| direction */ + new_fraction(dx); + new_fraction(dy); + new_number(xx); + new_number(yy); + new_number(z); + new_number(d); + mp_knot q = mp_next_knot(p); /* a knot node adjacent to knot |p| */ + while (1) { + @<Make |(dx,dy)| the final direction for the path segment from |q| to~|p|; set~|d|@> + pyth_add(d, dx, dy); + if (number_positive(d)) { + @<Normalize the direction |(dx,dy)| and find the pen offset |(xx,yy)|@> + for (int 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@> + number_negate(dx); + number_negate(dy); + } + } + if (mp_right_type(p) == mp_endpoint_knot) { + goto DONE; + } else { + @<Advance |p| to the end of the path and make |q| the previous knot@> + } + } + DONE: + free_number(dx); + free_number(dy); + free_number(xx); + free_number(yy); + free_number(z); + free_number(d); + } +} + +@ @<Make |(dx,dy)| the final direction for the path segment from...@>= +if (q == mp_next_knot(p)) { + set_number_from_subtraction(dx, p->x_coord, p->right_x); + set_number_from_subtraction(dy, p->y_coord, p->right_y); + if (number_zero(dx) && number_zero(dy)) { + set_number_from_subtraction(dx, p->x_coord, q->left_x); + set_number_from_subtraction(dy, p->y_coord, q->left_y); + } +} else { + set_number_from_subtraction(dx, p->x_coord, p->left_x); + set_number_from_subtraction(dy, p->y_coord, p->left_y); + if (number_zero(dx) && number_zero(dy)) { + set_number_from_subtraction(dx, p->x_coord, q->right_x); + set_number_from_subtraction(dy, p->y_coord, q->right_y); + } +} +set_number_from_subtraction(dx, p->x_coord, q->x_coord); +set_number_from_subtraction(dy, p->y_coord, q->y_coord); + +@ @<Normalize the direction |(dx,dy)| and find the pen offset |(xx,yy)|@>= +mp_number arg1, r; +new_fraction(r); +new_number(arg1); +make_fraction(r, dx, d); +number_clone(dx, r); +make_fraction(r, dy, d); +number_clone(dy, r); +free_number(r); +number_negated_clone(arg1, dy); +mp_find_offset(mp, &arg1, &dx, pp); +free_number(arg1); +number_clone(xx, mp->cur_x); +number_clone(yy, mp->cur_y); + +@ @<Use |(dx,dy)| to generate a vertex of the square end cap and...@>= +mp_number r1, r2, arg1; +new_number(arg1); +new_fraction(r1); +new_fraction(r2); +mp_find_offset(mp, &dx, &dy, pp); +set_number_from_subtraction(arg1, xx, mp->cur_x); +take_fraction(r1, arg1, dx); +set_number_from_subtraction(arg1, yy, mp->cur_y); +take_fraction(r2, arg1, dy); +set_number_from_addition(d, r1, r2); +if ((number_negative(d) && (i == 1)) || (number_positive(d) && (i == 2))) { + mp_confusion(mp, "box ends"); + @:this can't happen box ends}{\quad|box\_ends|@> +} +take_fraction(r1, d, dx); +set_number_from_addition(z, p->x_coord, mp->cur_x); +number_add(z, r1); +if (number_less(z, h->minx)) { + number_clone(h->minx, z); +} +if (number_greater(z, h->maxx)) { + number_clone(h->maxx, z); +} +take_fraction(r1, d, dy); +set_number_from_addition(z, p->y_coord, mp->cur_y); +number_add(z, r1); +if (number_less(z, h->miny)) { + number_clone(h->miny, z); +} +if (number_greater(z, h->maxy)) { + number_clone(h->maxy, z); +} +free_number(r1); +free_number(r2); +free_number(arg1); + +@ @<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_knot); + +@ 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_edge_header_node h, int top_level) +{ + /* + Wipe out any existing bounding box information if |bbtype(h)| is + incompatible with |internal[mp_true_corners]| + */ + switch (h->bbtype ) { + case mp_no_bounds_code: + break; + case mp_bounds_set_code: + if (number_positive(internal_value(mp_true_corners_internal))) { + mp_init_bbox(mp, h); + } + break; + case mp_bounds_unset_code: + if (number_nonpositive(internal_value(mp_true_corners_internal))) { + mp_init_bbox(mp, h); + } + break; + } + + while (mp_link(mp_bblast(h)) != NULL) { + mp_node p = mp_link(mp_bblast(h)); /* a graphical object being considered */ + mp_bblast(h) = p; + switch (mp_type(p)) { + case mp_stop_clip_node_type: + if (top_level) { + mp_confusion(mp, "clip"); + break; + } else { + return; + @:this can't happen bbox}{\quad bbox@> + } + @<Other cases for updating the bounding box based on the type of object |p|@> + default: + break; + } + } + if (! top_level) { + mp_confusion(mp, "boundingbox"); + } +} + +@ @<Declarations@>= +static void mp_set_bbox (MP mp, mp_edge_header_node h, int top_level); + +@ @<Other cases for updating the bounding box...@>= +case mp_start_bounds_node_type: + if (number_positive(internal_value(mp_true_corners_internal))) { + h->bbtype = mp_bounds_unset_code; + } else { + h->bbtype = mp_bounds_set_code; + mp_path_bbox(mp, mp_path_ptr((mp_start_node) p)); + mp_adjust_bbox(mp, h); + @<Scan to the matching |mp_stop_bounds_node| node and update |p| and |mp_bblast(h)|@> + } + break; +case mp_stop_bounds_node_type: + if (number_nonpositive (internal_value(mp_true_corners_internal))) { + mp_confusion(mp, "bounds"); + @:this can't happen bbox2}{\quad bbox2@> + } + break; + +@ @<Scan to the matching |mp_stop_bounds_node| node and update |p| and...@>= +{ + int lev = 1; + while (lev != 0) { + if (mp_link(p) == NULL) { + mp_confusion(mp, "bounds"); + @:this can't happen bbox2}{\quad bbox2@> + } else { + p = mp_link(p); + if (mp_type(p) == mp_start_bounds_node_type) { + ++lev; + } else if (mp_type(p) == mp_stop_bounds_node_type) { + --lev; + } + } + } + mp_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_fill_node_type: +case mp_stroked_node_type: + { + mp_number x0a, y0a, x1a, y1a; + mp_path_bbox(mp, mp_path_ptr((mp_shape_node) p)); + /* Stroked paths always have a pen */ + if (mp_pen_ptr((mp_shape_node) p) != NULL) { + new_number_clone(x0a, mp_minx); + new_number_clone(y0a, mp_miny); + new_number_clone(x1a, mp_maxx); + new_number_clone(y1a, mp_maxy); + mp_pen_bbox(mp, mp_pen_ptr((mp_shape_node) p)); + number_add(mp_minx, x0a); + number_add(mp_miny, y0a); + number_add(mp_maxx, x1a); + number_add(mp_maxy, y1a); + free_number(x0a); + free_number(y0a); + free_number(x1a); + free_number(y1a); + } + mp_adjust_bbox(mp, h); + /* Stroked paths can be open, so: */ + if ((mp_left_type(mp_path_ptr((mp_shape_node) p)) == mp_endpoint_knot) && (((mp_shape_node) p)->linecap == 2)) { + mp_box_ends(mp, mp_path_ptr((mp_shape_node) p), mp_pen_ptr((mp_shape_node) p), h); + } + } + break; + +@ This case involves a recursive call that advances |mp_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_number sminx, sminy, smaxx, smaxy; + /* for saving the bounding box during recursive calls */ + mp_number x0a, y0a, x1a, y1a; + mp_path_bbox(mp, mp_path_ptr((mp_start_node) p)); + new_number_clone(x0a, mp_minx); + new_number_clone(y0a, mp_miny); + new_number_clone(x1a, mp_maxx); + new_number_clone(y1a, mp_maxy); + new_number_clone(sminx, h->minx); + new_number_clone(sminy, h->miny); + new_number_clone(smaxx, h->maxx); + new_number_clone(smaxy, h->maxy); + @<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 |x0a|, |x1a|, |y0a|, |y1a|@> + number_clone(mp_minx, sminx); + number_clone(mp_miny, sminy); + number_clone(mp_maxx, smaxx); + number_clone(mp_maxy, smaxy); + mp_adjust_bbox(mp, h); + free_number(sminx); + free_number(sminy); + free_number(smaxx); + free_number(smaxy); + free_number(x0a); + free_number(y0a); + free_number(x1a); + free_number(y1a); + } + break; + +@ @<Reinitialize the bounding box in header |h| and call |set_bbox|...@>= +set_number_to_inf(h->minx); +set_number_to_inf(h->miny); +set_number_to_negative_inf(h->maxx); +set_number_to_negative_inf(h->maxy); +mp_set_bbox(mp, h, 0); + +@ @<Clip the bounding box in |h| to the rectangle given by |x0a|, |x1a|,...@>= +if (number_less(h->minx, x0a)) { + number_clone(h->minx, x0a); +} +if (number_less(h->miny, y0a)) { + number_clone(h->miny, y0a); +} +if (number_greater(h->maxx, x1a)) { + number_clone(h->maxx, x1a); +} +if (number_greater(h->maxy, y1a)) { + number_clone(h->maxy, y1a); +} + +@* 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 +\quote {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 0 /* 16384 */ /* added to offset changes to make them positive */ + +@<Glob...@>= +int spec_offset; /* number of pen edges between |h| and the initial offset */ +int spec_padding; /* be nice */ + +@ The next function calculates $1/3 B'(t) = (-p + (3c_1 + (-3c_2 + q)))*t^2 + (2p ++ (-4c_1 + 2*c_2))t + (-p + c_1)$, for cubic curve |B(t)| given by +|p|,|c1|,|c2|,|q| and it's used for |t| near 0 and |t| near 1. We use double +mode, otherwise we have to take care of overflow. + +@ @c +static mp_knot mp_offset_prep (MP mp, mp_knot c, mp_knot h) +{ + int n; /* the number of vertices in the pen polygon */ + mp_knot c0, p, q, q0, r, w, ww; /* for list manipulation */ + int 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| */ + mp_number dxin, dyin; /* the direction into knot |p| */ + int turn_amt; /* change in pen offsets for the current cubic */ + mp_number max_coef; /* used while scaling */ + mp_number ss; + mp_number x0, x1, x2, y0, y1, y2; /* representatives of derivatives */ + mp_number t0, t1, t2; /* coefficients of polynomial for slope testing */ + mp_number du, dv, dx, dy; /* for directions of the pen and the curve */ + mp_number dx0, dy0; /* initial direction for the first cubic in the curve */ + mp_number x0a, x1a,x2a, y0a, y1a, y2a; /* intermediate values */ + mp_number t; /* where the derivative passes through zero */ + mp_number s; /* a temporary value */ + mp_number dx_m; /* signal a pertubation of dx */ + mp_number dy_m; /* signal a pertubation of dx */ + mp_number dxin_m; /* signal a pertubation of dxin */ + mp_number u0, u1, v0, v1; /* intermediate values for $d(t)$ calculation */ + int d_sign; /* sign of overall change in direction for this cubic */ + new_number(max_coef); + new_number(dxin); + new_number(dyin); + new_number(dx0); + new_number(dy0); + new_number(x0); + new_number(y0); + new_number(x1); + new_number(y1); + new_number(x2); + new_number(y2); + new_number(du); + new_number(dv); + new_number(dx); + new_number(dy); + new_number(x0a); + new_number(y0a); + new_number(x1a); + new_number(y1a); + new_number(x2a); + new_number(y2a); + new_number(t0); + new_number(t1); + new_number(t2); + new_number(u0); + new_number(u1); + new_number(v0); + new_number(v1); + new_number(dx_m); + new_number(dy_m); + new_number(dxin_m); + new_fraction(ss); + new_fraction(s); + new_fraction(t); + @<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 \quote {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|@> + free_number(ss); + free_number(s); + free_number(dxin); + free_number(dyin); + free_number(dx0); + free_number(dy0); + free_number(x0); + free_number(y0); + free_number(x1); + free_number(y1); + free_number(x2); + free_number(y2); + free_number(max_coef); + free_number(du); + free_number(dv); + free_number(dx); + free_number(dy); + free_number(x0a); + free_number(y0a); + free_number(x1a); + free_number(y1a); + free_number(x2a); + free_number(y2a); + free_number(t0); + free_number(t1); + free_number(t2); + free_number(u0); + free_number(u1); + free_number(v0); + free_number(v1); + free_number(dx_m); + free_number(dy_m); + free_number(dxin_m); + free_number(t); + 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 { + ++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|@>= +{ + mp_knot hn = mp_next_knot(h); + mp_knot hp = mp_prev_knot(h); + set_number_from_subtraction(dxin, hn->x_coord, hp->x_coord); + set_number_from_subtraction(dyin, hn->y_coord, hp->y_coord); + if (number_zero(dxin) && number_zero(dyin)) { + set_number_from_subtraction(dxin, hp->y_coord, h->y_coord); + set_number_from_subtraction(dyin, h->x_coord, hp->x_coord); + } +} +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||aw +Jackowski in tracker id 267, case 52c on Sarovar.) + +@<Advance |p| to node |q|, removing any \quote {dead} cubics...@>= +q0 = q; +do { + r = mp_next_knot(p); + if (r != p && r != q + && number_equal(p->x_coord, p->right_x) + && number_equal(p->y_coord, p->right_y) + && number_equal(p->x_coord, r->left_x) + && number_equal(p->y_coord, r->left_y) + && number_equal(p->x_coord, r->x_coord) + && number_equal(p->y_coord, r->y_coord)) { + @<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, mp_number *t); +static mp_knot mp_split_cubic_knot (MP mp, mp_knot p, mp_number *t); + +@ @c +void mp_split_cubic (MP mp, mp_knot p, mp_number *t) +{ + /* splits the cubic after |p| */ + mp_number v; /* an intermediate value */ + mp_knot q = mp_next_knot(p); + mp_knot r = mp_new_knot(mp); + mp_prev_knot(r) = p; + mp_next_knot(p) = r; + mp_prev_knot(q) = r; + mp_next_knot(r) = q; + mp_originator(r) = mp_program_code; + mp_knotstate(r) = mp_regular_knot; + mp_left_type(r) = mp_explicit_knot; + mp_right_type(r) = mp_explicit_knot; + new_number(v); + set_number_from_of_the_way(v, *t, p->right_x, q->left_x); + set_number_from_of_the_way(p->right_x, *t, p->x_coord, p->right_x); + set_number_from_of_the_way(q->left_x, *t, q->left_x, q->x_coord); + set_number_from_of_the_way(r->left_x, *t, p->right_x, v); + set_number_from_of_the_way(r->right_x, *t, v, q->left_x); + set_number_from_of_the_way(r->x_coord, *t, r->left_x, r->right_x); + set_number_from_of_the_way(v, *t, p->right_y, q->left_y); + set_number_from_of_the_way(p->right_y, *t, p->y_coord, p->right_y); + set_number_from_of_the_way(q->left_y, *t, q->left_y, q->y_coord); + set_number_from_of_the_way(r->left_y, *t, p->right_y, v); + set_number_from_of_the_way(r->right_y, *t, v, q->left_y); + set_number_from_of_the_way(r->y_coord, *t, r->left_y, r->right_y); + free_number(v); +} + +static mp_knot mp_split_cubic_knot (MP mp, mp_knot p, mp_number *t) /* can be less as we only need x y */ +{ + mp_number v; + mp_knot k = mp_new_knot(mp); + mp_knot r = mp_copy_knot(mp, mp_next_knot(p)); + mp_knot l = mp_copy_knot(mp, p); + mp_originator(k) = mp_program_code; + mp_knotstate(k) = mp_regular_knot; + mp_left_type(k) = mp_explicit_knot; + mp_right_type(k) = mp_explicit_knot; + new_number(v); + set_number_from_of_the_way(v, *t, l->right_x, r->left_x); + set_number_from_of_the_way(l->right_x, *t, l->x_coord, l->right_x); + set_number_from_of_the_way(r->left_x, *t, r->left_x, r->x_coord); + set_number_from_of_the_way(k->left_x, *t, l->right_x, v); + set_number_from_of_the_way(k->right_x, *t, v, r->left_x); + set_number_from_of_the_way(k->x_coord, *t, k->left_x, k->right_x); + set_number_from_of_the_way(v, *t, l->right_y, r->left_y); + set_number_from_of_the_way(l->right_y, *t, l->y_coord, l->right_y); + set_number_from_of_the_way(r->left_y, *t, r->left_y, r->y_coord); + set_number_from_of_the_way(k->left_y, *t, l->right_y, v); + set_number_from_of_the_way(k->right_y, *t, v, r->left_y); + set_number_from_of_the_way(k->y_coord, *t, k->left_y, k->right_y); + free_number(v); + mp_toss_knot(mp, l); + mp_toss_knot(mp, r); + return k; +} + +@ 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 = mp_next_knot(p); /* the node that disappears */ + mp_prev_knot(q) = mp_next_knot(p); + mp_next_knot(p) = mp_next_knot(q); + number_clone(p->right_x, q->right_x); + number_clone(p->right_y, q->right_y); + /* was: mp_memory_free(q); */ + mp_toss_knot(mp, 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, int k); + +@ @c +mp_knot mp_pen_walk (MP mp, mp_knot w, int k) +{ + /* walk |k| steps around a pen from |w| */ + (void) mp; + while (k > 0) { + w = mp_next_knot(w); + --k; + } + while (k < 0) { + w = mp_prev_knot(w); + ++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. \quote {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)$. + +@ @<Prepare for derivative computations...@>= +set_number_from_subtraction(x0, p->right_x, p->x_coord); +set_number_from_subtraction(x2, q->x_coord, q->left_x); +set_number_from_subtraction(x1, q->left_x, p->right_x); +set_number_from_subtraction(y0, p->right_y, p->y_coord); +set_number_from_subtraction(y2, q->y_coord, q->left_y); +set_number_from_subtraction(y1, q->left_y, p->right_y); +{ + /* somewhat weird: these copies to absval */ + mp_number absval; + new_number_abs(absval, x1); + number_abs_clone(max_coef, x0); + if (number_greater(absval, max_coef)) { + number_clone(max_coef, absval); + } + number_abs_clone(absval, x2); + if (number_greater(absval, max_coef)) { + number_clone(max_coef, absval); + } + number_abs_clone(absval, y0); + if (number_greater(absval, max_coef)) { + number_clone(max_coef, absval); + } + number_abs_clone(absval, y1); + if (number_greater(absval, max_coef)) { + number_clone(max_coef, absval); + } + number_abs_clone(absval, y2); + if (number_greater(absval, max_coef)) { + number_clone(max_coef, absval); + } + free_number(absval); + if (number_zero(max_coef)) { + goto NOT_FOUND; + } +} +while (number_less(max_coef, fraction_half_t)) { + number_double(max_coef); + number_double(x0); + number_double(x1); + number_double(x2); + number_double(y0); + number_double(y1); + number_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, mp_number *x0, mp_number *x1, mp_number *x2, mp_number *y0, mp_number *y1, mp_number *y2, int rise, int turn_amt); + +@ @c +void mp_fin_offset_prep (MP mp, mp_knot p, mp_knot w, mp_number *x0, mp_number *x1, mp_number *x2, mp_number *y0, mp_number *y1, mp_number *y2, int rise, int turn_amt) +{ + mp_number du, dv; /* for slope calculation */ + mp_number t0, t1, t2; /* test coefficients */ + mp_number t; /* place where the derivative passes a critical slope */ + mp_number s; /* slope or reciprocal slope */ + mp_number v; /* intermediate value for updating |x0..y2| */ + mp_knot q = mp_next_knot(p); + new_number(du); + new_number(dv); + new_number(v); + new_number(t0); + new_number(t1); + new_number(t2); + new_fraction(s); + new_fraction(t); + while (1) { + mp_knot ww = rise > 0 ? mp_next_knot(w) : mp_prev_knot(w); + @<Compute (case 1) test coefficients |(t0,t1,t2)| for $d(t)$ versus $d_k$ or $d_{k-1}$@> + crossing_point(t, t0, t1, t2); + if (number_greaterequal(t, fraction_one_t)) { + if (turn_amt > 0) { + number_clone(t, fraction_one_t); + } else { + goto RETURN; + } + } + @<Split the cubic at $t$, and split off another cubic if the derivative crosses back@> + w = ww; + } + RETURN: + free_number(s); + free_number(t); + free_number(du); + free_number(dv); + free_number(v); + free_number(t0); + free_number(t1); + free_number(t2); +} + +@ 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 (case 1) test coefficients |(t0,t1,t2)| for $d(t)$ versus $d_k$ or $d_{k-1}$...@>= +{ + mp_number abs_du, abs_dv; + new_number(abs_du); + new_number(abs_dv); + set_number_from_subtraction(du, ww->x_coord, w->x_coord); + set_number_from_subtraction(dv, ww->y_coord, w->y_coord); + number_abs_clone(abs_du, du); + number_abs_clone(abs_dv, dv); + if (number_greaterequal(abs_du, abs_dv)) { + mp_number r1; + new_fraction(r1); + make_fraction(s, dv, du); + take_fraction(r1, *x0, s); + set_number_from_subtraction(t0, r1, *y0); + take_fraction(r1, *x1, s); + set_number_from_subtraction(t1, r1, *y1); + take_fraction(r1, *x2, s); + set_number_from_subtraction(t2, r1, *y2); + if (number_negative(du)) { + number_negate(t0); + number_negate(t1); + number_negate(t2); + } + free_number(r1); + } else { + mp_number r1; + new_fraction(r1); + make_fraction(s, du, dv); + take_fraction(r1, *y0, s); + set_number_from_subtraction(t0, *x0, r1); + take_fraction(r1, *y1, s); + set_number_from_subtraction(t1, *x1, r1); + take_fraction(r1, *y2, s); + set_number_from_subtraction(t2, *x2, r1); + if (number_negative(dv)) { + number_negate(t0); + number_negate(t1); + number_negate(t2); + } + free_number(r1); + } + free_number(abs_du); + free_number(abs_dv); + if (number_negative(t0)) { + set_number_to_zero(t0); /* should be positive without rounding error */ + } +} + +@ @<Compute (case 2) test coefficients |(t0,t1,t2)| for $d(t)$ versus $d_k$ or $d_{k-1}$...@>= +{ + mp_number abs_du, abs_dv; + new_number(abs_du); + new_number(abs_dv); + set_number_from_subtraction(du, ww->x_coord, w->x_coord); + set_number_from_subtraction(dv, ww->y_coord, w->y_coord); + number_abs_clone(abs_du, du); + number_abs_clone(abs_dv, dv); + if (number_greaterequal(abs_du, abs_dv)) { + mp_number r1; + new_fraction(r1); + make_fraction(s, dv, du); + take_fraction(r1, x0, s); + set_number_from_subtraction(t0, r1, y0); + take_fraction(r1, x1, s); + set_number_from_subtraction(t1, r1, y1); + take_fraction(r1, x2, s); + set_number_from_subtraction(t2, r1, y2); + if (number_negative(du)) { + number_negate(t0); + number_negate(t1); + number_negate(t2); + } + free_number(r1); + } else { + mp_number r1; + new_fraction(r1); + make_fraction(s, du, dv); + take_fraction(r1, y0, s); + set_number_from_subtraction(t0, x0, r1); + take_fraction(r1, y1, s); + set_number_from_subtraction(t1, x1, r1); + take_fraction(r1, y2, s); + set_number_from_subtraction(t2, x2, r1); + if (number_negative(dv)) { + number_negate(t0); + number_negate(t1); + number_negate(t2); + } + free_number(r1); + } + free_number(abs_du); + free_number(abs_dv); + if (number_negative(t0)) { + set_number_to_zero(t0); /* 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; + --turn_amt; + set_number_from_of_the_way(v, t, *x0, *x1); + set_number_from_of_the_way(*x1, t, *x1, *x2); + set_number_from_of_the_way(*x0, t, v, *x1); + set_number_from_of_the_way(v, t, *y0, *y1); + set_number_from_of_the_way(*y1, t, *y1, *y2); + set_number_from_of_the_way(*y0, t, v, *y1); + if (turn_amt < 0) { + mp_number arg1, arg2, arg3; + new_number(arg1); + new_number(arg2); + new_number(arg3); + set_number_from_of_the_way(t1, t, t1, t2); + if (number_positive(t1)) { + set_number_to_zero(t1); /* without rounding error, |t1| would be |<=0| */ + } + number_negated_clone(arg2, t1); + number_negated_clone(arg3, t2); + crossing_point(t, arg1, arg2, arg3); /* arg1 is zero */ + free_number(arg1); + free_number(arg2); + free_number(arg3); + if (number_greater(t, fraction_one_t)) { + number_clone(t, fraction_one_t); + } + ++turn_amt; + if (number_equal(t,fraction_one_t) && (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; + set_number_from_of_the_way(v, t, *x1, *x2); + set_number_from_of_the_way(*x1, t, *x0, *x1); + set_number_from_of_the_way(*x2, t, *x1, v); + set_number_from_of_the_way(v, t, *y1, *y2); + set_number_from_of_the_way(*y1, t, *y0, *y1); + set_number_from_of_the_way(*y2, t, *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)|@>= +number_clone(dx_m, zero_t); +number_clone(dy_m, zero_t); +/* todo: just if else and test before assignment */ +number_clone(dx, x0); +number_clone(dy, y0); +if (number_zero(dx) && number_zero(dy)) { + number_clone(dx, x1); + number_clone(dy, y1); + if (number_zero(dx) && number_zero(dy)) { + number_clone(dx, x2); + number_clone(dy, y2); + } +} +if (p == c) { + number_clone(dx0, dx); + number_clone(dy0, dy); +} + +@ @<Find the final direction |(dxin,dyin)|@>= +number_clone(dxin, x2); +number_clone(dyin, y2); +if (number_zero(dxin) && number_zero(dyin)) { + number_clone(dxin, x1); + number_clone(dyin, y1); + if (number_zero(dxin) && number_zero(dyin)) { + number_clone(dxin, x0); + number_clone(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, ab_vs_cd(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 int mp_get_turn_amt (MP mp, mp_knot w, mp_number *dx, mp_number *dy, int ccw); + +@ @c +int mp_get_turn_amt (MP mp, mp_knot w, mp_number *dx, mp_number *dy, int ccw) +{ + int s = 0; /* turn amount so far */ + mp_number arg1, arg2; + new_number(arg1); + new_number(arg2); + if (ccw) { + int t; + mp_knot ww = mp_next_knot(w); + do { + set_number_from_subtraction(arg1, ww->x_coord, w->x_coord); + set_number_from_subtraction(arg2, ww->y_coord, w->y_coord); + t = ab_vs_cd(*dy, arg1, *dx, arg2); + if (t < 0) { + break; + } else { + ++s; + w = ww; + ww = mp_next_knot(ww); + } + } while (t > 0); + } else { + mp_knot ww = mp_prev_knot(w); + set_number_from_subtraction(arg1, w->x_coord, ww->x_coord); + set_number_from_subtraction(arg2, w->y_coord, ww->y_coord); + while (ab_vs_cd(*dy, arg1, *dx, arg2) < 0) { + --s; + w = ww; + ww = mp_prev_knot(ww); + set_number_from_subtraction(arg1, w->x_coord, ww->x_coord); + set_number_from_subtraction(arg2, w->y_coord, ww->y_coord); + } + } + free_number(arg1); + free_number(arg2); + 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|. + +@<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 { + mp_knot_info(c) += k_needed; + while (w0 != h) { + mp_knot_info(c) += 1; + w0 = mp_next_knot(w0); + } + while (mp_knot_info(c) <= zero_off - n) { + mp_knot_info(c) += n; + } + while (mp_knot_info(c) > zero_off) { + mp_knot_info(c) -= n; + } + ; + if ((mp_knot_info(c) != zero_off) && ab_vs_cd(dy0, dxin, dx0, dyin) >= 0) { + mp_knot_info(c) += 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 (case 2) test coefficients |(t0,t1,t2)| for $d(t)$ versus $d_k$ or $d_{k-1}$@> +@<Find the first |t| where $d(t)$ crosses $d_{k-1}$ or set |t:=fraction_one+1|@> +if (number_greater(t, fraction_one_t)) { + 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); + set_number_from_of_the_way(x1a, t, x0, x1); + set_number_from_of_the_way(x1, t, x1, x2); + set_number_from_of_the_way(x2a, t, x1a, x1); + set_number_from_of_the_way(y1a, t, y0, y1); + set_number_from_of_the_way(y1, t, y1, y2); + set_number_from_of_the_way(y2a, t, y1a, y1); + mp_fin_offset_prep (mp, p, w, &x0, &x1a, &x2a, &y0, &y1a, &y2a, 1, 0); + number_clone(x0, x2a); + number_clone(y0, y2a); + mp_knot_info(r) = zero_off - 1; + if (turn_amt >= 0) { + mp_number arg1, arg2, arg3; + new_number(arg1); + new_number(arg2); + new_number(arg3); + set_number_from_of_the_way(t1, t, t1, t2); + if (number_positive(t1)) { + set_number_to_zero(t1); + } + number_negated_clone(arg2, t1); + number_negated_clone(arg3, t2); + crossing_point(t, arg1, arg2, arg3); + free_number(arg1); + free_number(arg2); + free_number(arg3); + if (number_greater(t, fraction_one_t)) { + number_clone(t, fraction_one_t); + } + @<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; +set_number_from_of_the_way(x1a, t, x1, x2); +set_number_from_of_the_way(x1, t, x0, x1); +set_number_from_of_the_way(x0a, t, x1, x1a); +set_number_from_of_the_way(y1a, t, y1, y2); +set_number_from_of_the_way(y1, t, y0, y1); +set_number_from_of_the_way(y0a, t, y1, y1a); +mp_fin_offset_prep (mp, mp_next_knot(r), w, &x0a, &x1a, &x2, &y0a, &y1a, &y2, 1, turn_amt); +number_clone(x2, x0a); +number_clone(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...@>= +crossing_point(t, t0, t1, t2); +if (turn_amt >= 0) { + if (number_negative(t2)) { + number_clone(t, fraction_one_t); + number_add_scaled(t, 1); + } else { + mp_number tmp, arg1, r1; + new_fraction(r1); + new_number(tmp); + new_number(arg1); + set_number_from_of_the_way(u0, t, x0, x1); + set_number_from_of_the_way(u1, t, x1, x2); + set_number_from_of_the_way(tmp, t, u0, u1); + number_negated_clone(arg1, du); + take_fraction(ss, arg1, tmp); + set_number_from_of_the_way(v0, t, y0, y1); + set_number_from_of_the_way(v1, t, y1, y2); + set_number_from_of_the_way(tmp, t, v0, v1); + number_negated_clone(arg1, dv); + take_fraction(r1, arg1, tmp); + number_add(ss, r1); + free_number(tmp); + if (number_negative(ss)) { + number_clone(t, fraction_one_t); + number_add_scaled(t, 1); + } + free_number(arg1); + free_number(r1); + } +} else if (number_greater(t, fraction_one_t)) { + number_clone(t, fraction_one_t); +} + +@ 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|@>= +{ + int sign = ab_vs_cd(dx, dyin, dxin, dy); + if (sign < 0) { + d_sign = -1; + } else if (sign == 0) { + d_sign = 0; + } else { + d_sign = 1; + } +} +if (d_sign == 0) { + @<Check rotation direction based on node position@> +} +if (d_sign == 0) { + if (number_zero(dx)) { + d_sign = number_positive(dy) ? 1 : -1; + } else { + d_sign = number_positive(dx) ? 1 : -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 (number_negative(ss)) { + 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@>= +{ + int t; + set_number_from_subtraction(u0, q->x_coord, p->x_coord); + set_number_from_subtraction(u1, q->y_coord, p->y_coord); + t = ab_vs_cd(dx, u1, u0, dy) + ab_vs_cd(u0, dyin, dxin, u1); + // number_half(t); + if (t < 0) { + d_sign = -1; + } else if (t == 0) { + d_sign = 0; + } else { + d_sign = 1; + } +} + +@ 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...@>= +{ + mp_number r1, r2, arg1; + new_number(arg1); + new_fraction(r1); + new_fraction(r2); + take_fraction(r1, x0, y2); + take_fraction(r2, x2, y0); + number_half(r1); + number_half(r2); + set_number_from_subtraction(t0, r1, r2); + set_number_from_addition(arg1, y0, y2); + take_fraction(r1, x1, arg1); + set_number_from_addition(arg1, x0, x2); + /*|take_fraction(r1, y1, arg1);|*//* The old one, is it correct ?*/ + take_fraction(r2, y1, arg1); + number_half(r1); + number_half(r2); + set_number_from_subtraction(t1, r1, r2); + free_number(arg1); + free_number(r1); + free_number(r2); +} +if (number_zero(t0)) { + set_number_from_scaled(t0, d_sign); /* path reversal always negates |d_sign| */ +} +if (number_positive(t0)) { + mp_number arg3; + new_number(arg3); + number_negated_clone(arg3, t0); + crossing_point(t, t0, t1, arg3); + free_number(arg3); + set_number_from_of_the_way(u0, t, x0, x1); + set_number_from_of_the_way(u1, t, x1, x2); + set_number_from_of_the_way(v0, t, y0, y1); + set_number_from_of_the_way(v1, t, y1, y2); +} else { + mp_number arg1; + new_number(arg1); + number_negated_clone(arg1, t0); + crossing_point(t, arg1, t1, t0); + free_number(arg1); + set_number_from_of_the_way(u0, t, x2, x1); + set_number_from_of_the_way(u1, t, x1, x0); + set_number_from_of_the_way(v0, t, y2, y1); + set_number_from_of_the_way(v1, t, y1, y0); +} +{ + mp_number tmp1, tmp2, r1, r2, arg1; + new_fraction(r1); + new_fraction(r2); + new_number(arg1); + new_number(tmp1); + new_number(tmp2); + set_number_from_of_the_way(tmp1, t, u0, u1); + set_number_from_of_the_way(tmp2, t, v0, v1); + set_number_from_addition(arg1, x0, x2); + take_fraction(r1, arg1, tmp1); + set_number_from_addition(arg1, y0, y2); + take_fraction(r2, arg1, tmp2); + set_number_from_addition(ss, r1, r2); + free_number(arg1); + free_number(r1); + free_number(r2); + free_number(tmp1); + free_number(tmp2); +} + +@ 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 w; /* the current pen offset */ + mp_knot p = cur_spec; /* list traversal */ + mp_print_diagnostic(mp, "Envelope spec", s, 1); + w = mp_pen_walk(mp, cur_pen, mp->spec_offset); + mp_print_ln(mp); + mp_print_two(mp, &(cur_spec->x_coord), &(cur_spec->y_coord)); + mp_print_str(mp, " % beginning with offset "); + mp_print_two(mp, &(w->x_coord), &(w->y_coord)); + do { + while (1) { + mp_knot 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, 1); +} + +@ @<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_str(mp, " % "); +if (mp_knot_info(p) > zero_off) { + mp_print_str(mp, "counter"); +} +mp_print_str(mp, "clockwise to offset "); +mp_print_two(mp, &(w->x_coord), &(w->y_coord)); + +@ @<Print the cubic between |p| and |q|@>= +mp_print_nl(mp, " .. controls "); +mp_print_two(mp, &(p->right_x), &(p->right_y)); +mp_print_str(mp, " and "); +mp_print_two(mp, &(q->left_x), &(q->left_y)); +mp_print_nl(mp, " .. "); +mp_print_two(mp, &(q->x_coord), &(q->y_coord)); + +@ 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 |linejoin| and |miterlimit| parameters control the treatment of points where the +pen offset changes, and |linecap| 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, int linejoin, int linecap, mp_number *miterlimit) +{ + mp_knot p, q, r, q0; /* for manipulating the path */ + mp_knot w, w0; /* the pen knot for the current offset */ + int k, k0; /* controls pen edge insertion */ + mp_number qx, qy; /* unshifted coordinates of |q| */ + mp_fraction dxin, dyin, dxout, dyout; /* directions at |q| when square or mitered */ + int join_type = 0; /* codes |0..3| for mitered, round, beveled, or square */ + @<Other local variables for |make_envelope|@> + new_number(max_ht); + new_number(tmp); + new_fraction(dxin); + new_fraction(dyin); + new_fraction(dxout); + new_fraction(dyout); + mp->spec_p1 = NULL; + mp->spec_p2 = NULL; + new_number(qx); + new_number(qy); + @<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; + number_clone(qx, q->x_coord); + number_clone(qy, q->y_coord); + 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)) { + mp_number xtot, ytot; + new_number(xtot); + new_number(ytot); + set_number_from_addition(xtot, qx, w->x_coord); + set_number_from_addition(ytot, qy, w->y_coord); + q = mp_insert_knot(mp, q, &xtot, &ytot); + free_number(xtot); + free_number(ytot); + } + } + 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); + free_number(max_ht); + free_number(tmp); + free_number(qx); + free_number(qy); + free_number(dxin); + free_number(dyin); + free_number(dxout); + free_number(dyout); + return c; +} + +@ @<Use |offset_prep| to compute the envelope spec then walk |h| around to...@>= +c = mp_offset_prep (mp, c, h); +if (number_positive(internal_value(mp_tracing_specs_internal))) { + 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; /* mp_beveled_linejoin_code */ +} else { + if ((q != mp->spec_p1) && (q != mp->spec_p2)) { + join_type = linejoin; + } else if (linecap == mp_squared_linecap_code) { + join_type = 3; /* mp_weird_linejoin_code */ + } else { + join_type = 2 - linecap; /* mp_beveled_linejoin_code - linecap */ + } + if ((join_type == 0) || (join_type == 3)) { /* mp_mitered_linejoin_code || mp_weird_linejoin_code */ + @<Set the incoming and outgoing directions at |q|; in case of degeneracy set |join_type:=2|@> + if (join_type == 0) { /* mp_mitered_linejoin_code */ + @<If |miterlimit| is less than the secant of half the angle at |q| then set |join_type:=2|@> + } + } +} + +@ @<If |miterlimit| is less than the secant of half the angle at |q|...@>= +mp_number r1, r2; +new_fraction(r1); +new_fraction(r2); +take_fraction(r1, dxin, dxout); +take_fraction(r2, dyin, dyout); +number_add(r1, r2); +number_half(r1); +number_add(r1, fraction_half_t); +take_fraction(tmp, *miterlimit, r1); +if (number_less(tmp, unity_t)) { + mp_number ret; + new_number(ret); + take_scaled(ret, *miterlimit, tmp); + if (number_less(ret, unity_t)) { + join_type = 2; + } + free_number(ret); +} +free_number(r1); +free_number(r2); + +@ @<Other local variables for |make_envelope|@>= +mp_number 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|@>= +number_add(p->right_x, w->x_coord); +number_add(p->right_y, w->y_coord); +number_add(q->left_x, w->x_coord); +number_add(q->left_y, w->y_coord); +number_add(q->x_coord, w->x_coord); +number_add(q->y_coord, w->y_coord); +mp_left_type(q) = mp_explicit_knot; +mp_right_type(q) = mp_explicit_knot; + +@ @<Step |w| and move |k| one step closer to |zero_off|@>= +if (k > zero_off) { + w = mp_next_knot(w); + --k; +} else { + w = mp_prev_knot(w); + ++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 \quote {yet to be examined.} + +@<Declarations@>= +static mp_knot mp_insert_knot (MP mp, mp_knot q, mp_number *x, mp_number *y); + +@ @c +mp_knot mp_insert_knot (MP mp, mp_knot q, mp_number *x, mp_number *y) +{ + /* returns the inserted knot */ + mp_knot r = mp_new_knot(mp); + mp_knot n = mp_next_knot(q); + mp_next_knot(r) = n; + mp_prev_knot(n) = r; + mp_prev_knot(r) = q; + mp_next_knot(q) = r; + number_clone(r->right_x, q->right_x); + number_clone(r->right_y, q->right_y); + number_clone(r->x_coord, *x); + number_clone(r->y_coord, *y); + number_clone(q->right_x, q->x_coord); + number_clone(q->right_y, q->y_coord); + number_clone(r->left_x, r->x_coord); + number_clone(r->left_y, r->y_coord); + mp_left_type(r) = mp_explicit_knot; + mp_right_type(r) = mp_explicit_knot; + mp_originator(r) = mp_program_code; + mp_knotstate(r) = mp_regular_knot; + 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) { + number_clone(r->right_x, r->x_coord); + number_clone(r->right_y, r->y_coord); + } +} + +@ For very small angles, adding a knot is unnecessary and would cause numerical +problems, so we just set |r:=NULL| in that case. + +@d near_zero_angle_k mp->math->md_near_zero_angle_t + +@<Insert a new knot |r| between |p| and |q| as required for a mitered join@>= +mp_number det; /* a determinant used for mitered join calculations */ +mp_number absdet; +mp_number r1, r2; +new_fraction(r1); +new_fraction(r2); +new_fraction(det); +new_fraction(absdet); +take_fraction(r1, dyout, dxin); +take_fraction(r2, dxout, dyin); +set_number_from_subtraction(det, r1, r2); +number_abs_clone(absdet, det); +if (number_less(absdet, near_zero_angle_k)) { + r = NULL; /* sine $<10^{-4}$ */ +} else { + mp_number xtot, ytot, xsub, ysub; + new_fraction(xsub); + new_fraction(ysub); + new_number(xtot); + new_number(ytot); + set_number_from_subtraction(tmp, q->x_coord, p->x_coord); + take_fraction(r1, tmp, dyout); + set_number_from_subtraction(tmp, q->y_coord, p->y_coord); + take_fraction(r2, tmp, dxout); + set_number_from_subtraction(tmp, r1, r2); + make_fraction(r1, tmp, det); + number_clone(tmp, r1); + take_fraction(xsub, tmp, dxin); + take_fraction(ysub, tmp, dyin); + set_number_from_addition(xtot, p->x_coord, xsub); + set_number_from_addition(ytot, p->y_coord, ysub); + r = mp_insert_knot(mp, p, &xtot, &ytot); + free_number(xtot); + free_number(ytot); + free_number(xsub); + free_number(ysub); +} +free_number(r1); +free_number(r2); +free_number(det); +free_number(absdet); + +@ @<Make |r| the last of two knots inserted between |p| and |q| to form a...@>= +mp_number ht_x, ht_y; /* perpendicular to the segment from |p| to |q| */ +mp_number ht_x_abs, ht_y_abs; /* absolutes */ +mp_number xtot, ytot, xsub, ysub; +new_fraction(xsub); +new_fraction(ysub); +new_number(xtot); +new_number(ytot); +new_fraction(ht_x); +new_fraction(ht_y); +new_fraction(ht_x_abs); +new_fraction(ht_y_abs); +set_number_from_subtraction(ht_x, w->y_coord, w0->y_coord); +set_number_from_subtraction(ht_y, w0->x_coord, w->x_coord); +number_abs_clone(ht_x_abs, ht_x); +number_abs_clone(ht_y_abs, ht_y); +while (number_less(ht_x_abs, fraction_half_t) && number_less(ht_y_abs, fraction_half_t)) { + number_double(ht_x); + number_double(ht_y); + number_abs_clone(ht_x_abs, ht_x); + number_abs_clone(ht_y_abs, ht_y); +} +@<Scan the pen polygon between |w0| and |w| and make |max_ht| the range dot product with |(ht_x,ht_y)|@> +{ + mp_number r1 ,r2; + new_fraction(r1); + new_fraction(r2); + take_fraction(r1, dxin, ht_x); + take_fraction(r2, dyin, ht_y); + number_add(r1, r2); + make_fraction(tmp, max_ht, r1); + free_number(r1); + free_number(r2); +} +take_fraction(xsub, tmp, dxin); +take_fraction(ysub, tmp, dyin); +set_number_from_addition(xtot, p->x_coord, xsub); +set_number_from_addition(ytot, p->y_coord, ysub); +r = mp_insert_knot(mp, p, &xtot, &ytot); +{ + mp_number r1 ,r2; + new_fraction(r1); + new_fraction(r2); + take_fraction(r1, dxout, ht_x); + take_fraction(r2, dyout, ht_y); + number_add(r1, r2); + make_fraction(tmp, max_ht, r1); + free_number(r1); + free_number(r2); +} +take_fraction(xsub, tmp, dxout); +take_fraction(ysub, tmp, dyout); +set_number_from_addition(xtot, q->x_coord, xsub); +set_number_from_addition(ytot, q->y_coord, ysub); +r = mp_insert_knot(mp, r, &xtot, &ytot); +free_number(xsub); +free_number(ysub); +free_number(xtot); +free_number(ytot); +free_number(ht_x); +free_number(ht_y); +free_number(ht_x_abs); +free_number(ht_y_abs); + +@ @<Other local variables for |make_envelope|@>= +mp_number max_ht; /* maximum height of the pen polygon above the |w0|-|w| line */ +int 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...@>= +set_number_to_zero(max_ht); +kk = zero_off; +ww = w; +while (1) { + @<Step |ww| and move |kk| one step closer to |k0|@> + if (kk == k0) { + break; + } else { + mp_number r1, r2; + new_fraction(r1); + new_fraction(r2); + set_number_from_subtraction(tmp, ww->x_coord, w0->x_coord); + take_fraction(r1, tmp, ht_x); + set_number_from_subtraction(tmp, ww->y_coord, w0->y_coord); + take_fraction(r2, tmp, ht_y); + set_number_from_addition(tmp, r1, r2); + free_number(r1); + free_number(r2); + if (number_greater(tmp, max_ht)) { + number_clone(max_ht, tmp); + } + } +} + +@ @<Step |ww| and move |kk| one step closer to |k0|@>= +if (kk > k0) { + ww = mp_next_knot(ww); + --kk; +} else { + ww = mp_prev_knot(ww); + ++kk; +} + +@ @<If endpoint, double the path |c|, and set |spec_p1| and |spec_p2|@>= +if (mp_left_type(c) == mp_endpoint_knot) { + mp->spec_p1 = mp_htap_ypoc(mp, c); + mp->spec_p2 = mp->path_tail; + mp_originator(mp->spec_p1) = mp_program_code; + mp_knotstate(mp->spec_p1) = mp_regular_knot; + mp_prev_knot(mp->spec_p1) = mp_next_knot(mp->spec_p2); + mp_next_knot(mp->spec_p2) = mp_next_knot(mp->spec_p1); + mp_prev_knot(c) = 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_knotstate(mp->spec_p2) = mp_regular_knot; + 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_knot; +mp_right_type(c) = mp_explicit_knot; +number_clone(c->left_x, c->x_coord); +number_clone(c->left_y, c->y_coord); +number_clone(c->right_x, c->x_coord); +number_clone(c->right_y, c->y_coord); + +@ 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...@>= +set_number_from_subtraction(dxin, q->x_coord, q->left_x); +set_number_from_subtraction(dyin, q->y_coord, q->left_y); +if (number_zero(dxin) && number_zero(dyin)) { + set_number_from_subtraction(dxin, q->x_coord, p->right_x); + set_number_from_subtraction(dyin, q->y_coord, p->right_y); + if (number_zero(dxin) && number_zero(dyin)) { + set_number_from_subtraction(dxin, q->x_coord, p->x_coord); + set_number_from_subtraction(dyin, q->y_coord, p->y_coord); + if (p != c) { + /* the coordinates of |p| have been offset by |w| */ + number_add(dxin, w->x_coord); + number_add(dyin, w->y_coord); + } + } +} +pyth_add(tmp, dxin, dyin); +if (number_zero(tmp)) { + join_type = 2; +} else { + mp_number r1; + new_fraction(r1); + make_fraction(r1, dxin, tmp); + number_clone(dxin, r1); + make_fraction(r1, dyin, tmp); + number_clone(dyin, r1); + free_number(r1); + @<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|@>= +set_number_from_subtraction(dxout, q->right_x, q->x_coord); +set_number_from_subtraction(dyout, q->right_y, q->y_coord); +if (number_zero(dxout) && number_zero(dyout)) { + r = mp_next_knot(q); + set_number_from_subtraction(dxout, r->left_x, q->x_coord); + set_number_from_subtraction(dyout, r->left_y, q->y_coord); + if (number_zero(dxout) && number_zero(dyout)) { + set_number_from_subtraction(dxout, r->x_coord, q->x_coord); + set_number_from_subtraction(dyout, r->y_coord, q->y_coord); + } +} +if (q == c) { + number_subtract(dxout, h->x_coord); + number_subtract(dyout, h->y_coord); +} +pyth_add(tmp, dxout, dyout); +if (number_zero(tmp)) { + /* |mp_confusion(mp, "degenerate spec");| */ + @:this can't happen degerate spec}{\quad degenerate spec@> + /* + + But apparently, it actually can happen. The test case is this: + + path p; + linejoin := mitered; + p:= (10,0)..(0,10)..(-10,0)..(0,-10)..cycle; + addto currentpicture contour p withpen pensquare; + + The reason for failure here is the addition of |r != q| in revision + 1757 in \quote {Advance |p| to node |q|, removing any ``dead} cubics'', + which itself was needed to fix a bug with disappearing knots in a + path that was rotated exactly 45 degrees (luatex.org bug 530). + */ +} else { + mp_number r1; + new_fraction(r1); + make_fraction(r1, dxout, tmp); + number_clone(dxout, r1); + make_fraction(r1, dyout, tmp); + number_clone(dyout, r1); + free_number(r1); +} + +@* 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 \quote {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 \quote {due east.} + +@c +static void mp_find_direction_time (MP mp, mp_number *ret, mp_number *x_orig, mp_number *y_orig, mp_knot h) +{ + mp_number max; /* $\max\bigl(\vert x\vert,\vert y\vert\bigr)$ */ + mp_knot p, q; /* for list traversal */ + mp_number n; /* the direction time at knot |p| */ + mp_number tt; /* the direction time within a cubic */ + mp_number abs_x, abs_y; /* Other local variables for |find_direction_time| */ + mp_number x1, x2, x3, y1, y2, y3; /* multiples of rotated derivatives */ + mp_number phi; /* angles of exit and entry at a knot */ + mp_number t; /* temp storage */ + mp_number x, y; + new_number(max); + new_number(x1); + new_number(x2); + new_number(x3); + new_number(y1); + new_number(y2); + new_number(y3); + new_fraction(t); + new_angle(phi); + set_number_to_zero(*ret); /* just in case */ + new_number(x); + new_number(y); + new_number(abs_x); + new_number(abs_y); + new_number(n); + new_fraction(tt); + number_clone(x, *x_orig); + number_clone(y, *y_orig); + number_abs_clone(abs_x, *x_orig); + number_abs_clone(abs_y, *y_orig); + /* + Normalize the given direction for better accuracy; but |return| with zero + result if it's zero + */ + if (number_less(abs_x, abs_y)) { + mp_number r1; + new_fraction(r1); + make_fraction(r1, x, abs_y); + number_clone(x, r1); + free_number(r1); + if (number_positive(y)) { + number_clone(y, fraction_one_t); + } else { + number_negated_clone(y, fraction_one_t); + } + } else if (number_zero(x)) { + goto FREE; + } else { + mp_number r1; + new_fraction(r1); + make_fraction(r1, y, abs_x); + number_clone(y, r1); + free_number(r1); + if (number_positive(x)) { + number_clone(x, fraction_one_t); + } else { + number_negated_clone(x, fraction_one_t); + } + } + p = h; + while (1) { + if (mp_right_type(p) == mp_endpoint_knot) { + break; + } else { + 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; + number_add(n, unity_t); + } + } + set_number_to_unity(*ret); + number_negate(*ret); + goto FREE; + FOUND: + set_number_from_addition(*ret, n, tt); + goto FREE; + FREE: + free_number(x); + free_number(y); + free_number(abs_x); + free_number(abs_y); + /* Free local variables for |find_direction_time| */ + free_number(x1); + free_number(x2); + free_number(x3); + free_number(y1); + free_number(y2); + free_number(y3); + free_number(t); + free_number(phi); + free_number(n); + free_number(max); + free_number(tt); +} + +@ 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 scale-d 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...@>= +set_number_to_zero(tt); +/* + Set local variables |x1,x2,x3| and |y1,y2,y3| to multiples of the control + points of the rotated derivatives. +*/ +{ + mp_number absval; + new_number(absval); + set_number_from_subtraction(x1, p->right_x, p->x_coord); + set_number_from_subtraction(x2, q->left_x, p->right_x); + set_number_from_subtraction(x3, q->x_coord, q->left_x); + set_number_from_subtraction(y1, p->right_y, p->y_coord); + set_number_from_subtraction(y2, q->left_y, p->right_y); + set_number_from_subtraction(y3, q->y_coord, q->left_y); + number_abs_clone(absval, x2); + number_abs_clone(max, x1); + if (number_greater(absval, max)) { + number_clone(max, absval); + } + number_abs_clone(absval, x3); + if (number_greater(absval, max)) { + number_clone(max, absval); + } + number_abs_clone(absval, y1); + if (number_greater(absval, max)) { + number_clone(max, absval); + } + number_abs_clone(absval, y2); + if (number_greater(absval, max)) { + number_clone(max, absval); + } + number_abs_clone(absval, y3); + if (number_greater(absval, max)) { + number_clone(max, absval); + } + free_number(absval); + if (number_zero(max)) { + goto FOUND; + } + while (number_less(max, fraction_half_t)) { + number_double(max); + number_double(x1); + number_double(x2); + number_double(x3); + number_double(y1); + number_double(y2); + number_double(y3); + } + number_clone(t, x1); + { + mp_number r1, r2; + new_fraction(r1); + new_fraction(r2); + take_fraction(r1, x1, x); + take_fraction(r2, y1, y); + set_number_from_addition(x1, r1, r2); + take_fraction(r1, y1, x); + take_fraction(r2, t, y); + set_number_from_subtraction(y1, r1, r2); + number_clone(t, x2); + take_fraction(r1, x2, x); + take_fraction(r2, y2, y); + set_number_from_addition(x2, r1, r2); + take_fraction(r1, y2, x); + take_fraction(r2, t, y); + set_number_from_subtraction(y2, r1, r2); + number_clone(t, x3); + take_fraction(r1, x3 ,x); + take_fraction(r2, y3, y); + set_number_from_addition(x3, r1, r2); + take_fraction(r1, y3, x); + take_fraction(r2, t, y); + set_number_from_subtraction(y3, r1, r2); + free_number(r1); + free_number(r2); + } +} +if (number_zero(y1) && (number_zero(x1) || number_positive(x1))) { + goto FOUND; +} +if (number_positive(n)) { + /* Exit to |found| if an eastward direction occurs at knot |p| */ + mp_number theta; + mp_number tmp; + new_angle(theta); + n_arg(theta, x1, y1); + new_angle(tmp); + set_number_from_subtraction(tmp, theta, one_eighty_deg_t); + if (number_nonnegative(theta) && number_nonpositive(phi) && number_greaterequal(phi, tmp)) { + free_number(tmp); + free_number(theta); + goto FOUND; + } + set_number_from_addition(tmp, theta, one_eighty_deg_t); + if (number_nonpositive(theta) && number_nonnegative(phi) && number_lessequal(phi, tmp)) { + free_number(tmp); + free_number(theta); + goto FOUND; + } + free_number(tmp); + free_number(theta); + if (p == h) { + break; + } +} +if (number_nonzero(x3) || number_nonzero(y3)) { + n_arg(phi, 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|. 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. +*/ +if (number_negative(x1) && number_negative(x2) && number_negative(x3)) { + goto DONE; +} +{ + if (ab_vs_cd(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 (ab_vs_cd(y1, y2, zero_t, zero_t) < 0) { + mp_number tmp, arg2; + new_number(tmp); + new_number(arg2); + set_number_from_subtraction(arg2, y1, y2); + make_fraction(t, y1, arg2); + free_number(arg2); + set_number_from_of_the_way(x1, t, x1, x2); + set_number_from_of_the_way(x2, t, x2, x3); + set_number_from_of_the_way(tmp, t, x1, x2); + if (number_zero(tmp) || number_positive(tmp)) { + free_number(tmp); + number_clone(tt, t); + fraction_to_round_scaled(tt); + goto FOUND; + } else { + free_number(tmp); + } + } else if (number_zero(y3)) { + if (number_zero(y1)) { + /* + Exit to |found| if the derivative $B(x_1,x_2,x_3;t)$ becomes |>=0| 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. + */ + { + mp_number arg1, arg2, arg3; + new_number(arg1); + new_number(arg2); + new_number(arg3); + number_negated_clone(arg1, x1); + number_negated_clone(arg2, x2); + number_negated_clone(arg3, x3); + crossing_point(t, arg1, arg2, arg3); + free_number(arg1); + free_number(arg2); + free_number(arg3); + if (number_lessequal(t, fraction_one_t)) { + number_clone(tt, t); + fraction_to_round_scaled(tt); + goto FOUND; + } else if (ab_vs_cd(x1, x3, x2, x2) <= 0) { + mp_number arg2; + new_number(arg2); + set_number_from_subtraction(arg2, x1, x2); + make_fraction(t, x1, arg2); + free_number(arg2); + number_clone(tt, t); + fraction_to_round_scaled(tt); + goto FOUND; + } + } + } else if (number_zero(x3) || number_positive(x3)) { + set_number_to_unity(tt); + goto FOUND; + } + } + goto DONE; + } + } +} +if (number_zero(y1) || number_negative(y1)) { + if (number_negative(y1)) { + number_negate(y1); + number_negate(y2); + number_negate(y3); + } else if (number_positive(y2)) { + number_negate(y2); + number_negate(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$ + 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. +*/ +crossing_point(t, y1, y2, y3); +if (number_greater(t, fraction_one_t)) { + goto DONE; +} +set_number_from_of_the_way(y2, t, y2, y3); +set_number_from_of_the_way(x1, t, x1, x2); +set_number_from_of_the_way(x2, t, x2, x3); +set_number_from_of_the_way(x1, t, x1, x2); +if (number_zero(x1) || number_positive(x1)) { + number_clone(tt, t); + fraction_to_round_scaled(tt); + goto FOUND; +} +if (number_positive(y2)) { + set_number_to_zero(y2); +} +number_clone(tt, t); +{ + mp_number arg1, arg2, arg3; + new_number(arg1); + new_number(arg2); + new_number(arg3); + number_negated_clone(arg2, y2); + number_negated_clone(arg3, y3); + crossing_point(t, arg1, arg2, arg3); + free_number(arg1); + free_number(arg2); + free_number(arg3); +} +if (number_greater(t, fraction_one_t)) { + goto DONE; +} else { + mp_number tmp; + new_number(tmp); + set_number_from_of_the_way(x1, t, x1, x2); + set_number_from_of_the_way(x2, t, x2, x3); + set_number_from_of_the_way(tmp, t, x1, x2); + if (number_nonnegative(tmp)) { + free_number(tmp); + set_number_from_of_the_way(t, t, tt, fraction_one_t); + number_clone(tt, t); + fraction_to_round_scaled(tt); + goto FOUND; + } + free_number(tmp); +} +DONE: + +@ 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 \quote {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...@>= +mp_number *bisect_stack; +int bisect_ptr; + +@ @<Allocate or initialize ...@>= +mp->bisect_stack = mp_memory_allocate((size_t) (bistack_size + 1) * sizeof(mp_number)); +for (int i=0; i<bistack_size + 1; i++) { + new_number(mp->bisect_stack[i]); +} + +@ @<Dealloc variables@>= +for (int i=0; i<bistack_size + 1; i++) { + free_number(mp->bisect_stack[i]); +} +mp_memory_free(mp->bisect_stack); + +@ Computation of the min and max is a tedious but fairly fast sequence of +instructions; exactly four comparisons are made in each branch. + +@<Declarations...@>= +static void mp_set_min_max (MP mp, int v); + +@ This was a macro but a function is way more efficient here. @c +void mp_set_min_max (MP mp, int v) +{ + if (number_negative(stack_1(v))) { + if (number_nonnegative (stack_3(v))) { + if (number_negative(stack_2(v))) { + set_number_from_addition(stack_min(v), stack_1(v), stack_2(v)); + } else { + number_clone(stack_min(v), stack_1(v)); + } + set_number_from_addition(stack_max(v), stack_1(v), stack_2(v)); + number_add(stack_max(v), stack_3(v)); + if (number_negative(stack_max(v))) { + set_number_to_zero(stack_max(v)); + } + } else { + set_number_from_addition(stack_min(v), stack_1(v), stack_2(v)); + number_add(stack_min(v), stack_3(v)); + if (number_greater(stack_min(v), stack_1(v))) { + number_clone(stack_min(v), stack_1(v)); + } + set_number_from_addition(stack_max(v), stack_1(v), stack_2(v)); + if (number_negative(stack_max(v))) { + set_number_to_zero(stack_max(v)); + } + } + } else if (number_nonpositive(stack_3(v))) { + if (number_positive(stack_2(v))) { + set_number_from_addition(stack_max(v), stack_1(v), stack_2(v)); + } else { + number_clone(stack_max(v), stack_1(v)); + } + set_number_from_addition(stack_min(v), stack_1(v), stack_2(v)); + number_add(stack_min(v), stack_3(v)); + if (number_positive(stack_min(v))) { + set_number_to_zero(stack_min(v)); + } + } else { + set_number_from_addition(stack_max(v), stack_1(v), stack_2(v)); + number_add(stack_max(v), stack_3(v)); + if (number_less(stack_max(v), stack_1(v))) { + number_clone(stack_max(v), stack_1(v)); + } + set_number_from_addition(stack_min(v), stack_1(v), stack_2(v)); + if (number_positive(stack_min(v))) { + set_number_to_zero(stack_min(v)); + } + } +} + +@ 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...@>= +mp_number cur_t; +mp_number cur_tt; /* controls and results of |cubic_intersection| */ +int time_to_go; /* this many backtracks before giving up */ +mp_number max_t; /* maximum of $2^{l+1}$ so far achieved */ + +@ @<Initialize table ...@>= +new_number(mp->cur_t); +new_number(mp->cur_tt); +new_number(mp->max_t); + +@ @<Dealloc ...@>= +free_number(mp->cur_t); +free_number(mp->cur_tt); +free_number(mp->max_t); + +@ 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. + +@d half(A) ((A)/2) + +@c +static int mp_cubic_intersection(MP mp, mp_knot p, mp_knot pp, int run) +{ + mp_knot q, qq; /* |mp_link(p)|, |mp_link(pp)| */ + mp_number x_two_t; /* increment bit precision */ + mp_number x_two_t_low_precision; /* check for low precision */ + mp->time_to_go = max_patience; + set_number_from_scaled(mp->max_t, 2); + new_number_clone(x_two_t, two_t); + new_number(x_two_t_low_precision); + /* added 2 bit of precision */ + number_double(x_two_t); + number_double(x_two_t); + set_number_from_double(x_two_t_low_precision, -0.5); + number_add(x_two_t_low_precision, x_two_t); + @<Initialize for intersections at level zero@> + CONTINUE: + while (1) { + /* + When we are in arbitrary precision math, low precisions can lead to + acces locations beyond the |stack_size|: in this case we say that + there is no intersection. + */ + if (((x_packet (mp->xy))+4)>bistack_size + || ((u_packet (mp->uv))+4)>bistack_size + || ((y_packet (mp->xy))+4)>bistack_size + || ((v_packet (mp->uv))+4)>bistack_size){ + set_number_from_scaled(mp->cur_t,1); + set_number_from_scaled(mp->cur_tt,1); + goto NOT_FOUND; + } + /* + Also, low precision can lead to wrong result in comparing so we check + that the level of bisection stay low, and later we will also check + that the bisection level are safe from approximations. + */ + if (number_greater(mp->max_t, x_two_t)){ + set_number_from_scaled(mp->cur_t,1); + set_number_from_scaled(mp->cur_tt,1); + goto NOT_FOUND; + } + if (number_to_scaled(mp->delx) - mp->tol <= number_to_scaled(stack_max (x_packet (mp->xy))) - number_to_scaled(stack_min (u_packet (mp->uv)))) { + if (number_to_scaled(mp->delx) + mp->tol >= number_to_scaled(stack_min (x_packet (mp->xy))) - number_to_scaled(stack_max (u_packet (mp->uv)))) { + if (number_to_scaled(mp->dely) - mp->tol <= number_to_scaled(stack_max (y_packet (mp->xy))) - number_to_scaled(stack_min (v_packet (mp->uv)))) { + if (number_to_scaled(mp->dely) + mp->tol >= number_to_scaled(stack_min (y_packet (mp->xy))) - number_to_scaled(stack_max (v_packet (mp->uv)))) { + if (number_to_scaled(mp->cur_t) >= number_to_scaled(mp->max_t)) { + if (number_equal(mp->max_t, x_two_t) || number_greater(mp->max_t, x_two_t_low_precision)) { + if (run == 1) { + /* we've done 17+2 bisections, first restore values due bit precision */ + number_divide_int(mp->cur_t,1<<2); + number_divide_int(mp->cur_tt,1<<2); + set_number_from_scaled(mp->cur_t, ((number_to_scaled(mp->cur_t) + 1)/2)); + set_number_from_scaled(mp->cur_tt, ((number_to_scaled(mp->cur_tt) + 1)/2)); +free_number(x_two_t); +free_number(x_two_t_low_precision); + return 1; + } else { + run--; + goto NOT_FOUND; + } + } + number_double(mp->max_t); + number_clone(mp->appr_t, mp->cur_t); + number_clone(mp->appr_tt, mp->cur_tt); + } + @<Subdivide for a new level of intersection@> + goto CONTINUE; + } + } + } + } + if (mp->time_to_go > 0) { + --mp->time_to_go; + } else { + /* we have added 2 bit of precision */ + number_divide_int(mp->appr_t, 1<<2); + number_divide_int(mp->appr_tt, 1<<2); + while (number_less(mp->appr_t, unity_t)) { + number_double(mp->appr_t); + number_double(mp->appr_tt); + } + number_clone(mp->cur_t, mp->appr_t); + number_clone(mp->cur_tt, mp->appr_tt); +free_number(x_two_t); +free_number(x_two_t_low_precision); + return 2; + } + NOT_FOUND: + /* Advance to the next pair |(cur_t,cur_tt)| */ + if (odd(number_to_scaled(mp->cur_tt))) { + // if (number_odd(mp->cur_tt)) { + if (odd(number_to_scaled(mp->cur_t))) { + // if (number_odd(mp->cur_t)) { + /* Descend to the previous level and |goto not_found| */ + set_number_from_scaled(mp->cur_t, half (number_to_scaled(mp->cur_t))); + set_number_from_scaled(mp->cur_tt, half (number_to_scaled(mp->cur_tt))); + if (number_to_scaled(mp->cur_t) == 0) { +free_number(x_two_t); +free_number(x_two_t_low_precision); + return 3; + } else { + mp->bisect_ptr -= int_increment; + mp->three_l -= (int) mp->tol_step; + number_clone(mp->delx, stack_dx); + number_clone(mp->dely, stack_dy); + mp->tol = number_to_scaled(stack_tol); + mp->uv = number_to_scaled(stack_uv); + mp->xy = number_to_scaled(stack_xy); + goto NOT_FOUND; + } + } else { + set_number_from_scaled(mp->cur_t, number_to_scaled(mp->cur_t) + 1); + number_add(mp->delx, stack_1(u_packet (mp->uv))); + number_add(mp->delx, stack_2(u_packet (mp->uv))); + number_add(mp->delx, stack_3(u_packet (mp->uv))); + number_add(mp->dely, stack_1(v_packet (mp->uv))); + number_add(mp->dely, stack_2(v_packet (mp->uv))); + number_add(mp->dely, stack_3(v_packet (mp->uv))); + mp->uv = mp->uv + int_packets; /* switch from |l_packets| to |r_packets| */ + set_number_from_scaled(mp->cur_tt, number_to_scaled(mp->cur_tt) - 1); + mp->xy = mp->xy - int_packets; + number_add(mp->delx, stack_1(x_packet (mp->xy))); + number_add(mp->delx, stack_2(x_packet (mp->xy))); + number_add(mp->delx, stack_3(x_packet (mp->xy))); + number_add(mp->dely, stack_1(y_packet (mp->xy))); + number_add(mp->dely, stack_2(y_packet (mp->xy))); + number_add(mp->dely, stack_3(y_packet (mp->xy))); + } + } else { + set_number_from_scaled(mp->cur_tt, number_to_scaled(mp->cur_tt) + 1); + mp->tol = mp->tol + mp->three_l; + number_subtract(mp->delx, stack_1(x_packet (mp->xy))); + number_subtract(mp->delx, stack_2(x_packet (mp->xy))); + number_subtract(mp->delx, stack_3(x_packet (mp->xy))); + number_subtract(mp->dely, stack_1(y_packet (mp->xy))); + number_subtract(mp->dely, stack_2(y_packet (mp->xy))); + number_subtract(mp->dely, stack_3(y_packet (mp->xy))); + mp->xy = mp->xy + int_packets; /* switch from |l_packets| to |r_packets| */ + } + } +free_number(x_two_t); +free_number(x_two_t_low_precision); +} + +@ 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...@>= +mp_number delx; +mp_number dely; /* the components of $\Delta=2^l(w_0-z_0)$ */ +int tol; /* bound on the uncertainty in the overlap test */ +int uv; +int xy; /* pointers to the current packets of interest */ +int three_l; /* |tol_step| times the bisection level */ +mp_number appr_t; +mp_number appr_tt; /* best approximations known to the answers */ + +@ @<Initialize table ...@>= +new_number(mp->delx); +new_number(mp->dely); +new_number(mp->appr_t); +new_number(mp->appr_tt); + +@ @<Dealloc...@>= +free_number(mp->delx); +free_number(mp->dely); +free_number(mp->appr_t); +free_number(mp->appr_tt); + +@ 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; +set_number_from_subtraction(u1r, p->right_x, p->x_coord); +set_number_from_subtraction(u2r, q->left_x, p->right_x); +set_number_from_subtraction(u3r, q->x_coord, q->left_x); +mp_set_min_max(mp, ur_packet); +set_number_from_subtraction(v1r, p->right_y, p->y_coord); +set_number_from_subtraction(v2r, q->left_y, p->right_y); +set_number_from_subtraction(v3r, q->y_coord, q->left_y); +mp_set_min_max(mp, vr_packet); +set_number_from_subtraction(x1r, pp->right_x, pp->x_coord); +set_number_from_subtraction(x2r, qq->left_x, pp->right_x); +set_number_from_subtraction(x3r, qq->x_coord, qq->left_x); +mp_set_min_max(mp, xr_packet); +set_number_from_subtraction(y1r, pp->right_y, pp->y_coord); +set_number_from_subtraction(y2r, qq->left_y, pp->right_y); +set_number_from_subtraction(y3r, qq->y_coord, qq->left_y); +mp_set_min_max(mp, yr_packet); +set_number_from_subtraction(mp->delx, p->x_coord, pp->x_coord); +set_number_from_subtraction(mp->dely, p->y_coord, pp->y_coord); +mp->tol = 0; +mp->uv = r_packets; +mp->xy = r_packets; +mp->three_l = 0; +set_number_from_scaled(mp->cur_t, 1); +set_number_from_scaled(mp->cur_tt, 1); + +@ @<Subdivide for a new level of intersection@>= +number_clone(stack_dx, mp->delx); +number_clone(stack_dy, mp->dely); +set_number_from_scaled(stack_tol, mp->tol); +set_number_from_scaled(stack_uv, mp->uv); +set_number_from_scaled(stack_xy, mp->xy); +mp->bisect_ptr = mp->bisect_ptr + int_increment; +number_double(mp->cur_t); +number_double(mp->cur_tt); +number_clone(u1l, stack_1(u_packet (mp->uv))); +number_clone(u3r, stack_3(u_packet (mp->uv))); +set_number_half_from_addition(u2l, u1l, stack_2(u_packet(mp->uv))); +set_number_half_from_addition(u2r, u3r, stack_2(u_packet(mp->uv))); +set_number_half_from_addition(u3l, u2l, u2r); +number_clone(u1r, u3l); +mp_set_min_max(mp, ul_packet); +mp_set_min_max(mp, ur_packet); +number_clone(v1l, stack_1(v_packet (mp->uv))); +number_clone(v3r, stack_3(v_packet (mp->uv))); +set_number_half_from_addition(v2l, v1l, stack_2(v_packet(mp->uv))); +set_number_half_from_addition(v2r, v3r, stack_2(v_packet(mp->uv))); +set_number_half_from_addition(v3l, v2l, v2r); +number_clone(v1r, v3l); +mp_set_min_max(mp, vl_packet); +mp_set_min_max(mp, vr_packet); +number_clone(x1l, stack_1(x_packet (mp->xy))); +number_clone(x3r, stack_3(x_packet (mp->xy))); +set_number_half_from_addition(x2l, x1l, stack_2(x_packet(mp->xy))); +set_number_half_from_addition(x2r, x3r, stack_2(x_packet(mp->xy))); +set_number_half_from_addition(x3l, x2l, x2r); +number_clone(x1r, x3l); +mp_set_min_max(mp, xl_packet); +mp_set_min_max(mp, xr_packet); +number_clone(y1l, stack_1(y_packet (mp->xy))); +number_clone(y3r, stack_3(y_packet (mp->xy))); +set_number_half_from_addition(y2l, y1l, stack_2(y_packet(mp->xy))); +set_number_half_from_addition(y2r, y3r, stack_2(y_packet(mp->xy))); +set_number_half_from_addition(y3l, y2l, y2r); +number_clone(y1r, y3l); +mp_set_min_max(mp, yl_packet); +mp_set_min_max(mp, yr_packet); +mp->uv = l_packets; +mp->xy = l_packets; +number_double(mp->delx); +number_double(mp->dely); +mp->tol = mp->tol - mp->three_l + (int) mp->tol_step; +mp->tol += mp->tol; +mp->three_l = mp->three_l + (int) mp->tol_step; + +@ 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|. + +@d intersection_run_shift 8 + +@c +static mp_knot mp_path_intersection_add(MP mp, mp_knot list, mp_knot *last, mp_number *t, mp_number *tt) +{ + int a = number_to_scaled(*t) >> intersection_run_shift; + int aa = number_to_scaled(*tt) >> intersection_run_shift; + int b = (list ? number_to_scaled((*last)->x_coord) : -1) >> intersection_run_shift ; + int bb = (list ? number_to_scaled((*last)->y_coord) : -1) >> intersection_run_shift ; + if (a == b && aa == bb) { + /* ignore */ + } else { + /* todo: just the point as we have it */ + mp_knot k = mp_new_knot(mp); + mp_left_type(k) = mp_explicit_knot; + mp_right_type(k) = mp_explicit_knot; + number_clone(k->x_coord, *t); + number_clone(k->y_coord, *tt); + if (list) { + mp_prev_knot(k) = *last; + mp_next_knot(*last) = k; + mp_prev_knot(list) = k; + mp_next_knot(k) = list; + } else { + list = k; + mp_prev_knot(k) = k; + mp_next_knot(k) = k; + } + *last = k; + } + return list; +} + +@c +static mp_knot mp_path_intersection(MP mp, mp_knot h, mp_knot hh, int path, mp_knot *last) +{ + mp_number n, nn; /* integer parts of intersection times, minus |unity| */ + int done = 0; + mp_knot list = NULL; + mp_knot l = NULL; + mp_knot ll = NULL; + if (last) { + *last = NULL; + } + @<Change one-point paths into dead cycles@> + new_number(n); + new_number(nn); + mp->tol_step = 0; + do { + mp_knot p, pp; /* link registers that traverse the given paths */ + int t = -1; + int tt = -1; + // set_number_to_unity(n); + // number_negate(n); + number_negated_clone(n, unity_t); + p = h; + do { + if (mp_right_type(p) != mp_endpoint_knot) { + // set_number_to_unity(nn); + // number_negate(nn); + number_negated_clone(nn, unity_t); + pp = hh; + do { + if (mp_right_type(pp) != mp_endpoint_knot) { + int run = 0; + int retrials = 0; + RETRY: + ++run; + mp_cubic_intersection(mp, p, pp, run); + if (number_positive(mp->cur_t)) { + number_add(mp->cur_t, n); + number_add(mp->cur_tt, nn); + done = 1; + if (path) { + list = mp_path_intersection_add(mp, list, last, &(mp->cur_t), &(mp->cur_tt)); + if (t == number_to_scaled(mp->cur_t) && tt == number_to_scaled(mp->cur_tt)) { + if (retrials == 8) { /* is 8 okay? */ + break; + } else { + retrials += 1; + goto RETRY; + } + } else { + retrials = 0; + t = number_to_scaled(mp->cur_t); + tt = number_to_scaled(mp->cur_tt); + goto RETRY; + } + } else { + goto DONE; + } + } + } + number_add(nn, unity_t); + ll = pp; + pp = mp_next_knot(pp); + } while (pp != hh); + } + number_add(n, unity_t); + l = p; + p = mp_next_knot(p); + } while (p != h); + mp->tol_step = mp->tol_step + 3; + if (done) { + goto DONE; /* when we do all points */ + } + } while (mp->tol_step <= 3); + DONE: + if (path && l && ll && number_equal(l->x_coord, ll->x_coord) && number_equal(l->y_coord, ll->y_coord)) { + list = mp_path_intersection_add(mp, list, last, &n, &nn); + } + if (! done) { + number_negated_clone(mp->cur_t, unity_t); + number_negated_clone(mp->cur_tt, unity_t); + if (path && ! list) { + mp_knot k = mp_new_knot(mp); + number_clone(k->x_coord, mp->cur_t); + number_clone(k->y_coord, mp->cur_tt); + mp_prev_knot(k) = k; + mp_next_knot(k) = k; + list = k; + if (last) { + *last = k; + } + } + } + free_number(n); + free_number(nn); + return list; +} + +@ @<Change one-point paths...@>= +if (mp_right_type(h) == mp_endpoint_knot) { + number_clone(h->right_x, h->x_coord); + number_clone(h->left_x, h->x_coord); + number_clone(h->right_y, h->y_coord); + number_clone(h->left_y, h->y_coord); + mp_right_type(h) = mp_explicit_knot; +} +if (mp_right_type(hh) == mp_endpoint_knot) { + number_clone(hh->right_x, hh->x_coord); + number_clone(hh->left_x, hh->x_coord); + number_clone(hh->right_y, hh->y_coord); + number_clone(hh->left_y, hh->y_coord); + mp_right_type(hh) = mp_explicit_knot; +} + +@* 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 +|mp_get_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 |mp_get_indep_value(p)=s|, where +|s>0| is a \quote {serial number} reflecting the time this variable was first used in +an equation; and there is an extra field |mp_get_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 mp_get_indep_scale(A) ((mp_value_node) (A))->data.indep.scale +@d mp_set_indep_scale(A,B) ((mp_value_node) (A))->data.indep.scale = (B) +@d mp_get_indep_value(A) ((mp_value_node) (A))->data.indep.serial +@d mp_set_indep_value(A,B) ((mp_value_node) (A))->data.indep.serial = (B) + +@c +static void mp_new_indep (MP mp, mp_node p) +{ + (void) mp; + /* create a new independent variable */ + if (mp->serial_no >= max_integer) { + mp_fatal_error(mp, "Variable instance identifiers exhausted"); + } + mp_type(p) = mp_independent_type; + mp->serial_no = mp->serial_no + 1; + mp_set_indep_scale(p, 0); + mp_set_indep_value(p, mp->serial_no); +} + +@ @<Declarations@>= +static void mp_new_indep (MP mp, mp_node p); + +@ @<Glob...@>= +int 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=mp_get_dep_list(p)| points to this list, and if |k>0|, then |mp_get_dep_value(q)= +@t$\alpha_1$@>| (which is a |fraction|); |mp_get_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 +|mp_get_dep_value(q)=@t$\beta$@>| (which is |scaled|) and |mp_get_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 |mp_get_prev_dep(dep_head)=dep_head|; otherwise +|mp_link(dep_head)| points to the first dependent variable, say~|p|, and +|mp_get_prev_dep(p)=dep_head|. We have |type(p)=mp_dependent|, and |mp_get_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 |mp_get_prev_dep(r)=q|, etc. + +Dependency nodes sometimes mutate into value nodes and vice versa, so their +structures have to match. + +@d mp_get_dep_value(A) ((mp_value_node) (A))->data.n +@d mp_get_dep_list(A) ((mp_value_node) (A))->attr_head /* half of the |value| field in a |dependent| variable */ +@d mp_get_prev_dep(A) ((mp_value_node) (A))->subscr_head /* the other half; makes a doubly linked list */ +@d mp_get_dep_info(A) do_get_dep_info(mp, (A)) + +@d mp_set_dep_value(A,B) do_set_dep_value(mp,(A),&(B)) +@d mp_set_dep_list(A,B) ((mp_value_node) (A))->attr_head = (mp_node) (B) +@d mp_set_prev_dep(A,B) ((mp_value_node) (A))->subscr_head = (mp_node) (B) +@d mp_set_dep_info(A,B) ((mp_value_node) (A))->parent = (mp_node) (B) + +@c +inline static mp_node do_get_dep_info (MP mp, mp_value_node p) +{ + (void) mp; + mp_node d; + d = p->parent; /* half of the |value| field in a |dependent| variable */ + return d; +} + +inline static void do_set_dep_value (MP mp, mp_value_node p, mp_number *q) +{ + number_clone(p->data.n, *q); /* half of the |value| field in a |dependent| variable */ + p->attr_head = NULL; + p->subscr_head = NULL; +} + +@ @<Declarations...@>= +inline static mp_node do_get_dep_info (MP mp, mp_value_node p); +inline static void do_set_dep_value (MP mp, mp_value_node p, mp_number *q); + +@ @c +static mp_value_node mp_get_dep_node (MP mp) +{ + mp_value_node p = (mp_value_node) mp_new_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_value_node(mp, (mp_node) p); +} + +@ @<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); + +mp_set_link(mp->dep_head, mp->dep_head); +mp_set_prev_dep(mp->dep_head, mp->dep_head); +mp_set_dep_info(mp->dep_head, NULL); +mp_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, int t); + +@ @c +void mp_print_dependency (MP mp, mp_value_node p, int t) +{ + mp_number v; /* a coefficient */ + mp_node q; + mp_value_node pp = p; + new_number(v); + while (1) { + number_abs_clone(v, mp_get_dep_value(p)); + q = mp_get_dep_info(p); + if (q == NULL) { + /* the constant term */ + if (number_nonzero(v) || (p == pp)) { + if (number_positive(mp_get_dep_value(p)) && p != pp) { + mp_print_chr(mp, '+'); + } + print_number(mp_get_dep_value(p)); + } + return; + } + /* Print the coefficient, unless it's $\pm1.0$ */ + if (number_negative(mp_get_dep_value(p))) { + mp_print_chr(mp, '-'); + } else if (p != pp) { + mp_print_chr(mp, '+'); + } + if (t == mp_dependent_type) { + fraction_to_round_scaled(v); + } + if (! number_equal(v, unity_t)) { + print_number(v); + } + if (mp_type(q) != mp_independent_type) { + mp_confusion(mp, "dependency"); + } else { + mp_print_variable_name(mp, q); + set_number_from_scaled(v, mp_get_indep_scale(q)); + while (number_positive(v)) { + mp_print_str(mp, "*4"); + number_add_scaled(v, -2); + } + p = (mp_value_node) mp_link(p); + } + } +} + +@ The maximum absolute value of a coefficient in a given dependency list is +returned by the following simple function. + +@c +static void mp_max_coef (MP mp, mp_number *x, mp_value_node p) +{ + mp_number(absv); + new_number(absv); + set_number_to_zero(*x); + while (mp_get_dep_info(p) != NULL) { + number_abs_clone(absv, mp_get_dep_value(p)); + if (number_greater(absv, *x)) { + number_clone(*x, absv); + } + p = (mp_value_node) mp_link(p); + } + free_number(absv); +} + +@ 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 \quote {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 independent_needing_fix 0 + +@<Glob...@>= +int fix_needed; /* does at least one |independent| variable need scaling? */ +int 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 = 0; +mp->watch_coefs = 1; + +@ 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_k mp->math->md_fraction_threshold_t +@d half_fraction_threshold_k mp->math->md_half_fraction_threshold_t +@d scaled_threshold_k mp->math->md_scaled_threshold_t +@d half_scaled_threshold_k mp->math->md_half_scaled_threshold_t + +@<Declarations@>= +static mp_value_node mp_p_plus_fq (MP mp, mp_value_node p, mp_number *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, mp_number *f, + mp_value_node q, mp_variable_type t, + mp_variable_type tt +) +{ + mp_node pp, qq; /* |mp_get_dep_info(p)| and |mp_get_dep_info(q)|, respectively */ + mp_value_node r, s; /* for list manipulation */ + mp_number threshold; /* defines a neighborhood of zero */ + mp_number half_threshold; + mp_number v, vv; /* temporary registers */ + new_number(v); + new_number(vv); + if (t == mp_dependent_type) { + new_number_clone(threshold, fraction_threshold_k); + new_number_clone(half_threshold, half_fraction_threshold_k); + } else { + new_number_clone(threshold, scaled_threshold_k); + new_number_clone(half_threshold, half_scaled_threshold_k); + } + r = (mp_value_node) mp->temp_head; + pp = mp_get_dep_info(p); + qq = mp_get_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| + */ + mp_number r1; + mp_number absv; + new_fraction(r1); + new_number(absv); + if (tt == mp_dependent_type) { + take_fraction(r1, *f, mp_get_dep_value(q)); + } else { + take_scaled(r1, *f, mp_get_dep_value(q)); + } + set_number_from_addition(v, mp_get_dep_value(p), r1); + free_number(r1); + mp_set_dep_value(p, v); + s = p; + p = (mp_value_node) mp_link(p); + number_abs_clone(absv, v); + if (number_less(absv, threshold)) { + mp_free_dep_node(mp, s); + } else { + if (number_greaterequal(absv, coef_bound_k) && mp->watch_coefs) { + mp_type(qq) = independent_needing_fix; + /* + If we set this , then we can drop |(mp_type(pp) == + independent_needing_fix && mp->fix_needed)| later + |set_number_from_scaled(mp_get_value_number(qq), + mp_get_indep_value(qq));| + */ + mp->fix_needed = 1; + } + mp_set_link(r, s); + r = s; + } + free_number(absv); + pp = mp_get_dep_info(p); + q = (mp_value_node) mp_link(q); + qq = mp_get_dep_info(q); + } + } else { + if (pp == NULL) { + set_number_to_negative_inf(v); + } else if (mp_type(pp) == mp_independent_type || (mp_type(pp) == independent_needing_fix && mp->fix_needed)) { + set_number_from_scaled(v, mp_get_indep_value(pp)); + } else { + number_clone(v, mp_get_value_number(pp)); + } + if (qq == NULL) { + set_number_to_negative_inf(vv); + } else if (mp_type(qq) == mp_independent_type || (mp_type(qq) == independent_needing_fix && mp->fix_needed)) { + set_number_from_scaled(vv, mp_get_indep_value(qq)); + } else { + number_clone(vv, mp_get_value_number(qq)); + } + if (number_less(v, vv)) { + /* Contribute a term from |q|, multiplied by~|f| */ + mp_number absv; + { + mp_number r1; + mp_number arg1, arg2; + new_fraction(r1); + new_number_clone(arg1, *f); + new_number_clone(arg2, mp_get_dep_value(q)); + if (tt == mp_dependent_type) { + take_fraction(r1, arg1, arg2); + } else { + take_scaled(r1, arg1, arg2); + } + number_clone(v, r1); + free_number(r1); + free_number(arg1); + free_number(arg2); + } + new_number_abs(absv, v); + if (number_greater(absv, half_threshold)) { + s = mp_get_dep_node(mp); + mp_set_dep_info(s, qq); + mp_set_dep_value(s, v); + if (number_greaterequal(absv, coef_bound_k) && mp->watch_coefs) { + mp_type(qq) = independent_needing_fix; + mp->fix_needed = 1; + } + mp_set_link(r, s); + r = s; + } + q = (mp_value_node) mp_link(q); + qq = mp_get_dep_info(q); + free_number(absv); + } else { + mp_set_link(r, p); + r = p; + p = (mp_value_node) mp_link(p); + pp = mp_get_dep_info(p); + } + } + } + { + mp_number r1; + mp_number arg1, arg2; + new_fraction(r1); + new_number(arg1); + new_number(arg2); + number_clone(arg1, mp_get_dep_value(q)); + number_clone(arg2, *f); + if (t == mp_dependent_type) { + take_fraction(r1, arg1, arg2); + } else { + take_scaled(r1, arg1, arg2); + } + slow_add(arg1, mp_get_dep_value(p), r1); + mp_set_dep_value(p, arg1); + free_number(r1); + free_number(arg1); + free_number(arg2); + } + mp_set_link(r, p); + mp->dep_final = p; + free_number(threshold); + free_number(half_threshold); + free_number(v); + free_number(vv); + return (mp_value_node) mp_link(mp->temp_head); +} + +@ 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; /* |mp_get_dep_info(p)| and |mp_get_dep_info(q)|, respectively */ + mp_value_node s; /* for list manipulation */ + mp_value_node r; /* for list manipulation */ + mp_number threshold; /* defines a neighborhood of zero */ + mp_number v, vv; /* temporary register */ + new_number(v); + new_number(vv); + new_number(threshold); + if (t == mp_dependent_type) { + number_clone(threshold, fraction_threshold_k); + } else { + number_clone(threshold, scaled_threshold_k); + } + r = (mp_value_node) mp->temp_head; + pp = mp_get_dep_info(p); + qq = mp_get_dep_info(q); + while (1) { + if (pp == qq) { + if (pp == NULL) { + break; + } else { + /* Contribute a term from |p|, plus the corresponding term from |q| */ + mp_number test; + new_number(test); + set_number_from_addition(v, mp_get_dep_value(p), mp_get_dep_value(q)); + mp_set_dep_value(p, v); + s = p; + p = (mp_value_node) mp_link(p); + pp = mp_get_dep_info(p); + number_abs_clone(test, v); + if (number_less(test, threshold)) { + mp_free_dep_node(mp, s); + } else { + if (number_greaterequal(test, coef_bound_k) && mp->watch_coefs) { + mp_type(qq) = independent_needing_fix; + /* + If we set this , then we can drop |(mp_type(pp) == + independent_needing_fix && mp->fix_needed)| later + |set_number_from_scaled(mp_get_value_number(qq), + mp_get_indep_value(qq));| + */ + mp->fix_needed = 1; + } + mp_set_link(r, s); + r = s; + } + free_number(test); + q = (mp_value_node) mp_link(q); + qq = mp_get_dep_info(q); + } + + } else { + if (pp == NULL) { + set_number_to_zero(v); + } else if (mp_type(pp) == mp_independent_type || (mp_type(pp) == independent_needing_fix && mp->fix_needed)) { + set_number_from_scaled(v, mp_get_indep_value(pp)); + } else { + number_clone(v, mp_get_value_number(pp)); + } + if (qq == NULL) { + set_number_to_zero(vv); + } else if (mp_type(qq) == mp_independent_type || (mp_type(qq) == independent_needing_fix && mp->fix_needed)) { + set_number_from_scaled(vv, mp_get_indep_value(qq)); + } else { + number_clone(vv, mp_get_value_number(qq)); + } + if (number_less(v, vv)) { + s = mp_get_dep_node(mp); + mp_set_dep_info(s, qq); + mp_set_dep_value(s, mp_get_dep_value(q)); + q = (mp_value_node) mp_link(q); + qq = mp_get_dep_info(q); + mp_set_link(r, s); + r = s; + } else { + mp_set_link(r, p); + r = p; + p = (mp_value_node) mp_link(p); + pp = mp_get_dep_info(p); + } + } + } + { + mp_number r1; + new_number(r1); + slow_add(r1, mp_get_dep_value(p), mp_get_dep_value(q)); + mp_set_dep_value(p, r1); + free_number(r1); + } + mp_set_link(r, p); + mp->dep_final = p; + free_number(v); + free_number(vv); + free_number(threshold); + return (mp_value_node) mp_link(mp->temp_head); +} + +@ 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, mp_number *v, int t0, int t1, int v_is_scaled) +{ + mp_value_node r, s; /* for list manipulation */ + mp_number w; /* tentative coefficient */ + mp_number threshold; + int scaling_down = (t0 != t1) ? 1 : (! v_is_scaled); + new_number(threshold); + new_number(w); + if (t1 == mp_dependent_type) { + number_clone(threshold, half_fraction_threshold_k); + } else { + number_clone(threshold, half_scaled_threshold_k); + } + r = (mp_value_node) mp->temp_head; + while (mp_get_dep_info(p) != NULL) { + mp_number test; + new_number(test); + if (scaling_down) { + take_fraction(w, *v, mp_get_dep_value(p)); + } else { + take_scaled(w, *v, mp_get_dep_value(p)); + } + number_abs_clone(test, w); + if (number_lessequal(test, threshold)) { + s = (mp_value_node) mp_link(p); + mp_free_dep_node(mp, p); + p = s; + } else { + if (number_greaterequal(test, coef_bound_k)) { + mp->fix_needed = 1; + mp_type(mp_get_dep_info(p)) = independent_needing_fix; + } + mp_set_link(r, p); + r = p; + mp_set_dep_value(p, w); + p = (mp_value_node) mp_link(p); + } + free_number(test); + } + mp_set_link(r, p); + { + mp_number r1; + new_number(r1); + if (v_is_scaled) { + take_scaled(r1, mp_get_dep_value(p), *v); + } else { + take_fraction(r1, mp_get_dep_value(p), *v); + } + mp_set_dep_value(p, r1); + free_number(r1); + } + free_number(w); + free_number(threshold); + 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, mp_number *v, int t0, int t1); + +@ @d p_over_v_threshold_k mp->math->md_p_over_v_threshold_t + +@ @c +mp_value_node mp_p_over_v (MP mp, mp_value_node p, mp_number *v_orig, int t0, int t1) +{ + mp_value_node r, s; /* for list manipulation */ + mp_number w; /* tentative coefficient */ + mp_number threshold; + mp_number v; + int scaling_down = (t0 != t1); + new_number(w); + new_number(threshold); + new_number_clone(v, *v_orig); + if (t1 == mp_dependent_type) { + number_clone(threshold, half_fraction_threshold_k); + } else { + number_clone(threshold, half_scaled_threshold_k); + } + r = (mp_value_node) mp->temp_head; + while (mp_get_dep_info(p) != NULL) { + if (scaling_down) { + mp_number x, absv; + new_number_abs(absv, v); + if (number_less(absv, p_over_v_threshold_k)) { + new_number_clone(x, v); + convert_scaled_to_fraction(x); + make_scaled(w, mp_get_dep_value(p), x); + } else { + new_number_clone(x, mp_get_dep_value(p)); + fraction_to_round_scaled(x); + make_scaled(w, x, v); + } + free_number(x); + free_number(absv); + } else { + make_scaled(w, mp_get_dep_value(p), v); + } + { + mp_number test; + new_number(test); + number_abs_clone(test, w); + if (number_lessequal(test, threshold)) { + s = (mp_value_node) mp_link(p); + mp_free_dep_node(mp, p); + p = s; + } else { + if (number_greaterequal(test, coef_bound_k)) { + mp->fix_needed = 1; + mp_type(mp_get_dep_info(p)) = independent_needing_fix; + } + mp_set_link(r, p); + r = p; + mp_set_dep_value(p, w); + p = (mp_value_node) mp_link(p); + } + free_number(test); + } + } + mp_set_link(r, p); + { + mp_number ret; + new_number(ret); + make_scaled(ret, mp_get_dep_value(p), v); + mp_set_dep_value(p, ret); + free_number(ret); + } + free_number(v); + free_number(w); + free_number(threshold); + 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, int t) +{ + mp_value_node s = p; + mp_value_node r = (mp_value_node) mp->temp_head; + int sx = mp_get_indep_value(x); /* serial number of |x| */ + while (mp_get_dep_info(s) != NULL && mp_get_indep_value(mp_get_dep_info(s)) > sx) { + r = s; + s = (mp_value_node) mp_link(s); + } + if (mp_get_dep_info(s) == NULL || mp_get_dep_info(s) != x) { + return p; + } else { + mp_value_node ret; + mp_number v1; + mp_set_link(mp->temp_head, p); + mp_set_link(r, mp_link(s)); + new_number_clone(v1, mp_get_dep_value(s)); + mp_free_dep_node(mp, s); + ret = mp_p_plus_fq(mp, (mp_value_node) mp_link(mp->temp_head), &v1, (mp_value_node) q, t, mp_dependent_type); + free_number(v1); + return ret; + } +} + +@ 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, mp_number *x); + +@ @c +static void mp_val_too_big (MP mp, mp_number *x) +{ + if (number_positive(internal_value(mp_warning_check_internal))) { + char msg[256]; + mp_snprintf(msg, 256, "Value is too large (%s)", number_tostring(*x)); + mp_error( + mp, + msg, + "The equation I just processed has given some variable a value outside of the\n" + "safetyp range. Continue and I'll try to cope with that big value; but it might be\n" + "dangerous. (Set 'warningcheck := 0' to suppress this message.)" + ); + } +} + +@ 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 = mp_type(p); /* the previous type */ + mp_number absp; + new_number(absp); + mp_set_prev_dep(mp_link(q), mp_get_prev_dep(p)); + mp_set_link(mp_get_prev_dep(p), mp_link(q)); + mp_type(p) = mp_known_type; + mp_set_value_number(p, mp_get_dep_value(q)); + mp_free_dep_node(mp, q); + number_abs_clone(absp, mp_get_value_number(p)); + if (number_greaterequal(absp, warning_limit_t)) { + mp_val_too_big (mp, &(mp_get_value_number(p))); + } + if ((number_positive(internal_value(mp_tracing_equations_internal))) && mp_interesting(mp, (mp_node) p)) { + mp_begin_diagnostic(mp); + mp_print_nl(mp, "#### "); + mp_print_variable_name(mp, (mp_node) p); + mp_print_chr(mp, '='); + print_number(mp_get_value_number(p)); + mp_end_diagnostic(mp, 0); + } + if (cur_exp_node == (mp_node) p && mp->cur_exp.type == t) { + mp->cur_exp.type = mp_known_type; + mp_set_cur_exp_value_number(mp, &(mp_get_value_number(p))); + mp_free_value_node(mp, (mp_node) p); + } + free_number(absp); +} + +@ 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); + +@ @d independent_being_fixed 1 /* this variable already appears in |s| */ + +@ @c +static void mp_fix_dependencies (MP mp) +{ + mp_value_node r = (mp_value_node) mp_link(mp->dep_head); + mp_value_node s = NULL; + while (r != mp->dep_head) { + /* + Run through the dependency list for variable |t|, fixing all nodes, + and ending with final link~|q| + */ + mp_value_node t = r; + mp_value_node q; + while (1) { + mp_node x; + if (t == r) { + q = (mp_value_node) mp_get_dep_list(t); + } else { + q = (mp_value_node) mp_link(r); + } + x = mp_get_dep_info(q); + if (x == NULL) { + break; + } else if (mp_type(x) <= independent_being_fixed) { + if (mp_type(x) < independent_being_fixed) { + mp_value_node p = mp_get_dep_node(mp); + mp_set_link(p, s); + s = p; + mp_set_dep_info(s, x); + mp_type(x) = independent_being_fixed; + } + mp_set_dep_value(q, mp_get_dep_value(q)); + number_divide_int(mp_get_dep_value(q), 4); + if (number_zero(mp_get_dep_value(q))) { + mp_set_link(r, mp_link(q)); + mp_free_dep_node(mp, q); + q = r; + } + } + r = q; + } + r = (mp_value_node) mp_link(q); + if (q == (mp_value_node) mp_get_dep_list(t)) { + mp_make_known(mp, t, q); + } + } + while (s != NULL) { + mp_value_node p = (mp_value_node) mp_link(s); + mp_node x = mp_get_dep_info(s); + mp_free_dep_node(mp, s); + s = p; + mp_type(x) = mp_independent_type; + mp_set_indep_scale(x, mp_get_indep_scale(x) + 2); + } + mp->fix_needed = 0; +} + +@ 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 */ + mp_type(q) = newtype; + mp_set_dep_list(q, p); + mp_set_prev_dep(q, (mp_node) mp->dep_head); + r = mp_link(mp->dep_head); + mp_set_link(mp->dep_final, r); + mp_set_prev_dep(r, (mp_node) mp->dep_final); + mp_set_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, mp_number *v) +{ + mp->dep_final = mp_get_dep_node(mp); + mp_set_dep_value(mp->dep_final, *v); + mp_set_dep_info(mp->dep_final, NULL); + 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; /* the new dependency list */ + int m = mp_get_indep_scale(p); /* the number of doublings */ + if (m > 28) { + q = mp_const_dependency(mp, &zero_t); + } else { + mp_value_node rr; + q = mp_get_dep_node(mp); + mp_set_dep_value(q, zero_t); + set_number_from_scaled(mp_get_dep_value(q), (int) two_to_the(28 - m)); + mp_set_dep_info(q, p); + rr = mp_const_dependency(mp, &zero_t); + mp_set_link(q, rr); + } + 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 = mp_get_dep_node(mp); /* the new dependency list */ + mp->dep_final = q; + while (1) { + mp_set_dep_info(mp->dep_final, mp_get_dep_info(p)); + mp_set_dep_value(mp->dep_final, mp_get_dep_value(p)); + if (mp_get_dep_info(mp->dep_final) == NULL) { + break; + } else { + mp_set_link(mp->dep_final, 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 mp_value_node find_node_with_largest_coefficient (MP mp, mp_value_node p, mp_number *v); + +static void display_new_dependency (MP mp, mp_value_node p, mp_node x, int n); + +static void change_to_known (MP mp, mp_value_node p, mp_node x, mp_value_node final_node, int n); + +static mp_value_node divide_p_by_minusv_removing_q (MP mp, + mp_value_node p, mp_value_node q, + mp_value_node *final_node, mp_number *v, int t +); + +static mp_value_node divide_p_by_2_n (MP mp, mp_value_node p, int n); + +static void mp_linear_eq (MP mp, mp_value_node p, int t) +{ + mp_value_node r; /* for link manipulation */ + mp_node x; /* the variable that loses its independence */ + int n; /* the number of times |x| had been halved */ + mp_number 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 */ + mp_value_node qq; + new_number(v); + qq = find_node_with_largest_coefficient(mp, p, &v); + x = mp_get_dep_info(qq); + n = mp_get_indep_scale(x); + p = divide_p_by_minusv_removing_q(mp, p, qq, &final_node, &v, t); + if (number_positive(internal_value(mp_tracing_equations_internal))) { + display_new_dependency(mp, p, (mp_node) x, n); + } + prev_r = (mp_value_node) mp->dep_head; + r = (mp_value_node) mp_link(mp->dep_head); + while (r != mp->dep_head) { + mp_value_node s = (mp_value_node) mp_get_dep_list(r); + mp_value_node q = mp_p_with_x_becoming_q(mp, s, x, (mp_node) p, mp_type(r)); + if (mp_get_dep_info(q) == NULL) { + mp_make_known(mp, r, q); + } else { + mp_set_dep_list(r, q); + do { + q = (mp_value_node) mp_link(q); + } while (mp_get_dep_info(q) != NULL); + prev_r = q; + } + r = (mp_value_node) mp_link(prev_r); + } + if (n > 0) { + p = divide_p_by_2_n(mp, p, n); + } + change_to_known(mp, p, (mp_node) x, final_node, n); + if (mp->fix_needed) { + mp_fix_dependencies(mp); + } + free_number(v); +} + +@ @c +static mp_value_node find_node_with_largest_coefficient (MP mp, mp_value_node p, mp_number *v) +{ + mp_number vabs; /* its absolute value of v*/ + mp_number rabs; /* the absolute value of |mp_get_dep_value(r)| */ + mp_value_node q = p; + mp_value_node r = (mp_value_node) mp_link(p); + new_number(vabs); + new_number(rabs); + number_clone(*v, mp_get_dep_value(q)); + while (mp_get_dep_info(r) != NULL) { + number_abs_clone(vabs, *v); + number_abs_clone(rabs, mp_get_dep_value(r)); + if (number_greater(rabs, vabs)) { + q = r; + number_clone(*v, mp_get_dep_value(r)); + } + r = (mp_value_node) mp_link(r); + } + free_number(vabs); + free_number(rabs); + return q; +} + +@ 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|. + +@c +static mp_value_node divide_p_by_minusv_removing_q (MP mp, + mp_value_node p, mp_value_node q, + mp_value_node *final_node, mp_number *v, int t +) +{ + mp_value_node r = p; /* for link manipulation */ + mp_value_node s = (mp_value_node) mp->temp_head; + mp_set_link(s, p); + do { + if (r == q) { + mp_set_link(s, mp_link(r)); + mp_free_dep_node(mp, r); + } else { + mp_number w; /* a tentative coefficient */ + mp_number absw; + new_number(w); + new_number(absw); + make_fraction(w, mp_get_dep_value(r), *v); + number_abs_clone(absw, w); + if (number_lessequal(absw, half_fraction_threshold_k)) { + mp_set_link(s, mp_link(r)); + mp_free_dep_node(mp, r); + } else { + number_negate(w); + mp_set_dep_value(r, w); + s = r; + } + free_number(w); + free_number(absw); + } + r = (mp_value_node) mp_link(s); + } while (mp_get_dep_info(r) != NULL); + if (t == mp_proto_dependent_type) { + mp_number ret; + new_number(ret); + make_scaled(ret, mp_get_dep_value(r), *v); + number_negate(ret); + mp_set_dep_value(r, ret); + free_number(ret); + } else if (number_to_scaled(*v) != -number_to_scaled(fraction_one_t)) { + mp_number ret; + new_fraction(ret); + make_fraction(ret, mp_get_dep_value(r), *v); + number_negate(ret); + mp_set_dep_value(r, ret); + free_number(ret); + } + *final_node = r; + return (mp_value_node) mp_link(mp->temp_head); +} + +@ @c +static void display_new_dependency (MP mp, mp_value_node p, mp_node x, int n) +{ + if (mp_interesting(mp, x)) { + mp_begin_diagnostic(mp); + mp_print_nl(mp, "## "); + mp_print_variable_name(mp, x); + while (n > 0) { + mp_print_str(mp, "*4"); + n = n - 2; + } + mp_print_chr(mp, '='); + mp_print_dependency(mp, p, mp_dependent_type); + mp_end_diagnostic(mp, 0); + } +} + +@ The |n > 0| test is repeated here because it is of vital importance to the +function's functioning. + +@c +static mp_value_node divide_p_by_2_n (MP mp, mp_value_node p, int n) +{ + mp_value_node pp = NULL; + if (n > 0) { + /* Divide list |p| by $2^n$ */ + mp_value_node r; + mp_value_node s; + mp_number absw; + mp_number w; /* a tentative coefficient */ + new_number(w); + new_number(absw); + s = (mp_value_node) mp->temp_head; + mp_set_link(mp->temp_head, p); + r = p; + do { + if (n > 30) { + set_number_to_zero(w); + } else { + number_clone(w, mp_get_dep_value(r)); + number_divide_int(w, two_to_the(n)); + } + number_abs_clone(absw, w); + if (number_lessequal(absw, half_fraction_threshold_k) && (mp_get_dep_info(r) != NULL)) { + mp_set_link(s, mp_link(r)); + mp_free_dep_node(mp, r); + } else { + mp_set_dep_value(r, w); + s = r; + } + r = (mp_value_node) mp_link(s); + } while (mp_get_dep_info(s) != NULL); + pp = (mp_value_node) mp_link(mp->temp_head); + free_number(absw); + free_number(w); + } + return pp; +} + +@ @c +static void change_to_known (MP mp, mp_value_node p, mp_node x, mp_value_node final_node, int n) +{ + (void) n; + if (mp_get_dep_info(p) == NULL) { + mp_number absx; + mp_type(x) = mp_known_type; + mp_set_value_number(x, mp_get_dep_value(p)); + new_number_abs(absx, mp_get_value_number(x)); + if (number_greaterequal(absx, warning_limit_t)) { + mp_val_too_big(mp, &(mp_get_value_number(x))); + } + free_number(absx); + mp_free_dep_node(mp, p); + if (cur_exp_node == x && mp->cur_exp.type == mp_independent_type) { + mp_set_cur_exp_value_number(mp, &(mp_get_value_number(x))); + mp->cur_exp.type = mp_known_type; + mp_free_value_node(mp, x); + } + } else { + mp->dep_final = final_node; + mp_new_dep(mp, x, mp_dependent_type, p); + if (cur_exp_node == x && mp->cur_exp.type == mp_independent_type) { + mp->cur_exp.type = mp_dependent_type; + } + } +} + +@* 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 +\quote {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 = mp_new_value_node(mp); /* the new capsule node */ + mp_name_type(q) = mp_capsule_operation; + mp_type(q) = mp_type(p); + if (mp_get_value_node(p) == NULL) { + mp_set_value_node(q, p); + } else { + mp_set_value_node(q, mp_get_value_node(p)); + } + mp_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) +{ + (void) mp; + mp_node q = mp_get_value_node(p); + if (q != NULL && q != p) { + while (mp_get_value_node(q) != p) { + q = mp_get_value_node(q); + } + mp_set_value_node(q, mp_get_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, int flush_p) +{ + mp_variable_type t = mp_type(p) - unknown_tag; /* the type of ring |p| */ + mp_node q = mp_get_value_node(p); + if (flush_p) { + mp_type(p) = mp_vacuous_type; + } else { + p = q; + } + do { + mp_node r = mp_get_value_node(q); + mp_type(q) = t; + switch (t) { + case mp_boolean_type: + mp_set_value_number(q, v.data.n); + break; + case mp_string_type: + mp_set_value_str(q, v.data.str); + add_str_ref(v.data.str); + break; + case mp_pen_type: + case mp_nep_type: + mp_set_value_knot(q, mp_copy_pen(mp, v.data.p)); + break; + case mp_path_type: + mp_set_value_knot(q, mp_copy_path(mp, v.data.p)); + break; + case mp_picture_type: + mp_set_value_node(q, v.data.node); + mp_add_edge_ref(mp, 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 = mp_get_value_node(p); /* traverses one list */ + while (r != p) { + if (r == q) { + mp_exclaim_redundant_equation(mp); + return; + } else { + r = mp_get_value_node(r); + } + } + r = mp_get_value_node(p); + mp_set_value_node(p, mp_get_value_node(q)); + mp_set_value_node(q, r); +} + +@ @c +static void mp_exclaim_redundant_equation (MP mp) +{ + mp_back_error( + mp, + "Redundant equation", + "I already knew that this equation was true. But perhaps no harm has been done;\n" + "let's continue." + ); + mp_get_x_next(mp); +} + +@ @<Declarations@>= +static void mp_exclaim_redundant_equation (MP 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 or operand 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. + +@d cur_cmd mp->cur_mod_->command +@d cur_mod number_to_scaled(mp->cur_mod_->data.n) +@d cur_mod_number mp->cur_mod_->data.n +@d cur_mod_node mp->cur_mod_->data.node +@d cur_mod_str mp->cur_mod_->data.str +@d cur_sym mp->cur_mod_->data.sym +@d cur_sym_mod mp->cur_mod_->name_type + +@d set_cur_cmd(A) mp->cur_mod_->command = (A) +@d set_cur_mod(A) set_number_from_scaled(mp->cur_mod_->data.n, (A)) +@d set_cur_mod_number(A) number_clone(mp->cur_mod_->data.n, (A)) +@d set_cur_mod_node(A) mp->cur_mod_->data.node = (A) +@d set_cur_mod_str(A) mp->cur_mod_->data.str = (A) +@d set_cur_sym(A) mp->cur_mod_->data.sym = (A) +@d set_cur_sym_mod(A) mp->cur_mod_->name_type = (A) + +@<Glob...@>= +mp_node cur_mod_; /* current command, symbol, and its operands */ + +@ @<Initialize table...@>= +mp->cur_mod_ = mp_new_symbolic_node(mp); + +@ @<Free table...@>= +mp_free_symbolic_node(mp, mp->cur_mod_); + +@ 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 const char *mp_cmd_mod_string (MP mp, int c, int m); +static void mp_print_cmd_mod (MP mp, int c, int m); + +@ @c +const char *mp_cmd_mod_string (MP mp, int c, int m) +{ + switch (c) { + @<Cases of |print_cmd_mod| for symbolic printing of primitives@> + } + return "[unknown command code!]"; +} + +void mp_print_cmd_mod (MP mp, int c, int m) +{ + mp_print_str(mp, mp_cmd_mod_string(mp, c, m)); +} + +@ Here is a procedure that displays a given command in braces, in the +user's transcript file. + +@c +static void mp_show_cmd_mod (MP mp, int c, int m) +{ + mp_begin_diagnostic(mp); + mp_print_nl(mp, "{"); + switch (c) { + case mp_primary_def_command: + case mp_secondary_def_command: + case mp_tertiary_def_command: + mp_print_cmd_mod(mp, mp_macro_def_command, c); + mp_print_str(mp, "'d macro:"); + mp_print_ln(mp); + mp_show_token_list(mp, mp_link(mp_link(cur_mod_node)),0); + break; + default: + mp_print_cmd_mod(mp, c, m); + break; + } + mp_print_chr(mp, '}'); + mp_end_diagnostic(mp, 0); +} + +@* 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 mp_in_state_record { + int start_field; + int loc_field; + int limit_field; + int index_field; + mp_node nstart_field; + mp_node nloc_field; + mp_string name_field; +} mp_in_state_record; + +@ @<Glob...@>= +mp_in_state_record *input_stack; +int input_ptr; /* first unused location of |input_stack| */ +int max_in_stack; /* largest value of |input_ptr| when pushing */ +mp_in_state_record cur_input; /* the \quote {top} input state */ +int stack_size; /* maximum number of simultaneous input sources */ + +@ @<Allocate or initialize ...@>= +mp->stack_size = 16; +mp->input_stack = mp_memory_allocate((size_t) (mp->stack_size + 1) * sizeof(mp_in_state_record)); + +@ @<Dealloc variables@>= +mp_memory_free(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 (mp_string) 0 /* |name| value when reading from the terminal for normal input */ +@d is_read (mp_string) 1 /* |name| value when executing a |readstring| or |readfrom| */ +@d is_scantok (mp_string) 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. + +The global variable |in_open| is equal to the highest |index| value 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. + +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 */ + +@<Glob...@>= +int in_open; /* the number of lines in the buffer, less one */ +int in_open_max; /* highest value of |in_open| ever seen */ +unsigned int open_parens; /* the number of open text files */ +void **input_file; +int *line_stack; /* the line number for each file */ + +@ @<Declarations@>= +static void mp_reallocate_input_stack (MP mp, int newsize); + +@ @c +static void mp_reallocate_input_stack (MP mp, int newsize) +{ + int n = newsize + 1; + mp->input_file = mp_memory_reallocate(mp->input_file, (size_t) (n + 1) * sizeof(void *)); + mp->line_stack = mp_memory_reallocate(mp->line_stack, (size_t) (n + 1) * sizeof(int)); + for (int k = mp->max_in_open; k <= n; k++) { + mp->input_file[k] = NULL; + mp->line_stack[k] = 0; + } + mp->max_in_open = newsize; +} + +@ This has to be more than |file_bottom|, so: +@<Allocate or ...@>= +mp_reallocate_input_stack(mp, mp_file_bottom_text + 4); + +@ @<Dealloc variables@>= +mp_memory_free(mp->input_file); +mp_memory_free(mp->line_stack); + +@ 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<=mp_macro_text) /* are we scanning a token list? */ +@d file_state (iindex>mp_macro_text) /* are we scanning a file line? */ +@d param_start limit /* base of macro parameters in |param_stack| */ + + +@ @<Enumeration types@>= +typedef enum mp_text_codes { + mp_forever_text, /* |token_type| code for loop texts */ + mp_loop_text, /* |token_type| code for loop texts */ + mp_parameter_text, /* |token_type| code for parameter texts */ + mp_backed_up_text, /* |token_type| code for texts to be reread */ + mp_inserted_text, /* |token_type| code for inserted texts */ + mp_macro_text, /* |token_type| code for macro replacement texts */ + mp_file_bottom_text, /* lowest file code */ +} mp_text_codes; + +@ 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 */ +int param_ptr; /* first unused entry in |param_stack| */ +int max_param_stack; /* largest value of |param_ptr| */ + +@ @<Allocate or initialize ...@>= +mp->param_stack = mp_memory_allocate((size_t) (mp->param_size + 1) * sizeof(mp_node)); + +@ @c +static void mp_check_param_size (MP mp, int k) +{ + while (k >= mp->param_size) { + mp->param_stack = mp_memory_reallocate(mp->param_stack, (size_t) ((k + k / 4) + 1) * sizeof(mp_node)); + mp->param_size = k + k / 4; + } +} + +@ @<Dealloc variables@>= +mp_memory_free(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 int mp_true_line (MP mp); + +@ @c +int 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 < mp_file_bottom_text) + || (mp->input_stack[(k - 1)].name_field <= max_spec_src))) { + --k; + } + return (k > 0 ? mp->line_stack[(k - 1) + mp_file_bottom_text] : 0); + } +} + +@ Thus, the \quote {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...@>= +int 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. 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 */ + mp->file_ptr = mp->input_ptr; + mp->input_stack[mp->file_ptr] = mp->cur_input; + /* store current state */ + while (1) { + /* enter into the context */ + mp->cur_input = mp->input_stack[mp->file_ptr]; + @<Display the current context@> + if (file_state && (name > max_spec_src || mp->file_ptr == 0)) { + break; + } else { + --mp->file_ptr; + } + } + /* restore original state */ + mp->cur_input = mp->input_stack[mp->input_ptr]; +} + +@ @<Display the current context@>= +/* we omit backed-up token lists that have already been read */ +if ((mp->file_ptr == mp->input_ptr) || file_state || (token_type != mp_backed_up_text) || (nloc != NULL)) { + if (file_state) { + @<Print location of current line@> + if (limit > 0) { + for (int i = start; i <= limit - 1; i++) { + mp_print_chr(mp, mp->buffer[i]); + } + } + } else { + @<Print type of token list@> + if (token_type == mp_macro_text) { + mp_show_macro(mp, nstart, nloc); + } else if (mp->show_mode) { + mp_show_token_list_space(mp, nstart, nloc); + } else { + mp_show_token_list(mp, nstart, nloc); + } + } +} + +@ 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_nl(mp, "<line "); + mp_print_int(mp, mp_true_line(mp)); + mp_print_chr(mp, '>'); +} else if (terminal_input) { + if (mp->file_ptr == 0) { + mp_print_nl(mp, "<direct>"); + } else { + mp_print_nl(mp, "<insert>"); + } +} else if (name == is_scantok) { + mp_print_nl(mp, "<scantokens>"); +} else { + mp_print_nl(mp, "<read>"); +} +mp_print_chr(mp, ' '); + +@ Can't use case statement here because the |token_type| is not a constant +expression. + +@<Print type of token list@>= +{ + switch (token_type) { + case mp_forever_text: + mp_print_nl(mp, "<forever> "); + break; + case mp_loop_text: + @<Print the current loop value@> + break; + case mp_parameter_text: + mp_print_nl(mp, "<argument> "); + break; + case mp_backed_up_text: + mp_print_nl(mp, nloc == NULL ? "<recently read> " : "<to be read again> "); + break; + case mp_inserted_text: + mp_print_nl(mp, "<inserted text> "); + break; + case mp_macro_text: + mp_print_nl(mp, "<macro> "); + // mp_print_ln(mp); + if (name != NULL) { + mp_print_mp_str(mp, name); + } else { + @<Print the name of a |vardef|'d macro@> + } + // mp_print_str(mp, "->"); + mp_print_str(mp, " -> "); + break; + default: + mp_print_nl(mp, "?"); /* this should never happen */ + @.?\relax@> + break; + } +} + +@ The parameter that corresponds to a loop text is either a token list (in the +case of |forsuffixes|) or a \quote {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->param_stack[param_start]; + mp_print_nl(mp, "<for("); + 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); + } + } + mp_print_str(mp, ")> "); +} + +@ The first two parameters of a macro defined by |vardef| will be token +lists representing the macro's prefix and \quote {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); + } 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); + mp_link(qq) = NULL; + } +} + +@* Maintaining the input stacks. + +The following subroutines change the input status in commonly needed ways. + +First comes |mp_push_input|, which stores the current state and creates a +new level (having, initially, the same properties as the old). We could have +a maximum depth here. + +@<Declarations@>= +static void mp_push_input (MP mp); + +@ @c +void mp_push_input (MP mp) +{ + 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)); + if (l > 1000) { + mp_fatal_error(mp, "job aborted, more than 1000 input levels"); + } else { + mp_in_state_record *s = mp_memory_reallocate(mp->input_stack, (size_t) (l + 1) * sizeof(mp_in_state_record)); + if (s) { + mp->input_stack = s; + mp->stack_size = l; + } else { + mp_fatal_error(mp, "job aborted, out of memory"); + } + } + } + } + mp->input_stack[mp->input_ptr] = mp->cur_input; + ++mp->input_ptr; +} + +@ And of course what goes up must come down. + +@<Declarations@>= +static void mp_pop_input (MP mp); + +@ @c + +void mp_pop_input (MP mp) +{ + --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. + +@c +static void mp_begin_token_list (MP mp, mp_node p, int t) +{ + mp_push_input(mp); + 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 */ + if (token_type >= mp_backed_up_text) { + /* token list to be deleted */ + if (token_type <= mp_inserted_text) { + mp_flush_token_list(mp, nstart); + goto DONE; + } else { + /* update reference count */ + mp_delete_mac_ref(mp, nstart); + } + } + while (mp->param_ptr > param_start) { + /* parameters must be flushed */ + mp_node p; /* temporary register */ + --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_value_node(mp, p); + } else { + /* it's a |suffix| or |text| parameter */ + mp_flush_token_list(mp, p); + } + } + } + DONE: + mp_pop_input(mp); +} + +@ 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 (cur_sym == NULL && (cur_sym_mod == 0 || cur_sym_mod == mp_normal_operation)) { + if (cur_cmd == mp_capsule_command) { + mp_number save_exp_num; /* possible |cur_exp| numerical to be restored */ + mp_value save_exp = mp->cur_exp; /* |cur_exp| to be restored */ + new_number(save_exp_num); + number_clone(save_exp_num, cur_exp_value_number); + mp_make_exp_copy(mp, cur_mod_node); + p = mp_stash_cur_exp(mp); + mp_link(p) = NULL; + mp->cur_exp = save_exp; + number_clone(mp->cur_exp.data.n, save_exp_num); + free_number(save_exp_num); + } else { + p = mp_new_token_node(mp); + mp_name_type(p) = mp_token_operation; + if (cur_cmd == mp_numeric_command) { + mp_set_value_number(p, cur_mod_number); + mp_type(p) = mp_known_type; + } else { + mp_set_value_str(p, cur_mod_str); + mp_type(p) = mp_string_type; + } + } + } else { + p = mp_new_symbolic_node(mp); + mp_set_sym_sym(p, cur_sym); + mp_name_type(p) = cur_sym_mod; + } + return p; +} + +@ Sometimes \MP\ has read too far and wants to \quote {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 = mp_cur_tok(mp); /* a token list of length one */ + /* conserve stack space */ + while (token_state && (nloc == NULL)) { + mp_end_token_list(mp); + } + mp_begin_token_list(mp, p, mp_backed_up_text); +} + +@ The |back_error| routine is used when we want to restore or replace an +offending token just before issuing an error message. + +@<Declarations@>= +static void mp_back_error (MP mp, const char *msg, const char *hlp) ; + +@ @c +static void mp_back_error (MP mp, const char *msg, const char *hlp) +{ + /* back up one token and call |error| */ + mp_back_input(mp); + mp_error(mp, msg, hlp); +} + +static void mp_ins_error (MP mp, const char *msg, const char *hlp) +{ + /* back up one inserted token and call |error| */ + mp_back_input(mp); + token_type = mp_inserted_text; + mp_error(mp, msg, hlp); +} + +@ 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++; + mp_push_input(mp); + iindex = (int) mp->in_open; + if (mp->in_open_max < mp->in_open) { + mp->in_open_max = mp->in_open; + } + start = (int) mp->first; + name = is_term; /* |terminal_input| is now |true| */ +} + +@ Conversely, the variables must be downdated when such a level of input is +finished. 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->in_open > iindex) { + if ((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]); + --mp->in_open; + } + } + mp->first = (size_t) start; + if (iindex != mp->in_open) { + mp_confusion(mp, "endinput"); + } else { + if (name > max_spec_src) { + (mp->close_file) (mp, cur_file); + } + mp_pop_input(mp); + --mp->in_open; + } +} + +@* 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 \quote {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. + +@ @<Enumeration types@>= +typedef enum mp_scanner_states { + mp_normal_state, /* |scanner_status| at \quote {quiet times} */ + mp_skipping_state, /* |scanner_status| when false conditional text is being skipped */ + mp_flushing_state, /* |scanner_status| when junk after a statement is being ignored */ + mp_absorbing_state, /* |scanner_status| when a |text| parameter is being scanned */ + mp_var_defining_state, /* |scanner_status| when a |vardef| is being scanned */ + mp_op_defining_state, /* |scanner_status| when a macro |def| is being scanned */ + mp_loop_defining_state, /* |scanner_status| when a |for| loop is being scanned */ + mp_tex_flushing_state, +} mp_scanner_states; + +@ @<Glob...@>= +int 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? */ +int warning_line; +mp_node warning_info_node; + +@ 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 int mp_check_outer_validity (MP mp) +{ + if (mp->scanner_status == mp_normal_state) { + return 1; + } else if (mp->scanner_status == mp_tex_flushing_state) { + @<Check if the file has ended while flushing \TeX\ material and set the result value for |check_outer_validity|@> + } else { + @<Back up an outer symbolic token so that it can be reread@> + if (mp->scanner_status > mp_skipping_state) { + @<Tell the user what has run away and try to recover@> + } else { + char msg[256]; + const char *hlp = NULL; + mp_snprintf(msg, 256, "Incomplete if; all text was ignored after line %d", (int) mp->warning_line); + @.Incomplete if...@> + if (cur_sym == NULL) { + hlp = + "The file ended while I was skipping conditional text. This kind of error happens\n" + "when you say 'if ...' and forget the matching 'fi'. I've inserted a 'fi'; this\n" + "might work."; + } else { + hlp = + "A forbidden 'outer' token occurred in skipped text. This kind of error happens\n" + "when you say 'if ...' and forget the matching 'fi'. I've inserted a 'fi'; this\n" + "might work."; + } + set_cur_sym(mp->frozen_fi); + mp_ins_error(mp, msg, hlp); + } + return 0; + } +} + +@ @<Check if the file has ended while flushing \TeX\ material and set...@>= +if (cur_sym != NULL) { + return 1; +} else { + char msg[256]; + mp_snprintf(msg, 256, "TeX mode didn't end; all text was ignored after line %d", (int) mp->warning_line); + set_cur_sym(mp->frozen_etex); + mp_ins_error( + mp, + msg, + "The file ended while I was looking for the 'etex' to finish this TeX material.\n" + "I've inserted 'etex' now." + ); + return 0; +} + +@ @<Back up an outer symbolic token so that it can be reread@>= +// if (cur_sym != NULL) { +// mp_node p = mp_new_symbolic_node(mp); +// mp_set_sym_sym(p, cur_sym); +// mp_name_type(p) = cur_sym_mod; +// /* prepare to read the symbolic token again */ +// mp_begin_token_list(mp, p, mp_backed_up_text); +// } + +@ @<Tell the user what has run away...@>= +{ + char msg[256]; + const char *mst = NULL; + const char *hlp = + "I suspect you have forgotten an 'enddef', causing me to read past where you\n" + "wanted me to stop. I'll try to recover."; + mp_runaway(mp); + /* print the definition-so-far */ + if (cur_sym == NULL) { + mst = "File ended while scanning"; + @.File ended while scanning...@> + } else { + mst = "Forbidden token found while scanning"; + @.Forbidden token found...@> + } + switch (mp->scanner_status) { + case mp_flushing_state: + { + mp_snprintf(msg, 256, "%s to the end of the statement", mst); + hlp = + "A previous error seems to have propagated, causing me to read past where\n" + "you wanted me to stop. I'll try to recover."; + set_cur_sym(mp->frozen_semicolon); + } + break; + case mp_absorbing_state: + { + mp_snprintf(msg, 256, "%s a text argument", mst); + hlp = + "It seems that a right delimiter was left out, causing me to read past where\n" + "you wanted me to stop. I'll try to recover."; + if (mp->warning_info == NULL) { + set_cur_sym(mp->frozen_end_group); + } else { + set_cur_sym(mp->frozen_right_delimiter); + /* + The next line makes sure that the inserted delimiter will match the + delimiter that already was read. + */ + set_equiv_sym(cur_sym, mp->warning_info); + } + } + break; + case mp_var_defining_state: + { + mp_string s; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_variable_name(mp, mp->warning_info_node); + s = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "%s the definition of %s", mst, s->str); + delete_str_ref(s); + set_cur_sym(mp->frozen_end_def); + } + break; + case mp_op_defining_state: + { + char *s = mp_str(mp, text(mp->warning_info)); + mp_snprintf(msg, 256, "%s the definition of %s", mst, s); + set_cur_sym(mp->frozen_end_def); + } + break; + case mp_loop_defining_state: + { + char *s = mp_str(mp, text(mp->warning_info)); + mp_snprintf(msg, 256, "%s the text of a %s loop", mst, s); + hlp = + "I suspect you have forgotten an 'endfor', causing me to read past where\n" + "you wanted me to stop. I'll try to recover."; + set_cur_sym(mp->frozen_end_for); + } + break; + } + mp_ins_error(mp, msg, hlp); +} + +@ 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 > mp_flushing_state) { + mp_print_nl(mp, "Runaway "); + switch (mp->scanner_status) { + case mp_absorbing_state: + mp_print_str(mp, "text?"); + break; + case mp_var_defining_state: + case mp_op_defining_state: + mp_print_str(mp, "definition?"); + break; + case mp_loop_defining_state: + mp_print_str(mp, "loop?"); + break; + } + mp_print_ln(mp); + mp_show_token_list(mp, mp_link(mp->hold_head), NULL); + } +} + +@ 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 */ + mp_sym cur_sym_; /* speed up access */ + RESTART: + set_cur_sym(NULL); + set_cur_sym_mod(0); + if (file_state) { + int k; /* an index into |buffer| */ + unsigned char c; /* the current character in the buffer */ + int cclass; /* its class number */ + /* + Input from external file; |goto restart| if no input found, or + |return| if a non-symbolic token is found. A percent sign appears in + |buffer[limit]|; this makes it unnecessary to have a special test for + end-of-line. + */ + SWITCH: + c = mp->buffer[loc]; + ++loc; + cclass = mp->char_class[c]; + switch (cclass) { + case mp_digit_class: + scan_numeric_token((c - '0')); + return; + case mp_period_class: + cclass = mp->char_class[mp->buffer[loc]]; + if (cclass > mp_period_class) { + goto SWITCH; + } else if (cclass < mp_period_class) { + /* |class=digit_class| */ + scan_fractional_token(0); + return; + } else { + break; + } + case mp_space_class: + goto SWITCH; + case mp_percent_class: + if (mp->scanner_status == mp_tex_flushing_state && loc < limit) { + /* btex .. etex */ + goto SWITCH; + } + /* + Move to next line of file, or |goto restart| if there is no + next line. + */ + if (mp_move_to_next_line(mp)) { + goto RESTART; + } else { + goto SWITCH; + } + case mp_string_class: + if (mp->scanner_status == mp_tex_flushing_state) { + goto SWITCH; + } else { + unsigned char cend = c == '"' ? '"' : 3 ; /* ASCII BTX ... ETX */ + if (mp->buffer[loc] == cend) { + set_cur_mod_str(mp_rts(mp,"")); + } else { + k = loc; + mp->buffer[limit + 1] = cend; + do { + ++loc; + } while (mp->buffer[loc] != cend); + if (loc > limit) { + /* + Decry the missing string delimiter and |goto restart|. 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. + */ + loc = limit; + /* the next character to be read on this line will be |"%"| */ + mp_error( + mp, + "Incomplete string token has been flushed", + "Strings should finish on the same line as they began. I've deleted the partial\n" + "string." + ); + goto RESTART; + } + mp_str_room(mp, (size_t) (loc - k)); + do { + mp_append_char(mp, mp->buffer[k]); + ++k; + } while (k != loc); + set_cur_mod_str(mp_make_string(mp)); + } + ++loc; + set_cur_cmd(mp_string_command); + return; + } + case mp_comma_class: + case mp_semicolon_class: + case mp_left_parenthesis_class: + case mp_right_parenthesis_class: + k = loc - 1; + goto FOUND; + case mp_invalid_class: + if (mp->scanner_status == mp_tex_flushing_state) { + goto SWITCH; + } else { + /* + Decry the invalid character and |goto restart|. 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|). + */ + mp_error( + mp, + "Text line contains an invalid character", + "A funny symbol that I can\'t read has just been input. Continue, and I'll forget\n" + "that it ever happened." + ); + goto RESTART; + } + default: + /* letters, etc. */ + break; + } + k = loc - 1; + while (mp->char_class[mp->buffer[loc]] == cclass) { + ++loc; + } + FOUND: + set_cur_sym(mp_id_lookup(mp, (char *) (mp->buffer + k), (size_t) (loc - k), 1)); + } 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. + */ + if (nloc != NULL && mp_type(nloc) == mp_symbol_node_type) { + /* symbolic token */ + int cur_sym_mod_ = mp_name_type(nloc); + int cur_info = mp_get_sym_info(nloc); + set_cur_sym(mp_get_sym_sym(nloc)); + set_cur_sym_mod(cur_sym_mod_); + /* move to next */ + nloc = mp_link(nloc); + if (cur_sym_mod_ == mp_expr_operation) { + set_cur_cmd(mp_capsule_command); + set_cur_mod_node(mp->param_stack[param_start + cur_info]); + set_cur_sym_mod(0); + set_cur_sym(NULL); + return; + } else if (cur_sym_mod_ == mp_suffix_operation || cur_sym_mod_ == mp_text_operation) { + mp_begin_token_list(mp, mp->param_stack[param_start + cur_info], (int) mp_parameter_text); + goto RESTART; + } + } else if (nloc != NULL) { + /* Get a stored numeric or string or capsule token and |return| */ + if (mp_name_type(nloc) == mp_token_operation) { + if (mp_type(nloc) == mp_known_type) { + set_cur_mod_number(mp_get_value_number(nloc)); + set_cur_cmd(mp_numeric_command); + } else { + set_cur_mod_str(mp_get_value_str(nloc)); + set_cur_cmd(mp_string_command); + add_str_ref(cur_mod_str); + } + } else { + set_cur_mod_node(nloc); + set_cur_cmd(mp_capsule_command); + } + nloc = mp_link(nloc); + return; + } else { + /* we are done with this token list */ + mp_end_token_list(mp); + /* resume previous level */ + goto RESTART; + } + } + /* + When a symbolic token is declared to be |outer|, its command code is + increased by |outer_tag|. + */ + cur_sym_ = cur_sym; + set_cur_cmd(eq_type(cur_sym_)); + set_cur_mod(equiv(cur_sym_)); + set_cur_mod_node(equiv_node(cur_sym_)); + // if (cur_cmd >= mp_outer_tag_command) { + // if (mp_check_outer_validity(mp)) { + // set_cur_cmd(cur_cmd - mp_outer_tag_command); + // } else { + // goto RESTART; + // } + // } +} + +@ The global variable |force_eof| is normally |false|; it is set |true| by an +|endinput| command. + +@<Glob...@>= +int force_eof; /* should the next |input| be aborted early? */ + +@ @<Declarations@>= +static int mp_move_to_next_line (MP mp); + +@ @c +static int mp_move_to_next_line (MP mp) +{ + if (name > max_spec_src) { + /* + Read next line of file into |buffer|, or return 1 (|goto restart|) if + the file has ended. 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|. + */ + ++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 = 1; + } + }; + if (mp->force_eof) { + mp->force_eof = 0; + --loc; + if (mp->interaction < mp_silent_mode) { + mp_print_chr(mp, ')'); + --mp->open_parens; + /* show user that file has been read */ + update_terminal(); + } + /* resume previous level */ + mp_end_file_reading(mp); + mp_check_outer_validity(mp); + return 1; + } else { + mp->buffer[limit] = '%'; + mp->first = (size_t) (limit + 1); + loc = start; /* ready to read */ + } + } else if (mp->input_ptr > 0) { + /* text was inserted during error recovery or by |scantokens| */ + mp_end_file_reading(mp); + /* goto RESTART */ + return 1; /* resume previous level */ + } else if (mp->interaction > mp_nonstop_mode) { + if (limit == start && mp->interaction < mp_silent_mode) { + /* previous line was empty */ + mp_print_nl(mp, "(Please type a command or say `end')"); + } + mp_print_ln(mp); + mp->first = (size_t) start; + /* get a line from the terminal, prompt delegated */ + if (! mp_input_ln(mp, mp->term_in)) { + longjmp(*(mp->jump_buf), 1); + } + mp->buffer[mp->last] = '%'; + /* done */ + limit = (int) mp->last; + mp->buffer[limit] = '%'; + mp->first = (size_t) (limit + 1); + loc = start; + } else { + mp_fatal_error(mp, "job aborted, no legal end found"); + } + return 0; +} + +@ 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) +{ + limit = (int) mp->last; +} + +@* 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. + +@ @<Enumeration types@>= +typedef enum mp_verbatim_codes { + mp_btex_code, + mp_verbatim_code, +} mp_verbatim_codes; + +@ @<Put each...@>= +mp_primitive(mp, "btex", mp_btex_command, mp_btex_code); +@:btex_}{|btex| primitive@> +mp_primitive(mp, "verbatimtex", mp_btex_command, mp_verbatim_code); +@:verbatimtex_}{|verbatimtex| primitive@> +mp_primitive(mp, "etex", mp_etex_command, 0); +mp->frozen_etex = mp_frozen_primitive (mp, "etex", mp_etex_command, 0); +@:etex_}{|etex| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_btex_command: return m == mp_btex_code ? "btex" : "verbatimtex"; +case mp_etex_command: return "etex"; + +@ Actually, |get_t_next| is a macro that avoids procedure overhead except in the +unusual case where |btex|, |verbatimtex| or |etex| is encountered. Nowadays +the compiler deals with this so it might become a function. + +@d get_t_next(mp) do { + mp_get_next(mp); + if (cur_cmd <= mp_max_pre_command) { + mp_t_next(mp); /* will probably get inlined anyway */ + } +} while (0) + +@c +@ @<Declarations@>= +static void mp_t_next (MP mp); + +@ @c +static void mp_t_next (MP mp) +{ + if ((mp->extensions == 1) && (cur_cmd == mp_btex_command)) { + @<Pass btex ... etex to script@> + } else { + @<Complain about a misplaced |btex|@> + } +} + +@ @<Complain about a misplaced |btex|@>= +{ + mp_error( + mp, + "A 'btex/verbatimtex ... etex' definition needs an extension", + "This file contains picture expressions for 'btex ... etex' blocks. Such files\n" + "need an extension (plugin) that seems to be absent." + ); +} + +@* 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 \quote {ending delimiters} like +|enddef| and |endfor|. + +@ @<Enumeration types@>= +typedef enum mp_def_codes { + mp_end_def_code, /* command modifier for |enddef| */ + mp_def_code, /* command modifier for |def| */ + mp_var_def_code, /* command modifier for |vardef| */ + mp_primary_def_code, /* command modifier for |primarydef| */ + mp_secondary_def_code, /* command modifier for |secondarydef| */ + mp_tertiary_def_code, /* command modifier for |tertiarydef| */ +} mp_def_codes; + +@ @<Enumeration types@>= +typedef enum mp_only_set_codes { + mp_random_seed_code, + mp_max_knot_pool_code, +} mp_only_set_codes; + +@ @<Enumeration types@>= +typedef enum mp_for_codes { + mp_end_for_code, /* command modifier for |endfor| */ + mp_start_forever_code, /* command modifier for |forever| */ + mp_start_for_code, /* command modifier for |for| */ + mp_start_forsuffixes_code, /* command modifier for |forsuffixes| */ +} mp_for_codes; + +@ @<Put each...@>= +mp_primitive(mp, "def", mp_macro_def_command, mp_def_code); +@:def_}{|def| primitive@> +mp_primitive(mp, "vardef", mp_macro_def_command, mp_var_def_code); +@:var_def_}{|vardef| primitive@> +mp_primitive(mp, "primarydef", mp_macro_def_command, mp_primary_def_code); +@:primary_def_}{|primarydef| primitive@> +mp_primitive(mp, "secondarydef", mp_macro_def_command, mp_secondary_def_code); +@:secondary_def_}{|secondarydef| primitive@> +mp_primitive(mp, "tertiarydef", mp_macro_def_command, mp_tertiary_def_code); +@:tertiary_def_}{|tertiarydef| primitive@> +mp_primitive(mp, "enddef", mp_macro_def_command, mp_end_def_code); +mp->frozen_end_def = mp_frozen_primitive(mp, "enddef", mp_macro_def_command, mp_end_def_code); +@:end_def_}{|enddef| primitive@> +mp_primitive(mp, "for", mp_iteration_command, mp_start_for_code); +@:for_}{|for| primitive@> +mp_primitive(mp, "forsuffixes", mp_iteration_command, mp_start_forsuffixes_code); +@:for_suffixes_}{|forsuffixes| primitive@> +mp_primitive(mp, "forever", mp_iteration_command, mp_start_forever_code); +@:forever_}{|forever| primitive@> +mp_primitive(mp, "endfor", mp_iteration_command, mp_end_for_code); +mp->frozen_end_for = mp_frozen_primitive (mp, "endfor", mp_iteration_command, mp_end_for_code); +@:end_for_}{|endfor| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_macro_def_command: + switch (m) { + /* low numbers, command specifiers */ + case mp_end_def_code : return "enddef"; + case mp_def_code : return "def"; + case mp_var_def_code : return "vardef"; + case mp_primary_def_code : return "primarydef"; + case mp_secondary_def_code: return "secondarydef"; + case mp_tertiary_def_code : return "tertiarydef"; + default: return "?def"; + } + break; + +case mp_iteration_command: + switch (m) { + case mp_end_for_code : return "endfor"; + case mp_start_forever_code : return "forever"; + case mp_start_for_code : return "for"; + case mp_start_forsuffixes_code: return "forsuffixes"; + } + break; + +case mp_only_set_command: + switch (m) { + case mp_random_seed_code : return"randomseed"; + case mp_max_knot_pool_code: return"maxknotpool"; + } + 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 { + mp_name_type_type info_mod; + int value_mod; + int value_data; + int padding; + mp_sym info; + struct mp_subst_list_item *link; +} mp_subst_list_item; + +@ @c +static mp_node mp_scan_toks (MP mp, mp_command_code terminator, mp_subst_list_item * subst_list, mp_node tail_end, int suffix_count) +{ + int cur_data; + int cur_data_mod = 0; + mp_node p = mp->hold_head; /* tail of the token list being built */ + int balance = 1; /* left delimiters minus right delimiters */ + mp_link(mp->hold_head) = NULL; + while (1) { + get_t_next(mp); + cur_data = -1; + if (cur_sym != NULL) { + @<Substitute for |cur_sym|, if it's on the |subst_list|@> + if (cur_cmd == terminator) { + @<Adjust the balance; |break| if it's zero@> + } else if (cur_cmd == mp_macro_special_command) { + /* Handle quoted symbols, |\#\AT!|, |\AT!|, or |\AT!\#| */ + if (cur_mod == mp_macro_quote_code) { + get_t_next(mp); + } else if (cur_mod <= suffix_count) { + cur_data = cur_mod - 1; + cur_data_mod = mp_suffix_operation; + } + } + } + if (cur_data != -1) { + mp_node pp = mp_new_symbolic_node(mp); + mp_set_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) { + mp_subst_list_item *q = subst_list->link; + mp_memory_free(subst_list); + subst_list = q; + } + return mp_link(mp->hold_head); +} + +@ @<Substitute for |cur_sym|...@>= +{ + mp_subst_list_item *q = subst_list; + while (q != NULL) { + if (q->info == cur_sym && q->info_mod == cur_sym_mod) { + cur_data = q->value_data; + cur_data_mod = q->value_mod; + set_cur_cmd(mp_relax_command); + break; + } + q = q->link; + } +} + +@ @<Adjust the balance; |break| if it's zero@>= +if (cur_mod > 0) { + ++balance; +} else { + --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|. + +@ @<Enumeration types@>= +typedef enum mp_macro_fix_codes { + mp_macro_quote_code, /* |macro_special| modifier for |quote| */ + mp_macro_prefix_code, /* |macro_special| modifier for |\#\AT!| */ + mp_macro_at_code, /* |macro_special| modifier for |\AT!| */ + mp_macro_suffix_code, /* |macro_special| modifier for |\AT!\#| */ +} mp_macro_fix_codes; + +@ @<Put each...@>= +mp_primitive(mp, "quote", mp_macro_special_command, mp_macro_quote_code); +@:quote_}{|quote| primitive@> +mp_primitive(mp, "#@@", mp_macro_special_command, mp_macro_prefix_code); +@:]]]\#\AT!_}{|\#\AT!| primitive@> +mp_primitive(mp, "@@", mp_macro_special_command, mp_macro_at_code); +@:]]]\AT!_}{|\AT!| primitive@> +mp_primitive(mp, "@@#", mp_macro_special_command, mp_macro_suffix_code); +@:]]]\AT!\#_}{|\AT!\#| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_macro_special_command: + switch (m) { + case mp_macro_prefix_code: return "#@@"; + case mp_macro_at_code : return "@@"; + case mp_macro_suffix_code: return "@@#"; + case mp_macro_quote_code : return "quote"; + } + 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 ((cur_sym == NULL) || mp_is_frozen(mp, cur_sym)) { + const char *hlp = NULL; + if (cur_sym != NULL) { + hlp = + "Sorry: You can't redefine my error-recovery tokens. I've inserted an\n" + "inaccessible symbol so that your definition will be completed without\n" + "mixing me up too badly."; + } else { + hlp = + "Sorry: You can't redefine a number, string, or expr. I've inserted an\n" + "inaccessible symbol so that your definition will be completed without\n" + "mixing me up too badly."; + if (cur_cmd == mp_string_command) { + delete_str_ref(cur_mod_str); + } + } + set_cur_sym(mp->frozen_inaccessible); + mp_ins_error(mp, "Missing symbolic token inserted", hlp); + @.Missing symbolic token...@> + 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, cur_sym, 0); +} + +@ 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 (cur_cmd != mp_equals_command && cur_cmd != mp_assignment_command) { + mp_back_error( + mp, + "Missing '=' has been inserted", + "The next thing in this 'def' should have been '=', because I've already looked at\n" + "the definition heading. But don't worry; I'll pretend that an equals sign was\n" + "present. Everything from here to 'enddef' will be the replacement text of this\n" + "macro." + ); + @.Missing `='@> + } +} + +@ 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, int code) +{ + mp_node q, r; + mp_command_code m = (code == mp_primary_def_code) ? mp_primary_def_command : (code == mp_secondary_def_code ? mp_secondary_def_command : mp_tertiary_def_command); + mp_subst_list_item *qm = NULL; + mp_subst_list_item *qn = NULL; + mp_get_symbol(mp); + qm = mp_memory_allocate(sizeof(mp_subst_list_item)); + qm->link = NULL; + qm->info = cur_sym; + qm->info_mod = cur_sym_mod; + qm->value_data = 0; + qm->value_mod = mp_expr_operation; + mp_get_clear_symbol(mp); + mp->warning_info = cur_sym; + mp_get_symbol(mp); + qn = mp_memory_allocate(sizeof(mp_subst_list_item)); + qn->link = qm; + qn->info = cur_sym; + qn->info_mod = cur_sym_mod; + qn->value_data = 1; + qn->value_mod = mp_expr_operation; + get_t_next(mp); + mp_check_equals(mp); + mp->scanner_status = mp_op_defining_state; + q = mp_new_symbolic_node(mp); + mp_set_ref_count(q, 0); + r = mp_new_symbolic_node(mp); + mp_link(q) = r; + mp_set_sym_info(r, mp_general_macro); + mp_name_type(r) = mp_macro_operation; + mp_link(r) = mp_scan_toks(mp, mp_macro_def_command, qn, NULL, 0); + mp->scanner_status = mp_normal_state; + set_eq_type(mp->warning_info, m); + set_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", mp_parameter_commmand, mp_expr_parameter); +@:expr_}{|expr| primitive@> +mp_primitive(mp, "suffix", mp_parameter_commmand, mp_suffix_parameter); +@:suffix_}{|suffix| primitive@> +mp_primitive(mp, "text", mp_parameter_commmand, mp_text_parameter); +@:text_}{|text| primitive@> +mp_primitive(mp, "primary", mp_parameter_commmand, mp_primary_macro); +@:primary_}{|primary| primitive@> +mp_primitive(mp, "secondary", mp_parameter_commmand, mp_secondary_macro); +@:secondary_}{|secondary| primitive@> +mp_primitive(mp, "tertiary", mp_parameter_commmand, mp_tertiary_macro); +@:tertiary_}{|tertiary| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_parameter_commmand: + switch (m) { + case mp_expr_parameter : return "expr"; + case mp_suffix_parameter: return "suffix"; + case mp_text_parameter : return "text"; + case mp_primary_macro : return "primary"; + case mp_secondary_macro : return "secondary"; + default : return "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 code) +{ + int n; /* the number of special suffix parameters */ + int k; /* the total number of parameters */ + mp_subst_list_item *r = NULL; /* parameter-substitution list */ + mp_subst_list_item *rp = NULL; /* parameter-substitution list */ + mp_node q; /* tail of the macro token list */ + mp_node p; /* temporary storage */ + int sym_type; /* |expr_sym|, |suffix_sym|, or |text_sym| */ + mp_sym l_delim, r_delim; /* matching delimiters */ + int c = mp_general_macro; /* the kind of macro we're defining */ + mp_link(mp->hold_head) = NULL; + q = mp_new_symbolic_node(mp); + mp_set_ref_count(q, 0); + r = NULL; + /* + Scan the token or variable to be defined; set |n|, |scanner_status|, and + |warning_info| + */ + if (code == mp_def_code) { + mp_get_clear_symbol(mp); + mp->warning_info = cur_sym; + get_t_next(mp); + mp->scanner_status = mp_op_defining_state; + n = 0; + set_eq_type(mp->warning_info, mp_defined_macro_command); + set_equiv_node(mp->warning_info, q); + } else { + /* |var_def| */ + p = mp_scan_declared_variable(mp); + mp_flush_variable(mp, equiv_node(mp_get_sym_sym(p)), mp_link(p), 1); + mp->warning_info_node = mp_find_variable(mp, p); + mp_flush_node_list(mp, p); + if (mp->warning_info_node == NULL) { + mp_error( + mp, + "This variable already starts with a macro", + "After 'vardef a' you can't say 'vardef a.b'. So I'll have to discard this\n" + "definition." + ); + mp->warning_info_node = mp->bad_vardef; + } + mp->scanner_status = mp_var_defining_state; + n = 2; + if (cur_cmd == mp_macro_special_command && cur_mod == mp_macro_suffix_code) { + /* |\AT!\#| */ + n = 3; + get_t_next(mp); + } + mp_type(mp->warning_info_node) = mp_unsuffixed_macro_type - 2 + n; + /* |mp_suffixed_macro=mp_unsuffixed_macro+1| */ + mp_set_value_node(mp->warning_info_node, q); + } + k = n; + if (cur_cmd == mp_left_delimiter_command) { + /* Absorb delimited parameters, putting them into lists |q| and |r| */ + do { + l_delim = cur_sym; + r_delim = equiv_sym(cur_sym); + get_t_next(mp); + if (cur_cmd == mp_parameter_commmand) { + switch (cur_mod) { + case mp_expr_parameter: + sym_type = mp_expr_operation; + goto OKAY; + break; + case mp_suffix_parameter: + sym_type = mp_suffix_operation; + goto OKAY; + break; + case mp_text_parameter: + sym_type = mp_text_operation; + goto OKAY; + break; + default: + break; + } + } + mp_back_error( + mp, + "Missing parameter type; 'expr' will be assumed", + "You should've had 'expr' or 'suffix' or 'text' here." + ); + sym_type = mp_expr_operation; + OKAY: + /* Absorb parameter tokens for type |sym_type| */ + do { + mp_link(q) = mp_new_symbolic_node(mp); + q = mp_link(q); + mp_name_type(q) = sym_type; + mp_set_sym_info(q, k); + mp_get_symbol(mp); + rp = mp_memory_allocate(sizeof(mp_subst_list_item)); + rp->link = NULL; + rp->value_data = k; + rp->value_mod = sym_type; + rp->info = cur_sym; + rp->info_mod = cur_sym_mod; + mp_check_param_size(mp, k); + ++k; + rp->link = r; + r = rp; + get_t_next(mp); + } while (cur_cmd == mp_comma_command); + + mp_check_delimiter(mp, l_delim, r_delim); + get_t_next(mp); + } while (cur_cmd == mp_left_delimiter_command); + + } + if (cur_cmd == mp_parameter_commmand) { + /* Absorb undelimited parameters, putting them into list |r| */ + rp = mp_memory_allocate(sizeof(mp_subst_list_item)); + rp->link = NULL; + rp->value_data = k; + switch (cur_mod) { + case mp_expr_parameter: + rp->value_mod = mp_expr_operation; + c = mp_expr_macro; + break; + case mp_suffix_parameter: + rp->value_mod = mp_suffix_operation; + c = mp_suffix_macro; + break; + case mp_text_parameter: + rp->value_mod = mp_text_operation; + c = mp_text_macro; + break; + default: + c = cur_mod; + rp->value_mod = mp_expr_operation; + break; + } + mp_check_param_size(mp, k); + ++k; + mp_get_symbol(mp); + rp->info = cur_sym; + rp->info_mod = cur_sym_mod; + rp->link = r; + r = rp; + get_t_next(mp); + if (c == mp_expr_macro && cur_cmd == mp_of_command) { + c = mp_of_macro; + rp = mp_memory_allocate(sizeof(mp_subst_list_item)); + rp->link = NULL; + mp_check_param_size(mp, k); + rp->value_data = k; + rp->value_mod = mp_expr_operation; + mp_get_symbol(mp); + rp->info = cur_sym; + rp->info_mod = cur_sym_mod; + rp->link = r; + r = rp; + get_t_next(mp); + } + } + mp_check_equals(mp); + p = mp_new_symbolic_node(mp); + mp_set_sym_info(p, c); + mp_name_type(p) = mp_macro_operation; + mp_link(q) = p; + /* + Attach the replacement text to the tail of node |p|. We don't put + |mp->frozen_end_group| into the replacement text of a |vardef|, + because the user may want to redefine |endgroup|. + */ + if (code == mp_def_code) { + mp_link(p) = mp_scan_toks(mp, mp_macro_def_command, r, NULL, (int) n); + } else { + mp_node qq = mp_new_symbolic_node(mp); + mp_set_sym_sym(qq, mp->bg_loc); + mp_link(p) = qq; + p = mp_new_symbolic_node(mp); + mp_set_sym_sym(p, mp->eg_loc); + mp_link(qq) = mp_scan_toks(mp, mp_macro_def_command, r, p, (int) n); + } + if (mp->warning_info_node == mp->bad_vardef) { + mp_flush_token_list(mp, mp_get_value_node(mp->bad_vardef)); + } + mp->scanner_status = mp_normal_state; + mp_get_x_next(mp); +} + +@ @<Glob...@>= +mp_sym bg_loc; +mp_sym eg_loc; /* hash addresses of |begingroup| and |endgroup| */ + +@ @<Initialize table entries@>= +mp->bad_vardef = mp_new_value_node(mp); +mp_name_type(mp->bad_vardef) = mp_root_operation; +mp_set_value_sym(mp->bad_vardef, mp->frozen_bad_vardef); + +@ @<Free table entries@>= +mp_free_value_node(mp, mp->bad_vardef); + +@* 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|, |run_script|, |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_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@> + +In any case, when the limit is crossed, that is a fatal error. + +@c +static void mp_check_expansion_depth (MP mp) +{ + if (++mp->expand_depth_count >= mp->expand_depth) { + if (mp->interaction >= mp_error_stop_mode) { + mp->interaction=mp_scroll_mode; /* no more interaction */ + } + mp_error( + mp, + "Maximum expansion depth reached", + "Recursive macro expansion cannot be unlimited because of runtime stack\n" + "constraints. The limit is 10000 recursion levels in total." + ); + mp->history=mp_fatal_error_stop; + mp_jump_out(mp); + } +} + +@ 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) +{ + mp_check_expansion_depth(mp); + if (number_greater(internal_value(mp_tracing_commands_internal), unity_t) && cur_cmd != mp_defined_macro_command) { + mp_show_cmd_mod(mp, cur_cmd, cur_mod); + } + switch (cur_cmd) { + case mp_if_test_command: + mp_conditional(mp); /* this procedure is discussed in Part 36 below */ + break; + case mp_fi_or_else_command: + @<Terminate the current conditional and skip to |fi|@> + break; + case mp_input_command: + @<Initiate or terminate input from a file@> + break; + case mp_iteration_command: + if (cur_mod == mp_end_for_code) { + @<Scold the user for having an extra |endfor|@> + } else { + mp_begin_iteration(mp); /* this procedure is discussed in Part 37 below */ + } + break; + case mp_repeat_loop_command: + @<Repeat a loop@> + break; + case mp_exit_test_command: + @<Exit a loop if the proper time has come@> + break; + case mp_relax_command: + break; + case mp_expand_after_command: + @<Expand the token after the next token@> + break; + case mp_scan_tokens_command: + @<Put a string into the input buffer@> + break; + case mp_runscript_command: + @<Put a script result string into the input buffer@> + break; + case mp_maketext_command: + @<Put a maketext result string into the input buffer@> + break; + case mp_defined_macro_command: + mp_macro_call(mp, cur_mod_node, NULL, cur_sym); + break; + default: + break; + }; + mp->expand_depth_count--; +} + +@ @<Scold the user...@>= +{ + mp_error( + mp, + "Extra 'endfor'", + "I'm not currently working on a for loop, so I had better not try to end anything." + ); + @.Extra `endfor'@> +} + +@ 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", mp_input_command, 0); +@:input_}{|input| primitive@> +mp_primitive(mp, "endinput", mp_input_command, 1); +@:end_input_}{|endinput| primitive@> + +@ @<Cases of |print_cmd_mod|...@>= +case mp_input_command: + return m == 0 ? "input" : "endinput"; + +@ @<Initiate or terminate input...@>= +if (cur_mod > 0) { + mp->force_eof = 1; +} 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) { + mp_error( + mp, + "Lost loop", + "I'm confused; after exiting from a loop, I still seem to want to repeat it. I'll\n" + "try to forget the problem." + ); + @.Lost loop@> + } else { + mp_resume_iteration(mp); /* this procedure is in Part 37 below */ + } +} + +@ @<Exit a loop if the proper time has come@>= +{ + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type != mp_boolean_type) { + do_boolean_error(mp); + } + if (number_greater(internal_value(mp_tracing_commands_internal), unity_t)) { + mp_show_cmd_mod(mp, mp_nullary_command, cur_exp_value_boolean); + } + if (cur_exp_value_boolean == mp_true_operation) { + if (mp->loop_ptr != NULL) { + @<Exit prematurely from an iteration@> + } else if (cur_cmd == mp_semicolon_command) { + mp_error( + mp, + "No loop is in progress", + "Why say 'exitif' when there's nothing to exit from?" + ); + } else { + mp_back_error( + mp, + "No loop is in progress", + "Why say 'exitif' when there's nothing to exit from?" + ); + @.No loop is in progress@> + } + } else if (cur_cmd != mp_semicolon_command) { + mp_back_error( + mp, + "Missing ';' has been inserted", + "After 'exitif <boolean exp>' I expect to see a semicolon. I shall pretend that\n" + "one was there." + ); + @.Missing `;'@> + } +} + +@ 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 <= mp_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 (cur_cmd < mp_min_command) { + mp_expand(mp); + } else { + mp_back_input(mp); + } + mp_begin_token_list(mp, p, mp_backed_up_text); +} + +@ @<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)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Not a string", + "I'm going to flush this expression, since scantokens should be followed by a\n" + "known string." + ); + @.Not a string@> + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else { + mp_back_input(mp); + if (cur_exp_str->len > 0) { + @<Pretend we're reading a new one-line file@> + } + } +} + +@ @<Declarations@>= +static void check_script_result (MP mp, char *s); + +@c +void check_script_result (MP mp, char *s) +{ + if (s) { + size_t size = strlen(s); + if (size > 0) { + size_t k ; + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_begin_file_reading(mp); + name = is_scantok; + mp->last = mp->first; + k = mp->first + size; + 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; + } + limit = (int) k; + memcpy((mp->buffer + mp->first), s, size); + mp->buffer[limit] = '%'; + mp->first = (size_t) (limit + 1); + loc = start; + mp_flush_cur_exp(mp, new_expr); + } + lmt_memory_free(s); + } +} + +@ @<Put a script result string into the input buffer@>= +{ + if (mp->extensions) { + mp_get_x_next(mp); + mp_scan_primary(mp); + switch (mp->cur_exp.type) { + case mp_string_type: + { + mp_back_input(mp); + if (cur_exp_str->len > 0) { + check_script_result(mp, mp->run_script(mp, (const char*) cur_exp_str->str, cur_exp_str->len, 0)); + } + } + break; + case mp_numeric_type: + case mp_known_type: + { + int n = 0 ; + mp_back_input(mp); + n = (int) number_to_scaled (cur_exp_value_number) / 65536; + if (n > 0) { + check_script_result(mp, mp->run_script(mp, NULL, 0, n)); + } + } + break; + default: + { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Not a string", + "I'm going to flush this expression, since runscript should be followed by a known\n" + "string or number." + ); + @.Not a string@> + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } + break; + } + } +} + +@ The |texscriptmode| parameter controls how spaces and newlines get honoured in +|btex| or |verbatimtex| ... |etex|. The default value is~1. Possible values are: +0: no newlines, 1: newlines in |verbatimtex|, 2: newlines in |verbatimtex| and +|etex|, 3: no leading and trailing strip in |verbatimtex|, 4: no leading and +trailing strip in |verbatimtex| and |btex|. That way the Lua handler can do what +it likes. An |etex| has to be followed by a space or |;| or be at the end of a +line and preceded by a space or at the beginning of a line. + +@<Pass btex ... etex to script@>= +char *txt = NULL; +char *ptr = NULL; +int slin = line; +int size = 0; +int done = 0; +int mode = round_unscaled(internal_value(mp_texscriptmode_internal)) ; /* default: 1 */ +int verb = cur_mod == mp_verbatim_code; +int first; +/* we had a (mandate) trailing space */ +if (loc <= limit && mp->char_class[mp->buffer[loc]] == mp_space_class) { + ++loc; +} else { + /* maybe issue an error message and quit */ +} +/* we loop over lines */ +first = loc; +while (1) { + /* we don't need to check when we have less than 4 characters left */ + if (loc < limit - 4) { + if (mp->buffer[loc] == 'e') { + ++loc; + if (mp->buffer[loc] == 't') { + ++loc; + if (mp->buffer[loc] == 'e') { + ++loc; + if (mp->buffer[loc] == 'x') { + /* let's see if we have the right boundary */ + if (first == (loc - 3)) { + /* when we're at the start of a line no leading space is required */ + done = 1; + } else if (mp->char_class[mp->buffer[loc - 4]] == mp_space_class) { + /* when we're beyond the start of a line a leading space is required */ + done = 2; + } + if (done) { + if ((loc + 1) <= limit) { + int c = mp->char_class[mp->buffer[loc + 1]] ; + if (c != mp_letter_class) { + ++loc; + /* we're past the 'x' */ + break; + } else { + /* this is no valid etex */ + done = 0; + } + } else { + /* when we're at the end of a line we're ok */ + ++loc; + /* we're past the 'x' */ + break; + } + } + } + } + } + } + } + /* no etex seen (yet) */ + if (loc >= limit) { + if (size) { + txt = mp_memory_reallocate(txt, (size_t) (size + limit - first + 1)); + } else { + txt = mp_memory_allocate((size_t) (limit - first + 1)); + } + memcpy(txt + size, mp->buffer + first, limit - first); + size += limit - first + 1; + if (mode <= 0) { + txt[size - 1] = ' '; + } else if (verb) { + /* modes $\geq 1$ permit a newline in verbatimtex */ + txt[size - 1] = '\n'; + } else if (mode >= 2) { + /* modes $\geq 2$ permit a newline in btex */ + txt[size - 1] = '\n'; + } else { + txt[size - 1] = ' '; + } + if (mp_move_to_next_line(mp)) { + /* we abort the scanning */ + goto FATAL_ERROR; + } + first = loc; + } else { + ++loc; + } +} +if (done) { + /* we're past the 'x' */ + int l = loc - 5 ; // 4 + int n = l - first + 1 ; + /* we're before the 'etex' */ + if (done == 2) { + /* we had ' etex' */ + l -= 1; + n -= 1; + /* we're before the ' etex' */ + } + if (size) { + txt = mp_memory_reallocate(txt, (size_t) (size + n + 1)); + } else { + txt = mp_memory_allocate((size_t) (n + 1)); + } + memcpy(txt + size, mp->buffer + first, n); /* 0 */ + size += n; + if (verb && mode >= 3) { + /* don't strip verbatimtex */ + txt[size] = '\0'; + ptr = txt; + } else if (mode >= 4) { + /* don't strip btex */ + txt[size] = '\0'; + ptr = txt; + } else { + /* strip trailing whitespace, we have a |'\0'| so we are off by one */ + while ((size > 1) && (mp->char_class[(unsigned char) txt[size-1]] == mp_space_class || txt[size-1] == '\n')) { + --size; + } + /* prune the string */ + txt[size] = '\0'; + /* strip leading whitespace */ + ptr = txt; + while ((size > 1) && (mp->char_class[(unsigned char) ptr[0]] == mp_space_class || ptr[0] == '\n')) { + ++ptr; + --size; + } + } + /* action */ + check_script_result(mp, mp->make_text(mp, ptr, size, verb)); + mp_memory_free(txt); + /* really needed */ + mp_get_next(mp); + return; +} +/* + We don't recover because in practice the graphic will be broken anyway and + we're not really interacting in mplib .. just fix the input. +*/ +FATAL_ERROR: +{ + /* line numbers are not always meaningfull so we can get a 0 reported */ + char msg[256]; + if (slin > 0) { + mp_snprintf(msg, 256, "No matching 'etex' for '%stex'.", verb ? "verbatim" : "b"); + } else { + mp_snprintf(msg, 256, "No matching 'etex' for '%stex' in line %d.", verb ? "verbatim" : "b",slin); + } + mp_error(mp, msg, "An 'etex' is missing at this input level, nothing gets done."); + mp_memory_free(txt); +} + +@ @<Put a maketext result string into the input buffer@>= +{ + if (mp->extensions) { + mp_get_x_next(mp); + mp_scan_primary(mp); + if (mp->cur_exp.type == mp_string_type) { + mp_back_input(mp); + if (cur_exp_str->len > 0) { + check_script_result(mp, mp->make_text(mp, (const char*) cur_exp_str->str, cur_exp_str->len, 0)); + } + } else { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Not a string", + "I'm going to flush this expression, since 'maketext' should be followed by a\n" + "known string." + ); + @.Not a string@> + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } + } +} + +@ @<Pretend we're reading a new one-line file@>= +size_t k; /* something that we hope is |<=buf_size| */ +size_t j; /* index into |str_pool| */ +mp_value new_expr; +memset(&new_expr, 0, sizeof(mp_value)); +new_number(new_expr.data.n); +mp_begin_file_reading(mp); +name = is_scantok; +k = mp->first + (size_t) cur_exp_str->len; +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 = (int) k; +while (mp->first < (size_t) limit) { + mp->buffer[mp->first] = *(cur_exp_str->str + j); + j++; + ++mp->first; +} +mp->buffer[limit] = '%'; +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 +static void mp_get_x_next (MP mp) +{ + get_t_next(mp); + if (cur_cmd < mp_min_command) { + /* the capsule to save |cur_type| and |cur_exp| */ + mp_node save_exp = mp_stash_cur_exp(mp); + do { + if (cur_cmd == mp_defined_macro_command) { + mp_macro_call(mp, cur_mod_node, NULL, cur_sym); + } else { + mp_expand(mp); + } + get_t_next(mp); + } while (cur_cmd < mp_min_command); + /* that restores |cur_type| and |cur_exp| */ + mp_unstash_cur_exp(mp, save_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 +\quote {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); + +@ This invokes a user-defined control sequence. + +@c +static void mp_macro_call (MP mp, mp_node def_ref, mp_node arg_list, mp_sym macro_name) +{ + int n; /* the number of arguments */ + mp_node tail = 0; /* tail of the argument list */ + mp_sym l_delim = NULL; /* a delimiter pair */ + mp_sym r_delim = NULL; /* a delimiter pair */ + mp_node r = mp_link(def_ref); /* current node in the macro's token list */ + mp_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 (number_positive(internal_value(mp_tracing_macros_internal))) { + @<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_str(mp, "@@#"); /* indicate a suffixed macro */ +} +mp_show_macro (mp, def_ref, NULL); +if (arg_list != NULL) { + mp_node p = arg_list; + n = 0; + do { + mp_node q = (mp_node) mp_get_sym_sym(p); + mp_print_arg(mp, q, n, 0, 0); + ++n; + p = mp_link(p); + } while (p != NULL); +} +mp_end_diagnostic(mp, 0); + +@ @<Declarations@>= +static void mp_print_macro_name (MP mp, mp_node a, mp_sym n); + +@ @c +static void mp_print_macro_name (MP mp, mp_node a, mp_sym n) +{ + if (n) { + mp_print_mp_str(mp,text(n)); + } else { + mp_node p = (mp_node) mp_get_sym_sym(a); + if (p) { + mp_node q = p; /* they traverse the first part of |a| */ + while (mp_link(q) != NULL) { + q = mp_link(q); + } + mp_link(q) = (mp_node) mp_get_sym_sym(mp_link(a)); + mp_show_token_list(mp, p, NULL); + mp_link(q) = NULL; + } else { + mp_print_mp_str(mp,text(mp_get_sym_sym((mp_node) mp_get_sym_sym(mp_link(a))))); + } + } +} + +@ @<Declarations@>= +static void mp_print_arg (MP mp, mp_node q, int n, int b, int bb); + +@ @c +static void mp_print_arg (MP mp, mp_node q, int n, int b, int bb) +{ + if (q && mp_link(q) == MP_VOID) { + mp_print_nl(mp, "(EXPR"); + } else if ((bb < mp_text_operation) && (b != mp_text_macro)) { + mp_print_nl(mp, "(SUFFIX"); + } else { + mp_print_nl(mp, "(TEXT"); + } + mp_print_int(mp, n); + mp_print_str(mp, ")<-"); + if (q && mp_link(q) == MP_VOID) { + mp_print_exp(mp, q, 1); + } else { + mp_show_token_list(mp, q, NULL); + } +} + +@ @<Determine the number |n| of arguments already supplied...@>= +n = 1; +tail = arg_list; +while (mp_link(tail) != NULL) { + ++n; + tail = mp_link(tail); +} + +@ @<Scan the remaining arguments, if any; set |r|...@>= +set_cur_cmd(mp_comma_command + 1); /* anything |<>comma| will do */ +while (mp_name_type(r) == mp_expr_operation || mp_name_type(r) == mp_suffix_operation || mp_name_type(r) == mp_text_operation) { + @<Scan the delimited argument represented by |mp_get_sym_info(r)|@> + r = mp_link(r); +} +if (cur_cmd == mp_comma_command) { + char msg[256]; + mp_string rname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_macro_name(mp, arg_list, macro_name); + rname = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "Too many arguments to %s; Missing '%s' has been inserted", + mp_str(mp, rname), mp_str(mp, text(r_delim))); + delete_str_ref(rname); + @.Too many arguments...@> + @.Missing `)'...@> + mp_error( + mp, + msg, + "I'm going to assume that the comma I just read was a right delimiter, and then:\n" + "I'll begin expanding the macro." + ); +} +if (mp_get_sym_info(r) != mp_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_get_sym_info(r)|@>= +if (cur_cmd != mp_comma_command) { + mp_get_x_next(mp); + if (cur_cmd != mp_left_delimiter_command) { + char msg[256]; + mp_string sname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_macro_name(mp, arg_list, macro_name); + sname = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "Missing argument to %s", mp_str(mp, sname)); + @.Missing argument...@> + delete_str_ref(sname); + if (mp_name_type(r) == mp_suffix_operation || mp_name_type(r) == mp_text_operation) { + mp_set_cur_exp_value_number(mp, &zero_t); /* todo: this was |null| */ + mp->cur_exp.type = mp_token_list_type; + } else { + mp_set_cur_exp_value_number(mp, &zero_t); + mp->cur_exp.type = mp_known_type; + } + mp_back_error( + mp, + msg, + "That macro has more parameters than you thought. I'll continue by pretending that\n" + "each missing argument is either zero or null." + ); + set_cur_cmd(mp_right_delimiter_command); + goto FOUND; + } + l_delim = cur_sym; + r_delim = equiv_sym(cur_sym); +} +@<Scan the argument represented by |mp_get_sym_info(r)|@> +if ((cur_cmd != mp_comma_command) && ((cur_cmd != mp_right_delimiter_command) || (equiv_sym(cur_sym) != l_delim))) { + switch (mp_name_type(mp_link(r))) { + case mp_expr_operation: + case mp_suffix_operation: + case mp_text_operation: + { + mp_back_error( + mp, + "Missing ',' has been inserted", + "I've finished reading a macro argument and am about to read another; the\n" + "arguments weren't delimited correctly." + ); + @.Missing `,'@> + set_cur_cmd(mp_comma_command); + } + break; + default: + { + char msg[256]; + mp_snprintf(msg, 256, "Missing '%s' has been inserted", mp_str(mp, text(r_delim))); + @.Missing `)'@> + mp_back_error(mp, msg, "I've gotten to the end of the macro parameter list."); + } + break; + } +} +FOUND: +@<Append the current expression to |arg_list|@> + +@ 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|@>= +{ + mp_node p = mp_new_symbolic_node(mp); + if (mp->cur_exp.type == mp_token_list_type) { + mp_set_sym_sym(p, mp->cur_exp.data.node); + } else { + mp_set_sym_sym(p, mp_stash_cur_exp(mp)); + } + if (number_positive(internal_value(mp_tracing_macros_internal))) { + mp_begin_diagnostic(mp); + mp_print_arg(mp, (mp_node) mp_get_sym_sym(p), n, mp_get_sym_info(r), mp_name_type(r)); + mp_end_diagnostic(mp, 0); + } + if (arg_list == NULL) { + arg_list = p; + } else { + mp_link(tail) = p; + } + tail = p; + ++n; +} + +@ @<Scan the argument represented by |mp_get_sym_info(r)|@>= +if (mp_name_type(r) == mp_text_operation) { + mp_scan_text_arg(mp, l_delim, r_delim); +} else { + mp_get_x_next(mp); + if (mp_name_type(r) == mp_suffix_operation) { + 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) +{ + int balance = 1; /* excess of |l_delim| over |r_delim| */ + mp->warning_info = l_delim; + mp->scanner_status = mp_absorbing_state; + mp_node p = mp->hold_head; /* list tail */ + 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); + } + mp_set_cur_exp_node(mp, mp_link(mp->hold_head)); + mp->cur_exp.type = mp_token_list_type; + mp->scanner_status = mp_normal_state; +} + +@ @<Adjust the balance for a delimited argument...@>= +if (cur_cmd == mp_right_delimiter_command) { + if (equiv_sym(cur_sym) == l_delim) { + --balance; + if (balance == 0) { + break; + } + } +} else if (cur_cmd == mp_left_delimiter_command) { + if (equiv_sym(cur_sym) == r_delim) { + ++balance; + } +} + +@ @<Adjust the balance for an undelimited...@>= +if (mp_end_of_statement) { + /* |cur_cmd=semicolon|, |end_group|, or |stop| */ + if (balance == 1) { + break; + } else if (cur_cmd == mp_end_group_command) { + --balance; + } +} else if (cur_cmd == mp_begin_group_command) { + ++balance; +} + +@ @<Scan undelimited argument(s)@>= +if (mp_get_sym_info(r) < mp_text_macro) { + mp_get_x_next(mp); + if (mp_get_sym_info(r) != mp_suffix_macro) { + if ((cur_cmd == mp_equals_command) || (cur_cmd == mp_assignment_command)) { + mp_get_x_next(mp); + } + } +} +switch (mp_get_sym_info(r)) { + case mp_primary_macro: + mp_scan_primary(mp); + break; + case mp_secondary_macro: + mp_scan_secondary(mp); + break; + case mp_tertiary_macro: + mp_scan_tertiary(mp); + break; + case mp_expr_macro: + mp_scan_expression(mp); + break; + case mp_of_macro: + { + @<Scan an expression followed by |of| $\langle$primary$\rangle$@> + } + break; + case mp_suffix_macro: + { + @<Scan a suffix with optional delimiters@> + } + break; + case mp_text_macro: + mp_scan_text_arg(mp, NULL, NULL); + break; +} +mp_back_input(mp); +@<Append the current expression to |arg_list|@> + +@ @<Scan an expression followed by |of| $\langle$primary$\rangle$@>= +mp_node p; +mp_scan_expression(mp); +p = mp_new_symbolic_node(mp); +mp_set_sym_sym(p, mp_stash_cur_exp(mp)); +if (number_positive(internal_value(mp_tracing_macros_internal))) { + mp_begin_diagnostic(mp); + mp_print_arg(mp, (mp_node) mp_get_sym_sym(p), n, 0, 0); + mp_end_diagnostic(mp, 0); +} +if (arg_list == NULL) { + arg_list = p; +} else { + mp_link(tail) = p; +} +tail = p; +++n; +if (cur_cmd != mp_of_command) { + char msg[256]; + mp_string sname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_macro_name(mp, arg_list, macro_name); + sname = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "Missing 'of' has been inserted for %s", mp_str(mp, sname)); + delete_str_ref(sname); + @.Missing `of'@> + mp_back_error(mp, msg, "I've got the first argument; will look now for the other."); +} +mp_get_x_next(mp); +mp_scan_primary(mp); + +@ @<Scan a suffix with optional delimiters@>= +if (cur_cmd != mp_left_delimiter_command) { + l_delim = NULL; +} else { + l_delim = cur_sym; + r_delim = equiv_sym(cur_sym); + mp_get_x_next(mp); +} +mp_scan_suffix(mp); +if (l_delim != NULL) { + if ((cur_cmd != mp_right_delimiter_command) || (equiv_sym(cur_sym) != l_delim)) { + char msg[256]; + mp_snprintf(msg, 256, "Missing '%s' has been inserted", mp_str(mp, text(r_delim))); + @.Missing `)'@> + mp_back_error(mp, msg, "I've gotten to the end of the macro parameter list."); + } + 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)) { + /* conserve stack space */ + mp_end_token_list(mp); +} +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, mp_macro_text); +name = macro_name ? text(macro_name) : NULL; +nloc = r; +if (n > 0) { + mp_node p = arg_list; + do { + mp->param_stack[mp->param_ptr] = (mp_node) mp_get_sym_sym(p); + ++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) { + ++mp->max_param_stack; + mp_check_param_size(mp, mp->max_param_stack); + } + mp->param_stack[mp->param_ptr] = p; + ++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 mp_if_line_field(A) ((mp_if_node) (A))->if_line_field + +@ @<Enumeration types@>= +typedef enum mp_if_codes { + mp_no_if_code, + mp_if_code, /* code for |if| being evaluated */ + mp_fi_code, /* code for |fi| */ + mp_else_code, /* code for |else| */ + mp_else_if_code, /* code for |elseif| */ +} mp_if_codes; + +@ @<MPlib internal header stuff@>= +typedef struct mp_if_node_data { + mp_variable_type type; + mp_name_type_type name_type; + int hasnumber; + int if_line_field; + struct mp_node_data *link; +} mp_if_node_data; + +typedef struct mp_if_node_data *mp_if_node; + +@c +static mp_node mp_get_if_node (MP mp) { + mp_if_node p = (mp_if_node) mp_allocate_node(mp, sizeof(mp_if_node_data)); + mp_type(p) = mp_if_node_type; + return (mp_node) p; +} + +@ @<Glob...@>= +mp_node cond_ptr; /* top of the condition stack */ +int if_limit; /* upper bound on |fi_or_else| codes */ +int cur_if; /* type of conditional being worked on */ +int if_line; /* line where that conditional began */ + +@ @<Set init...@>= +mp->cond_ptr = NULL; +mp->if_limit = mp_no_if_code; +mp->cur_if = 0; +mp->if_line = 0; + +@ @<Put each...@>= +mp_primitive(mp, "if", mp_if_test_command, mp_if_code); +@:if_}{|if| primitive@> +mp_primitive(mp, "fi", mp_fi_or_else_command, mp_fi_code); +mp->frozen_fi = mp_frozen_primitive (mp, "fi", mp_fi_or_else_command, mp_fi_code); +@:fi_}{|fi| primitive@> +mp_primitive(mp, "else", mp_fi_or_else_command, mp_else_code); +@:else_}{|else| primitive@> +mp_primitive(mp, "elseif", mp_fi_or_else_command, mp_else_if_code); +@:else_if_}{|elseif| primitive@> + +@ @<Cases of |print_cmd_mod|...@>= +case mp_if_test_command: +case mp_fi_or_else_command: + switch (m) { + case mp_if_code : return "if"; + case mp_fi_code : return "fi"; + case mp_else_code : return "else"; + case mp_else_if_code: return "elseif"; + } + 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) +{ + int level = 0; + mp->scanner_status = mp_skipping_state; + mp->warning_line = mp_true_line(mp); + while (1) { + get_t_next(mp); + if (cur_cmd <= mp_fi_or_else_command) { + if (cur_cmd < mp_fi_or_else_command) { + ++level; + } else if (level == 0) { + break; + } else if (cur_mod == mp_fi_code) { + --level; + } + } else { + @<Decrease the string reference count, if the current token is a string@> + } + } + mp->scanner_status = mp_normal_state; +} + +@ @<Decrease the string reference count...@>= +if (cur_cmd == mp_string_command) { + delete_str_ref(cur_mod_str); +} + +@ When we begin to process a new |if|, we set |if_limit:=mp_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. + +@<Declarations@>= +static void mp_push_condition_stack (MP mp); +static void mp_pop_condition_stack (MP mp); + +@ Push and pop the condition stack: + +@c +static void mp_push_condition_stack (MP mp) +{ + mp_node p = mp_get_if_node(mp); + mp_link(p) = mp->cond_ptr; + mp_type(p) = (int) mp->if_limit; + mp_name_type(p) = mp->cur_if; + mp_if_line_field(p) = mp->if_line; + mp->cond_ptr = p; + mp->if_limit = mp_if_code; + mp->if_line = mp_true_line(mp); + mp->cur_if = mp_if_code; +} + +static void mp_pop_condition_stack (MP mp) +{ + mp_node p = mp->cond_ptr; + mp->if_line = mp_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, sizeof(mp_if_node_data)); +} +@ 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, int l, mp_node p) +{ + if (p == mp->cond_ptr) { + /* that's the easy case */ + mp->if_limit = l; + } else { + mp_node q = mp->cond_ptr; + while (1) { + if (q == NULL) { + mp_confusion(mp, "if"); + @:this can't happen if}{\quad if@> + return; + } else if (mp_link(q) == p) { + mp_type(q) = l; + return; + } else { + 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 (cur_cmd != mp_colon_command) { + mp_back_error( + mp, + "Missing ':' has been inserted", + "There should've been a colon after the condition. I shall pretend that one was\n" + "there." + ); + @.Missing `:'@> + } +} + +@ 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_push_condition_stack(mp); + save_cond_ptr = mp->cond_ptr; + RESWITCH: + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type != mp_boolean_type) { + do_boolean_error(mp); + } + new_if_limit = mp_else_if_code; + if (number_greater(internal_value(mp_tracing_commands_internal), unity_t)) { + @<Display the boolean value of |cur_exp|@> + } + FOUND: + mp_check_colon(mp); + if (cur_exp_value_boolean == mp_true_operation) { + mp_change_if_limit (mp, (int) new_if_limit, save_cond_ptr); + /* wait for |elseif|, |else|, or |fi| */ + return; + } + @<Skip to |elseif| or |else| or |fi|, then |goto done|@> + DONE: + mp->cur_if = (int) cur_mod; + mp->if_line = mp_true_line(mp); + if (cur_mod == mp_fi_code) { + mp_pop_condition_stack(mp); + } else if (cur_mod == mp_else_if_code) { + goto RESWITCH; + } else { + mp_set_cur_exp_value_boolean(mp, mp_true_operation); + new_if_limit = mp_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 (cur_mod == mp_fi_code) { + mp_pop_condition_stack(mp); + } +} + +@ @<Display the boolean value...@>= +mp_begin_diagnostic(mp); +mp_print_str(mp, cur_exp_value_boolean == mp_true_operation ? "{true}" : "{false}"); +mp_end_diagnostic(mp, 0); + +@ The processing of conditionals is complete except for the following code, which +is actually part of |get_x_next|. It comes into play when |elseif|, |else|, +or |fi| is scanned. + +@<Terminate the current conditional and skip to |fi|@>= +if (cur_mod > mp->if_limit) { + if (mp->if_limit == mp_if_code) { + /* condition not yet evaluated */ + mp_back_input(mp); + set_cur_sym(mp->frozen_colon); + mp_ins_error(mp, "Missing ':' has been inserted", "Something was missing here"); + @.Missing `:'@> + } else { + const char *hlp = "I'm ignoring this; it doesn't match any if."; + switch (cur_mod) { + case mp_fi_code: + mp_error(mp, "Extra 'fi'", hlp); + @.Extra fi@> + break; + case mp_else_code: + mp_error(mp, "Extra 'else'", hlp); + @.Extra else@> + break; + default: + mp_error(mp, "Extra 'elseif'", hlp); + @.Extra elseif@> + break; + } + } +} else { + while (cur_mod != mp_fi_code) { + /* skip to |fi| */ + mp_pass_text(mp); + } + mp_pop_condition_stack(mp); +} + +@* 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=MP_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>MP_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 MP_VOID (mp_node) (1) /* |NULL+1|, a |NULL| pointer different from |NULL| */ +@d MP_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_sym var ; /* the var of the loop */ + 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 */ + mp_number old_value; /* previous value of current arithmetic value */ + mp_number value; /* current arithmetic value */ + mp_number step_size; /* arithmetic step size */ + mp_number final_value; /* end arithmetic value */ + struct mp_loop_data *link; /* the enclosing loop, if any */ + mp_knot point; +} 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) +{ + char msg[256]; + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + /* show the bad expression above the message */ + mp_snprintf(msg, 256, "Improper %s has been replaced by 0", s); + @.Improper...replaced by 0@> + mp_back_error( + mp, + msg, + "When you say 'for x=a step b until c', the initial value 'a' and the step size\n" + "'b' and the final value 'c' must have known numeric values. I'm zeroing this one.\n" + "Proceed, with fingers crossed." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(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) +{ + mp_node q; /* link manipulation register */ + mp_sym n = cur_sym; /* hash address of the current symbol */ + mp_subst_list_item *p = NULL; /* substitution list for |scan_toks| */ + int m = cur_mod; /* |start_for| (|for|) or |start_forsuffixes| (|forsuffixes|) */ + mp_loop_data *s = mp_memory_allocate(sizeof(mp_loop_data)); /* the new loop-control node */ + s->type = NULL; + s->list = NULL; + s->info = NULL; + s->list_start = NULL; + s->link = NULL; + s->var = NULL; + s->point = NULL; + new_number(s->value); + new_number(s->old_value); + new_number(s->step_size); + new_number(s->final_value); + if (m == mp_start_forever_code) { + s->type = MP_VOID; + mp_get_x_next(mp); + } else { + mp_get_symbol(mp); + p = mp_memory_allocate(sizeof(mp_subst_list_item)); + p->link = NULL; + p->info = cur_sym; + s->var = cur_sym; + p->info_mod = cur_sym_mod; + p->value_data = 0; + if (m == mp_start_for_code) { + p->value_mod = mp_expr_operation; + } else { + /* |start_forsuffixes| */ + p->value_mod = mp_suffix_operation; + } + mp_get_x_next(mp); + if (p->value_mod == mp_expr_operation && cur_cmd == mp_within_command) { + @<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 ((cur_cmd != mp_equals_command) && (cur_cmd != mp_assignment_command)) { + mp_back_error( + mp, + "Missing '=' has been inserted", + "The next thing in this loop should have been '=' or ':='. But don't worry; I'll\n" + "pretend that an equals sign was present, and I'll look for the values next." + ); + @.Missing `='@> +} + +@ @<Check for the presence of a colon@>= +if (cur_cmd != mp_colon_command) { + mp_back_error( + mp, + "Missing ':' has been inserted", + "The next thing in this loop should have been a ':'. So I'll pretend that a colon\n" + "was present; everything from here to 'endfor' will be iterated." + ); + @.Missing `:'@> +} + +@ 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_new_symbolic_node(mp); +mp_set_sym_sym(q, mp->frozen_repeat_loop); +mp->scanner_status = mp_loop_defining_state; +mp->warning_info = n; +s->info = mp_scan_toks(mp, mp_iteration_command, p, q, 0); +mp->scanner_status = mp_normal_state; +s->link = mp->loop_ptr; +mp->loop_ptr = s; + +@ @<Initialize table...@>= +mp->frozen_repeat_loop = +//mp_frozen_primitive (mp, " ENDFOR", mp_repeat_loop_command + mp_outer_tag_command, 0); +mp_frozen_primitive (mp, " ENDFOR", mp_repeat_loop_command, 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 == MP_PROGRESSION_FLAG) { + mp_set_cur_exp_value_number(mp, &(mp->loop_ptr->value)); + if ((number_positive(mp->loop_ptr->step_size) && number_greater(cur_exp_value_number, mp->loop_ptr->final_value)) + || (number_negative(mp->loop_ptr->step_size) && number_less (cur_exp_value_number, mp->loop_ptr->final_value))) { + mp_stop_iteration(mp); + return; + } + mp->cur_exp.type = mp_known_type; + /* make |q| an |expr| argument */ + q = mp_stash_cur_exp(mp); + number_clone(mp->loop_ptr->old_value, cur_exp_value_number); + set_number_from_addition(mp->loop_ptr->value, cur_exp_value_number, mp->loop_ptr->step_size); + /* Set |value(p)| for the next iteration and detect numeric overflow */ + if (number_positive(mp->loop_ptr->step_size) && number_less(mp->loop_ptr->value, cur_exp_value_number)) { + if (number_positive(mp->loop_ptr->final_value)) { + number_clone(mp->loop_ptr->value, mp->loop_ptr->final_value); + number_add_scaled(mp->loop_ptr->final_value, -1); + } else { + number_clone(mp->loop_ptr->value, mp->loop_ptr->final_value); + number_add_scaled(mp->loop_ptr->value, 1); + } + } else if (number_negative(mp->loop_ptr->step_size) && number_greater(mp->loop_ptr->value, cur_exp_value_number)) { + if (number_negative(mp->loop_ptr->final_value)) { + number_clone(mp->loop_ptr->value, mp->loop_ptr->final_value); + number_add_scaled(mp->loop_ptr->final_value, 1); + } else { + number_clone(mp->loop_ptr->value, mp->loop_ptr->final_value); + number_add_scaled(mp->loop_ptr->value, -1); + } + } + if (mp->loop_ptr->point != NULL) { + mp->loop_ptr->point = mp_next_knot(mp->loop_ptr->point); + } + } 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_get_sym_sym(p); + if (q) { + number_clone(mp->loop_ptr->old_value, q->data.n); + } + mp_free_symbolic_node(mp, p); + } else if (p == MP_VOID) { + mp_begin_token_list(mp, mp->loop_ptr->info, mp_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, mp_loop_text); + mp_stack_argument(mp, q); + if (number_greater(internal_value(mp_tracing_commands_internal), unity_t)) { + @<Trace the start of a loop@> + } + return; + NOT_FOUND: + mp_stop_iteration(mp); +} + +@ @<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); +} +mp_print_chr(mp, '}'); +mp_end_diagnostic(mp, 0); + +@ @<Make |q| a capsule containing the next picture component from...@>= +q = mp->loop_ptr->list; +if (q == NULL) { + goto NOT_FOUND; +} else if (! mp_is_start_or_stop(q)) { + q = mp_link(q); +} else if (! mp_is_stop(q)) { + q = mp_skip_1component(mp, q); +} else { + goto NOT_FOUND; +} +mp_set_cur_exp_node(mp, (mp_node) mp_copy_objects (mp, mp->loop_ptr->list, q)); +mp_init_bbox(mp, (mp_edge_header_node) 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 = mp->loop_ptr->type; + if (p == MP_PROGRESSION_FLAG) { + mp_free_symbolic_node(mp, mp->loop_ptr->list); + if (mp->loop_ptr->point) { + mp_toss_knot_list(mp, mp->loop_ptr->point); + } + } else if (p == NULL) { + mp_node q = mp->loop_ptr->list; + while (q != NULL) { + p = (mp_node) mp_get_sym_sym(q); + if (p != NULL) { + if (mp_link(p) == MP_VOID) { + /* it's an |expr| parameter */ + mp_recycle_value(mp, p); + mp_free_value_node(mp, p); + } else { + /* it's a |suffix| or |text| parameter */ + mp_flush_token_list(mp, p); + } + } + p = q; + q = mp_link(q); + mp_free_symbolic_node(mp, p); + } + } else if (p > MP_PROGRESSION_FLAG) { + mp_delete_edge_ref(mp, p); + } + { + mp_loop_data *tmp = mp->loop_ptr; + mp->loop_ptr = tmp->link; + mp_flush_token_list(mp, tmp->info); + free_number(tmp->value); + free_number(tmp->step_size); + free_number(tmp->final_value); + mp_memory_free(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_new_symbolic_node(mp); +s->list_start = s->list; +q = s->list; +do { + mp_get_x_next(mp); + if (m != mp_start_for_code) { + mp_scan_suffix(mp); + } else { + if (cur_cmd >= mp_colon_command && cur_cmd <= mp_comma_command) { + goto CONTINUE; + } + mp_scan_expression(mp); + if (cur_cmd == mp_step_command && q == s->list) { + @<Prepare for step-until construction and |break|@> + } + mp_set_cur_exp_node(mp, mp_stash_cur_exp(mp)); + } + mp_link(q) = mp_new_symbolic_node(mp); + q = mp_link(q); + mp_set_sym_sym(q, mp->cur_exp.data.node); + if (m == mp_start_for_code) { + mp_name_type(q) = mp_expr_operation; + } else if (m == mp_start_forsuffixes_code) { + mp_name_type(q) = mp_suffix_operation; + } + mp->cur_exp.type = mp_vacuous_type; + CONTINUE: + ; /* needed */ +} while (cur_cmd == mp_comma_command); + +@ @<Prepare for step-until construction and |break|@>= +{ + if (mp->cur_exp.type != mp_known_type) { + mp_bad_for (mp, "initial value"); + } + number_clone(s->value, cur_exp_value_number); + number_clone(s->old_value, cur_exp_value_number); + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type != mp_known_type) { + mp_bad_for (mp, "step size"); + } + number_clone(s->step_size, cur_exp_value_number); + if (cur_cmd != mp_until_command) { + mp_back_error( + mp, + "Missing 'until' has been inserted", + "I assume you meant to say 'until' after 'step'. So I'll look for the final value\n" + "and colon next." + ); + @.Missing `until'@> + } + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type != mp_known_type) { + mp_bad_for (mp, "final value"); + } + number_clone(s->final_value, cur_exp_value_number); + s->type = MP_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); +if (mp->cur_exp.type == mp_path_type) { + number_clone(s->value, zero_t); + number_clone(s->old_value, zero_t); + number_clone(s->step_size, unity_t); + /* */ + { + mp_knot p = cur_exp_knot; + // int l = mp_left_type(p) == mp_endpoint_knot ? -1 : 0; + int l = 0; + while (1) { + mp_knot n = mp_next_knot(p); + if (n == cur_exp_knot) { + /* So we actually start at the end because we next first. */ + s->point = p; + set_number_from_int(s->final_value, l); + break; + } else { + p = n; + ++l; + } + } + } + /* */ + s->type = MP_PROGRESSION_FLAG; + s->list = mp_new_symbolic_node(mp); + s->list_start = s->list; + q = s->list; +} else { + @<Make sure the current expression is a known picture@> + s->type = mp->cur_exp.data.node; + mp->cur_exp.type = mp_vacuous_type; + q = mp_link(mp_edge_list(mp->cur_exp.data.node)); + if (q != NULL && mp_is_start_or_stop (q) && 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_number(new_expr.data.n); + new_expr.data.node = (mp_node) mp_get_edge_header_node(mp); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Improper iteration spec has been replaced by nullpicture", + "When you say 'for x in p', p must be a known picture." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + mp_init_edges(mp, (mp_edge_header_node) 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 +\quote {extension}; and a \quote {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 |cur_name|. + +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| regardless of whether $|more_name|(c_n)$ returned +|true| or |false|. + +@<Glob...@>= +char *cur_name; /* name of file just scanned */ + +@ It is easier to maintain reference counts if we assign initial values. + +@<Set init...@>= +mp->cur_name = mp_strdup(""); + +@ @<Dealloc variables@>= +mp_memory_free(mp->cur_name); + +@ 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...@>= +int quoted_filename; /* whether the filename is wrapped in " markers */ + +@ Here are the routines for file name scanning. + +@<Declarations@>= +static void mp_begin_name (MP mp); +static int mp_more_name (MP mp, unsigned char c); +static void mp_end_name (MP mp); + +@ @c +void mp_begin_name (MP mp) +{ + mp_memory_free(mp->cur_name); + mp->cur_name = NULL; + mp->quoted_filename = 0; +} + +int mp_more_name (MP mp, unsigned char c) +{ + if (c == '"') { + mp->quoted_filename = ! mp->quoted_filename; + } else if ((c == ' ' || c == '\t') && (mp->quoted_filename == 0)) { + return 0; + } else { + mp_str_room(mp, 1); + mp_append_char(mp, c); + } + return 1; +} + +void mp_end_name (MP mp) +{ + mp->cur_name = mp_memory_allocate((size_t) (mp->cur_length + 1) * sizeof(char)); + (void) memcpy(mp->cur_name, (char *) (mp->cur_string), mp->cur_length); + mp->cur_name[mp->cur_length] = 0; + mp_reset_cur_string(mp); +} + +void mp_pack_file_name (MP mp, const char *n) +{ + mp_memory_free(mp->name_of_file); + mp->name_of_file = mp_strdup(n); +} + +@ @<Internal library declarations@>= +void mp_pack_file_name (MP mp, const char *n); + +@ 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@> + +@ @c +static mp_string mp_make_name_string (MP mp) +{ + int name_length = (int) strlen(mp->name_of_file); + mp_str_room(mp, name_length); + for (int k = 0; k < name_length; k++) { + mp_append_char(mp, (unsigned char) mp->name_of_file[k]); + } + return mp_make_string(mp); +} + +@ Now let's consider the \quote {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 \quote {quoted} +somehow. @^system dependencies@> + +@c +static void mp_scan_file_name (MP mp) +{ + mp_begin_name(mp); + while (mp->buffer[loc] == ' ') { + ++loc; + } + while (1) { + if ((mp->buffer[loc] == ';') || (mp->buffer[loc] == '%')) { + break; + } else if (! mp_more_name(mp, mp->buffer[loc])) { + break; + } else { + ++loc; + } + } + mp_end_name(mp); +} + +static void mp_ptr_scan_file (MP mp, char *s) +{ + char *p = s; + char *q = p + strlen(s); + mp_begin_name(mp); + while (p < q) { + if (! mp_more_name(mp, (unsigned char) (*p))) { + break; + } else { + p++; + } + } + mp_end_name(mp); +} + +@ The option variable |job_name| has no real meaning and is dealt with by the caller, but +it is available in a variable in \MP. + +@ @<Option variables@>= +char *job_name; + +@ Initially |job_name = NULL| and when it is not set the initializer will quit. Setting +it happens elsewhere. + +@ @<Dealloc variables@>= +mp_memory_free(mp->job_name); + +@ Cannot do this earlier because at the |<Allocate or ...>|, the string pool is +not yet initialized. + +@<Fix up |job_name|@>= +if (mp->job_name != NULL) { + if (internal_string(mp_job_name_internal) != 0) { + delete_str_ref(internal_string(mp_job_name_internal)); + } + set_internal_string(mp_job_name_internal, mp_rts(mp, mp->job_name)); +} + +@ 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) +{ + @<Put the desired file name in |cur_name|@> + mp_begin_file_reading(mp); /* set up |cur_file| and new level of input */ + mp_pack_file_name(mp, mp->cur_name); + if (mp_open_in(mp, &cur_file, mp_filetype_program)) { + char *fname = NULL; + name = mp_make_name_string(mp); + fname = mp_strdup(mp->name_of_file); + if (mp->interaction < mp_silent_mode) { + /* This needs a cleanup! */ + if ((mp->term_offset > 0) || (mp->file_offset > 0)) { + mp_print_chr(mp, ' '); + } + mp_print_chr(mp, '('); + ++mp->open_parens; + mp_print_str(mp, fname); + } + mp_memory_free(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@> + } else { + mp_fatal_error(mp, "invalid input file"); + mp_end_file_reading(mp); + } +} + +@<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); +mp_memory_free(mp->cur_name); +mp->cur_name = NULL; + +@ 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; +mp_input_ln(mp, cur_file); +mp_firm_up_the_line(mp); +mp->buffer[limit] = '%'; +mp->first = (size_t) (limit + 1); +loc = start; + +@ @<Put the desired file name in |cur_name|@>= +while (token_state && (nloc == NULL)) { + mp_end_token_list(mp); +} +if (token_state) { + mp_error( + mp, + "File names can't appear within macros", + "Sorry ... I've converted what follows to tokens, possibly garbaging the name you\n" + "gave. Please delete the tokens and insert the name again." + ); + @.File names can't...@> +} +if (file_state) { + mp_scan_file_name(mp); +} else { + mp_memory_free(mp->cur_name); + mp->cur_name = mp_strdup(""); +} + +@ 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@> + + +@ @<Glob...@>= +int 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 */ +int read_files; /* number of valid entries in the above arrays */ +int 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 */ +int write_files; /* number of valid entries in the above arrays */ + +@ @<Allocate or initialize ...@>= +mp->max_read_files = 8; +mp->rd_file = mp_memory_allocate((size_t) (mp->max_read_files + 1) * sizeof(void *)); +mp->rd_fname = mp_memory_allocate((size_t) (mp->max_read_files + 1) * sizeof(char *)); +mp->max_write_files = 8; +mp->wr_file = mp_memory_allocate((size_t) (mp->max_write_files + 1) * sizeof(void *)); +mp->wr_fname = mp_memory_allocate((size_t) (mp->max_write_files + 1) * sizeof(char *)); + +memset(mp->rd_fname, 0, sizeof(char *) * (mp->max_read_files + 1)); +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 int mp_start_read_input (MP mp, char *s, int n) +{ + mp_ptr_scan_file(mp, s); + mp_pack_file_name(mp, mp->cur_name); + mp_begin_file_reading(mp); + if (! mp_open_in(mp, &mp->rd_file[n], mp_filetype_text + n)) { + mp_end_file_reading(mp); + return 0; + } else if (! mp_input_ln(mp, mp->rd_file[n])) { + (mp->close_file)(mp, mp->rd_file[n]); + mp_end_file_reading(mp); + return 0; + } else { + mp->rd_fname[n] = mp_strdup(s); + return 1; + } +} + +@ Open |wr_file[n]| using file name~|s| and update |wr_fname[n]|. + +@<Declarations@>= +static void mp_open_write_file (MP mp, char *s, int n); + +@ @c +void mp_open_write_file (MP mp, char *s, int n) +{ + mp_ptr_scan_file(mp, s); + mp_pack_file_name(mp, mp->cur_name); + if (mp_open_out(mp, &mp->wr_file[n], mp_filetype_text + n)) { + mp->wr_fname[n] = mp_strdup(s); + } else { + mp_fatal_error(mp, "invalid write file"); + } +} + +@* 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 \quote {LL(1),} more or less; backup +mechanisms have been added in order to provide reasonable error recovery. + +@d cur_exp_value_boolean number_to_int(mp->cur_exp.data.n) +@d cur_exp_value_number mp->cur_exp.data.n +@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 + +@<Declarations@>= +static void mp_set_cur_exp_knot (MP mp, mp_knot n); +static void mp_set_cur_exp_node (MP mp, mp_node n); +static void mp_set_cur_exp_value_boolean (MP mp, int b); +static void mp_set_cur_exp_value_scaled (MP mp, int s); +static void mp_set_cur_exp_value_number (MP mp, mp_number *n); +static void mp_set_cur_exp_str (MP mp, mp_string s); + +@ @c +void mp_set_cur_exp_node (MP mp, mp_node n) +{ + if (cur_exp_str) { + delete_str_ref(cur_exp_str); + } + cur_exp_node = n; + cur_exp_str = NULL; + cur_exp_knot = NULL; + set_number_to_zero(mp->cur_exp.data.n); +} + +void mp_set_cur_exp_knot (MP mp, mp_knot n) +{ + if (cur_exp_str) { + delete_str_ref(cur_exp_str); + } + cur_exp_knot = n; + cur_exp_node = NULL; + cur_exp_str = NULL; + set_number_to_zero(mp->cur_exp.data.n); +} + +void mp_set_cur_exp_value_boolean (MP mp, int b) +{ + if (cur_exp_str) { + delete_str_ref(cur_exp_str); + } + set_number_from_boolean(mp->cur_exp.data.n, b); + cur_exp_node = NULL; + cur_exp_str = NULL; + cur_exp_knot = NULL; +} + +void mp_set_cur_exp_value_scaled (MP mp, int s) +{ + if (cur_exp_str) { + delete_str_ref(cur_exp_str); + } + set_number_from_scaled(mp->cur_exp.data.n, s); + cur_exp_node = NULL; + cur_exp_str = NULL; + cur_exp_knot = NULL; +} + +void mp_set_cur_exp_value_number (MP mp, mp_number *n) +{ + if (cur_exp_str) { + delete_str_ref(cur_exp_str); + } + number_clone(mp->cur_exp.data.n, *n); + cur_exp_node = NULL; + cur_exp_str = NULL; + cur_exp_knot = NULL; +} + +void mp_set_cur_exp_str (MP mp, mp_string s) +{ + if (cur_exp_str) { + delete_str_ref(cur_exp_str); + } + cur_exp_str = s; + add_str_ref(cur_exp_str); + cur_exp_node = NULL; + cur_exp_knot = NULL; + set_number_to_zero(mp->cur_exp.data.n); +} + +@ @<Glob...@>= +mp_value cur_exp; /* the value of the expression just found */ + +@ @<Set init...@>= +memset(&mp->cur_exp.data, 0, sizeof(mp_value)); +new_number(mp->cur_exp.data.n); + +@ @<Free table ...@>= +free_number(mp->cur_exp.data.n); + +@ 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. + +@<Declarations@>= +static mp_node mp_stash_cur_exp (MP mp); + +@ @c +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 mp_unknown_boolean_type: + case mp_unknown_string_type: + case mp_unknown_pen_type: + case mp_unknown_nep_type: + case mp_unknown_path_type: + case mp_unknown_picture_type: + case mp_transform_type: + case mp_color_type: + case mp_pair_type: + case mp_dependent_type: + case mp_proto_dependent_type: + case mp_independent_type: + case mp_cmykcolor_type: + p = cur_exp_node; + break; + default: /* |case mp_path_type: case mp_pen_type: case mp_string_type:| */ + p = mp_new_value_node(mp); + mp_name_type(p) = mp_capsule_operation; + mp_type(p) = mp->cur_exp.type; + mp_set_value_number(p, cur_exp_value_number); /* this also resets the rest to 0/NULL */ + if (cur_exp_str) { + mp_set_value_str(p, cur_exp_str); + } else if (cur_exp_knot) { + mp_set_value_knot(p, cur_exp_knot); + } else if (cur_exp_node) { + mp_set_value_node(p, cur_exp_node); + } + break; + } + mp->cur_exp.type = mp_vacuous_type; + 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 \quote {alive} and those in which they are \quote {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. + +@ @c +void mp_unstash_cur_exp (MP mp, mp_node p) +{ + mp->cur_exp.type = mp_type(p); + switch (mp->cur_exp.type) { + case mp_unknown_boolean_type: + case mp_unknown_string_type: + case mp_unknown_pen_type: + case mp_unknown_nep_type: + case mp_unknown_path_type: + case mp_unknown_picture_type: + case mp_transform_type: + case mp_color_type: + case mp_pair_type: + case mp_dependent_type: + case mp_proto_dependent_type: + case mp_independent_type: + case mp_cmykcolor_type: + mp_set_cur_exp_node(mp, p); + break; + case mp_token_list_type: /* this is how symbols are stashed */ + mp_set_cur_exp_node(mp, mp_get_value_node(p)); + mp_free_value_node(mp, p); + break; + case mp_path_type: + case mp_pen_type: + case mp_nep_type: + mp_set_cur_exp_knot(mp, mp_get_value_knot(p)); + mp_free_value_node(mp, p); + break; + case mp_string_type: + mp_set_cur_exp_str(mp, mp_get_value_str(p)); + mp_free_value_node(mp, p); + break; + case mp_picture_type: + mp_set_cur_exp_node(mp, mp_get_value_node(p)); + mp_free_value_node(mp, p); + break; + case mp_boolean_type: + case mp_known_type: + mp_set_cur_exp_value_number(mp, &(mp_get_value_number(p))); + mp_free_value_node(mp, p); + break; + default: + mp_set_cur_exp_value_number(mp, &(mp_get_value_number(p))); + if (mp_get_value_knot(p)) { + mp_set_cur_exp_knot(mp, mp_get_value_knot(p)); + } else if (mp_get_value_node(p)) { + mp_set_cur_exp_node(mp, mp_get_value_node(p)); + } else if (mp_get_value_str(p)) { + mp_set_cur_exp_str(mp, mp_get_value_str(p)); + } + mp_free_value_node(mp, p); + 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@>= +static void mp_print_dp (MP mp, int t, mp_value_node p, int verbosity); +static void mp_unstash_cur_exp (MP mp, mp_node p); +static void mp_print_exp (MP mp, mp_node p, int verbosity); +static void mp_print_big_node (MP mp, mp_node p, int verbosity); + +@ @c +void mp_print_exp (MP mp, mp_node p, int verbosity) +{ + int restore_cur_exp; /* should |cur_exp| be restored? */ + mp_variable_type t; /* the type of the expression */ + mp_number vv; /* the value of the expression */ + mp_node v = NULL; + new_number(vv); + if (p != NULL) { + restore_cur_exp = 0; + } else { + p = mp_stash_cur_exp(mp); + restore_cur_exp = 1; + } + t = mp_type(p); + if (t < mp_dependent_type) { + /* no dep list, could be a capsule */ + if (t != mp_vacuous_type && t != mp_known_type && mp_get_value_node(p) != NULL) { + v = mp_get_value_node(p); + } else { + number_clone(vv, mp_get_value_number(p)); + } + } else if (t < mp_independent_type) { + v = (mp_node) mp_get_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); + } + free_number(vv); +} + +void mp_print_big_node (MP mp, mp_node v, int verbosity) +{ + switch (mp_type(v)) { + case mp_known_type: + print_number(mp_get_value_number(v)); + break; + case mp_independent_type: + mp_print_variable_name(mp, v); + break; + default: + mp_print_dp(mp, mp_type(v), (mp_value_node) mp_get_dep_list((mp_value_node) v), verbosity); + break; + } +} + +@ @<Print an abbreviated value of |v| or |vv| with format depending on |t|@>= +switch (t) { + case mp_vacuous_type: + mp_print_str(mp, "vacuous"); + break; + case mp_boolean_type: + mp_print_str(mp, number_to_boolean(vv) == mp_true_operation ? "true": "false"); + break; + case mp_unknown_boolean_type: + case mp_unknown_string_type: + case mp_unknown_pen_type: + case mp_unknown_nep_type: + case mp_unknown_path_type: + case mp_unknown_picture_type: + case mp_numeric_type: + { + @<Display a variable that's been declared but not defined@> + } + break; + case mp_string_type: + mp_print_chr(mp, '"'); + mp_print_mp_str(mp, mp_get_value_str(p)); + mp_print_chr(mp, '"'); + break; + case mp_pen_type: + case mp_nep_type: + case mp_path_type: + case mp_picture_type: + { + @<Display a complex type@> + } + break; + case mp_transform_type: + if (number_zero(vv) && v == NULL) { + mp_print_type(mp, t); + } else { + @<Display a transform node@> + } + break; + case mp_color_type: + if (number_zero(vv) && v == NULL) { + mp_print_type(mp, t); + } else { + @<Display a color node@> + } + break; + case mp_pair_type: + if (number_zero(vv) && v == NULL) { + mp_print_type(mp, t); + } else { + @<Display a pair node@> + } + break; + case mp_cmykcolor_type: + if (number_zero(vv) && v == NULL) { + mp_print_type(mp, t); + } else { + @<Display a cmykcolor node@> + } + break; + case mp_known_type: + print_number(vv); + break; + case mp_dependent_type: + case mp_proto_dependent_type: + mp_print_dp(mp, t, (mp_value_node) v, verbosity); + break; + case mp_independent_type: + mp_print_variable_name(mp, p); + break; + default: + mp_confusion(mp, "expression"); + break; + @:this can't happen exp}{\quad exp@> +} + +@ In these cases, |v| starts as the big node. + +@<Display a pair node@>= +mp_print_chr(mp, '('); +mp_print_big_node(mp, mp_x_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_y_part(v), verbosity); +mp_print_chr(mp, ')'); + +@ @<Display a transform node@>= +mp_print_chr(mp, '('); +mp_print_big_node(mp, mp_tx_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_ty_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_xx_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_xy_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_yx_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_yy_part(v), verbosity); +mp_print_chr(mp, ')'); + +@ @<Display a color node@>= +mp_print_chr(mp, '('); +mp_print_big_node(mp, mp_red_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_green_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_blue_part(v), verbosity); +mp_print_chr(mp, ')'); + +@ @<Display a cmykcolor node@>= +mp_print_chr(mp, '('); +mp_print_big_node(mp, mp_cyan_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_magenta_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_yellow_part(v), verbosity); +mp_print_chr(mp, ','); +mp_print_big_node(mp, mp_black_part(v), verbosity); +mp_print_chr(mp, ')'); + +@ 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 == mp_term_and_log_selector) + if (number_nonpositive(internal_value(mp_tracing_online_internal))) { + mp->selector = mp_term_only_selector; + mp_print_type(mp, t); + mp_print_str(mp, " (see the transcript file)"); + mp->selector = mp_term_and_log_selector; + }; + switch (t) { + case mp_pen_type: + case mp_nep_type: + mp_print_pen(mp, mp_get_value_knot(p), "", 0); + break; + case mp_path_type: + mp_print_path(mp, mp_get_value_knot(p), "", 0); + break; + case mp_picture_type: + mp_print_edges(mp, v, "", 0); + break; + default: + break; + } +} + +@ @c +static void mp_print_dp (MP mp, int t, mp_value_node p, int verbosity) +{ + mp_value_node q = (mp_value_node) mp_link(p); /* the node following |p| */ + if ((mp_get_dep_info(q) == NULL) || (verbosity > 0)) { + mp_print_dependency(mp, p, t); + } else { + mp_print_str(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_chr(mp, ' '); + while ((mp_name_type(v) == mp_capsule_operation) && (v != p)) { + v = mp_get_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 |disp_err| just before |mp_error|. + +@<Declarations@>= +static void mp_disp_err (MP mp, mp_node p); + +@ @c +void mp_disp_err (MP mp, mp_node p) +{ + if (mp->interaction >= mp_error_stop_mode) { + wake_up_terminal(); + } + /* mp_print_nl(mp, ">> "); */ + mp_print_nl(mp, "<error> "); + @.>>@> + mp_print_exp(mp, p, 1); +} + +@ 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 +void mp_flush_cur_exp (MP mp, mp_value v) +{ + if (is_number(mp->cur_exp.data.n)) { + free_number(mp->cur_exp.data.n); + } + switch (mp->cur_exp.type) { + case mp_unknown_boolean_type: + case mp_unknown_string_type: + case mp_unknown_pen_type: + case mp_unknown_nep_type: + case mp_unknown_path_type: + case mp_unknown_picture_type: + case mp_transform_type: + case mp_color_type: + case mp_pair_type: + case mp_dependent_type: + case mp_proto_dependent_type: + case mp_independent_type: + case mp_cmykcolor_type: + mp_recycle_value(mp, cur_exp_node); + mp_free_value_node(mp, cur_exp_node); + break; + case mp_string_type: + delete_str_ref(cur_exp_str); + break; + case mp_pen_type: + case mp_nep_type: + case mp_path_type: + mp_toss_knot_list(mp, cur_exp_knot); + break; + case mp_picture_type: + mp_delete_edge_ref(mp, cur_exp_node); + break; + default: + break; + } + mp->cur_exp = v; + mp->cur_exp.type = mp_known_type; +} + +@ 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) +{ + if (p != NULL && p != MP_VOID) { + mp_variable_type t = mp_type(p); + switch (t) { + case mp_vacuous_type: + case mp_boolean_type: + case mp_known_type: + case mp_numeric_type: + break; + case mp_unknown_boolean_type: + case mp_unknown_string_type: + case mp_unknown_pen_type: + case mp_unknown_nep_type: + case mp_unknown_path_type: + case mp_unknown_picture_type: + mp_ring_delete (mp, p); + break; + case mp_string_type: + delete_str_ref(mp_get_value_str(p)); + break; + case mp_path_type: + case mp_pen_type: + case mp_nep_type: + mp_toss_knot_list(mp, mp_get_value_knot(p)); + break; + case mp_picture_type: + mp_delete_edge_ref(mp, mp_get_value_node(p)); + break; + case mp_cmykcolor_type: + if (mp_get_value_node(p) != NULL) { + mp_recycle_value(mp, mp_cyan_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_magenta_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_yellow_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_black_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_cyan_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_magenta_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_black_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_yellow_part(mp_get_value_node(p))); + mp_free_node(mp, mp_get_value_node(p), sizeof(mp_color_node_data)); + } + break; + case mp_pair_type: + if (mp_get_value_node(p) != NULL) { + mp_recycle_value(mp, mp_x_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_y_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_x_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_y_part(mp_get_value_node(p))); + mp_free_pair_node(mp, mp_get_value_node(p)); + } + break; + case mp_color_type: + if (mp_get_value_node(p) != NULL) { + mp_recycle_value(mp, mp_red_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_green_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_blue_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_red_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_green_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_blue_part(mp_get_value_node(p))); + mp_free_node(mp, mp_get_value_node(p), sizeof(mp_color_node_data)); + } + break; + case mp_transform_type: + if (mp_get_value_node(p) != NULL) { + mp_recycle_value(mp, mp_tx_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_ty_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_xx_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_xy_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_yx_part(mp_get_value_node(p))); + mp_recycle_value(mp, mp_yy_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_tx_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_ty_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_xx_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_xy_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_yx_part(mp_get_value_node(p))); + mp_free_value_node(mp, mp_yy_part(mp_get_value_node(p))); + mp_free_node(mp, mp_get_value_node(p), sizeof(mp_transform_node_data)); + } + break; + case mp_dependent_type: + case mp_proto_dependent_type: + /* Recycle a dependency list */ + { + mp_value_node qq = (mp_value_node) mp_get_dep_list((mp_value_node) p); + while (mp_get_dep_info(qq) != NULL) { + qq = (mp_value_node) mp_link(qq); + } + mp_set_link(mp_get_prev_dep((mp_value_node) p), mp_link(qq)); + mp_set_prev_dep(mp_link(qq), mp_get_prev_dep((mp_value_node) p)); + mp_set_link(qq, NULL); + mp_flush_node_list(mp, (mp_node) mp_get_dep_list((mp_value_node) p)); + } + break; + case mp_independent_type: + mp_recycle_independent_value(mp, p); + break; + case mp_token_list_type: + case mp_structured_type: + mp_confusion(mp, "recycle"); + break; + case mp_unsuffixed_macro_type: + case mp_suffixed_macro_type: + mp_delete_mac_ref(mp, mp_get_value_node(p)); + break; + default: + break; + } + mp_type(p) = mp_undefined_type; + } +} + +@ 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.n->data. 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. + +@<Declarations@>= +static void mp_recycle_independent_value (MP mp, mp_node p); + +@ @c +static void mp_recycle_independent_value (MP mp, mp_node p) +{ + mp_value_node q, r, s; + mp_node pp; /* link manipulation register */ + mp_number v ; /* a value */ + mp_number test; /* a temporary value */ + mp_variable_type t = mp_type(p); + new_number(test); + new_number(v); + if (t < mp_dependent_type) { + number_clone(v, mp_get_value_number(p)); + } + set_number_to_zero(mp->max_c[mp_dependent_type]); + set_number_to_zero(mp->max_c[mp_proto_dependent_type]); + mp->max_link[mp_dependent_type] = NULL; + mp->max_link[mp_proto_dependent_type] = NULL; + q = (mp_value_node) mp_link(mp->dep_head); + while (q != mp->dep_head) { + s = (mp_value_node) mp->temp_head; + mp_set_link(s, mp_get_dep_list(q)); + while (1) { + r = (mp_value_node) mp_link(s); + if (mp_get_dep_info(r) == NULL) { + break; + } else if (mp_get_dep_info(r) != p) { + s = r; + } else { + t = mp_type(q); + if (mp_link(s) == mp_get_dep_list(q)) { + /* reset the |dep_list| */ + mp_set_dep_list(q, mp_link(r)); + } + mp_set_link(s, mp_link(r)); + mp_set_dep_info(r, (mp_node) q); + number_abs_clone(test, mp_get_dep_value(r)); + if (number_greater(test, mp->max_c[t])) { + /* Record a new maximum coefficient of type |t| */ + if (number_positive(mp->max_c[t])) { + mp_set_link(mp->max_ptr[t], (mp_node) mp->max_link[t]); + mp->max_link[t] = mp->max_ptr[t]; + } + number_clone(mp->max_c[t], test); + mp->max_ptr[t] = r; + } else { + mp_set_link(r, mp->max_link[t]); + mp->max_link[t] = r; + } + } + } + q = (mp_value_node) mp_link(r); + } + if (number_positive(mp->max_c[mp_dependent_type]) || number_positive(mp->max_c[mp_proto_dependent_type])) { + /* + Choose a dependent variable to take the place of the disappearing + independent variable, and change all remaining dependencies + accordingly + */ + mp_number test, ret; /* temporary use */ + new_number(ret); + new_number_clone(test, mp->max_c[mp_dependent_type]); + number_divide_int(test, 4096); + if (number_greaterequal(test, mp->max_c[mp_proto_dependent_type])) { + t = mp_dependent_type; + } else { + t = mp_proto_dependent_type; + } + /* + Let |s=max_ptr[t]|. At this point we have + $|value|(s)=\pm|max_c|[t]$, and |mp_get_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 dependency list |s| to substitute for the independent + variable~|p| + */ + s = mp->max_ptr[t]; + pp = (mp_node) mp_get_dep_info(s); + number_clone(v, mp_get_dep_value(s)); + if (t == mp_dependent_type) { + mp_set_dep_value(s, fraction_one_t); + } else { + mp_set_dep_value(s, unity_t); + } + number_negate(mp_get_dep_value(s)); + r = (mp_value_node) mp_get_dep_list((mp_value_node) pp); + mp_set_link(s, r); + while (mp_get_dep_info(r) != NULL) { + r = (mp_value_node) mp_link(r); + } + q = (mp_value_node) mp_link(r); + mp_set_link(r, NULL); + mp_set_prev_dep(q, mp_get_prev_dep((mp_value_node) pp)); + mp_set_link(mp_get_prev_dep((mp_value_node) pp), (mp_node) q); + mp_new_indep(mp, pp); + if (cur_exp_node == pp && mp->cur_exp.type == t) { + mp->cur_exp.type = mp_independent_type; + } + if (number_positive(internal_value(mp_tracing_equations_internal)) && mp_interesting(mp, p)) { + mp_begin_diagnostic(mp); + mp_show_transformed_dependency(mp, &v, t, p); + mp_print_dependency(mp, s, t); + mp_end_diagnostic(mp, 0); + } + /* complement |t| */ + t = mp_dependent_type + mp_proto_dependent_type - t; + if (number_positive(mp->max_c[t])) { + /* we need to pick up an unchosen dependency */ + mp_set_link(mp->max_ptr[t], (mp_node) mp->max_link[t]); + mp->max_link[t] = mp->max_ptr[t]; + } + /* + Finally, there are dependent and proto-dependent variables whose + dependency lists must be brought up to date. + */ + if (t != mp_dependent_type) { + /* Substitute new dependencies in place of |p| */ + for (t = mp_dependent_type; t <= mp_proto_dependent_type; t=t+1) { + r = mp->max_link[t]; + while (r != NULL) { + q = (mp_value_node) mp_get_dep_info(r); + number_negated_clone(test, v); + make_fraction(ret, mp_get_dep_value(r), test); + mp_set_dep_list(q, mp_p_plus_fq(mp, (mp_value_node) mp_get_dep_list(q), &ret, s, t, mp_dependent_type)); + if (mp_get_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); + } + } + } else { + /* Substitute new proto-dependencies in place of |p| */ + for (t = mp_dependent_type; t <= mp_proto_dependent_type; t++) { + r = mp->max_link[t]; + while (r != NULL) { + q = (mp_value_node) mp_get_dep_info(r); + if (t == mp_dependent_type) { + /* for safety's sake, we change |q| to |mp_proto_dependent| */ + if (cur_exp_node == (mp_node) q && mp->cur_exp.type == mp_dependent_type) { + mp->cur_exp.type = mp_proto_dependent_type; + } + mp_set_dep_list(q, mp_p_over_v(mp, (mp_value_node) mp_get_dep_list(q), &unity_t, mp_dependent_type, mp_proto_dependent_type)); + mp_type(q) = mp_proto_dependent_type; + fraction_to_round_scaled(mp_get_dep_value(r)); + } + number_negated_clone(test, v); + make_scaled(ret, mp_get_dep_value(r), test); + mp_set_dep_list(q, mp_p_plus_fq(mp, (mp_value_node) mp_get_dep_list(q), &ret, s, mp_proto_dependent_type, mp_proto_dependent_type)); + if (mp_get_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); + } + } + } + mp_flush_node_list(mp, (mp_node) s); + if (mp->fix_needed) { + mp_fix_dependencies(mp); + } + check_arith(); + free_number(ret); + } + free_number(v); + free_number(test); +} + +@ @<Declarations@>= +static void mp_show_transformed_dependency (MP mp, mp_number *v, mp_variable_type t, mp_node p); + +@ @c +static void mp_show_transformed_dependency (MP mp, mp_number *v, mp_variable_type t, mp_node p) +{ + mp_number vv; /* for temp use */ + mp_print_nl(mp, "### "); + if (number_positive(*v)) { + mp_print_chr(mp, '-'); + } + if (t == mp_dependent_type) { + new_number_clone(vv, mp->max_c[mp_dependent_type]); + fraction_to_round_scaled(vv); + } else { + new_number_clone(vv, mp->max_c[mp_proto_dependent_type]); + } + if (! number_equal(vv, unity_t)) { + print_number(vv); + } + mp_print_variable_name(mp, p); + while (mp_get_indep_scale(p) > 0) { + mp_print_str(mp, "*4"); + mp_set_indep_scale(p, mp_get_indep_scale(p)-2); + } + if (t == mp_dependent_type) { + mp_print_chr(mp, '='); + } else { + mp_print_str(mp, " = "); + } + free_number(vv); +} + +@ The code for independency removal makes use of three non-symbolic arrays. + +@<Glob...@>= +mp_number max_c[mp_proto_dependent_type + 1]; /* max coefficient magnitude */ +mp_value_node max_ptr[mp_proto_dependent_type + 1]; /* where |p| occurs with |max_c| */ +mp_value_node max_link[mp_proto_dependent_type + 1]; /* other occurrences of |p| */ + + +@ @<Initialize table ... @>= +for (int i = 0; i < mp_proto_dependent_type + 1; i++) { + new_number(mp->max_c[i]); +} + +@ @<Dealloc...@>= +for (int i = 0; i < mp_proto_dependent_type + 1; i++) { + free_number(mp->max_c[i]); +} + +@ 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 \quote {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. + +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. + +@<Declare the basic parsing subroutines@>= +static void check_for_mediation (MP mp); + +static void mp_primary_error(MP mp) +{ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + mp_disp_err(mp, NULL); + new_number(new_expr.data.n); + mp_back_error( + mp, + "Nonnumeric part has been replaced by 0", + "I've started to scan a pair (x,y), color (r,g,b), cmykcolor (c,m,y,k) or\n" + "transform (tx,ty,xx,xy,yx,yy) but ran into a non-numeric type. I'll recover\n" + "as good as possible." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); +} + +void mp_scan_primary (MP mp) +{ + mp_command_code my_var_flag = mp->var_flag; + mp->var_flag = 0; + RESTART: + check_arith(); + /* Supply diagnostic information, if requested */ + switch (cur_cmd) { + case mp_left_delimiter_command: + { + /* Scan a delimited primary */ + mp_sym l_delim = cur_sym; + mp_sym r_delim = equiv_sym(cur_sym); + mp_get_x_next(mp); + mp_scan_expression(mp); + if ((cur_cmd == mp_comma_command) && (mp->cur_exp.type >= mp_known_type)) { + /* Scan the rest of a delimited set of numerics. */ + mp_node q = mp_new_value_node(mp); + mp_node p1 = mp_stash_cur_exp(mp); + mp_node r; /* temporary node */ + mp_name_type(q) = mp_capsule_operation; + mp_get_x_next(mp); + mp_scan_expression(mp); + /* Make sure the second part of a pair or color has a numeric type */ + if (mp->cur_exp.type < mp_known_type) { + mp_primary_error(mp); + } + if (cur_cmd != mp_comma_command) { + /* Package the pair. */ + mp_init_pair_node(mp, q); + r = mp_get_value_node(q); + mp_stash_in(mp, mp_y_part(r)); + mp_unstash_cur_exp(mp, p1); + mp_stash_in(mp, mp_x_part(r)); + } else { + mp_node p2 = mp_stash_cur_exp(mp); + /* Scan the last of a triplet of numerics */ + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type < mp_known_type) { + mp_primary_error(mp); + } + if (cur_cmd != mp_comma_command) { + /* Package the rgb color. */ + mp_init_color_node(mp, q, mp_color_type); + r = mp_get_value_node(q); + mp_stash_in(mp, mp_blue_part(r)); + mp_unstash_cur_exp(mp, p1); + mp_stash_in(mp, mp_red_part(r)); + mp_unstash_cur_exp(mp, p2); + mp_stash_in(mp, mp_green_part(r)); + } else { + mp_node p3 = mp_stash_cur_exp(mp); + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type < mp_known_type) { + mp_primary_error(mp); + } + if (cur_cmd != mp_comma_command) { + /* Package the cmyk color. */ + mp_init_color_node(mp, q, mp_cmykcolor_type); + r = mp_get_value_node(q); + mp_stash_in(mp, mp_black_part(r)); + mp_unstash_cur_exp(mp, p1); + mp_stash_in(mp, mp_cyan_part(r)); + mp_unstash_cur_exp(mp, p2); + mp_stash_in(mp, mp_magenta_part(r)); + mp_unstash_cur_exp(mp, p3); + mp_stash_in(mp, mp_yellow_part(r)); + } else { + mp_node p4 = mp_stash_cur_exp(mp); + mp_node p5; + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type < mp_known_type) { + mp_primary_error(mp); + p5 = mp_stash_cur_exp(mp); + goto HERE; + } + if (cur_cmd != mp_comma_command) { + mp_primary_error(mp); + p5 = mp_stash_cur_exp(mp); + goto HERE; + } + p5 = mp_stash_cur_exp(mp); + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type < mp_known_type) { + mp_primary_error(mp); + } + HERE: + mp_init_transform_node(mp, q); + /* Package the transform: xx xy yx yy tx ty */ + r = mp_get_value_node(q); + mp_stash_in(mp, mp_ty_part(r)); + mp_unstash_cur_exp(mp, p5); + mp_stash_in(mp, mp_tx_part(r)); + mp_unstash_cur_exp(mp, p4); + mp_stash_in(mp, mp_yy_part(r)); + mp_unstash_cur_exp(mp, p3); + mp_stash_in(mp, mp_yx_part(r)); + mp_unstash_cur_exp(mp, p2); + mp_stash_in(mp, mp_xy_part(r)); + mp_unstash_cur_exp(mp, p1); + mp_stash_in(mp, mp_xx_part(r)); + } + } + } + mp_check_delimiter(mp, l_delim, r_delim); + mp->cur_exp.type = mp_type(q); + mp_set_cur_exp_node(mp, q); + } else { + mp_check_delimiter(mp, l_delim, r_delim); + } + } + break; + case mp_begin_group_command: + /* Scan a grouped primary. 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. + */ + { + int group_line = mp_true_line(mp); /* where a group began */ + if (number_positive(internal_value(mp_tracing_commands_internal))) { + mp_show_cmd_mod(mp, cur_cmd, cur_mod); + } + mp_save_boundary(mp); + do { + mp_do_statement(mp); /* ends with |cur_cmd>=semicolon| */ + } while (cur_cmd == mp_semicolon_command); + if (cur_cmd != mp_end_group_command) { + char msg[256]; + mp_snprintf(msg, 256, "A group begun on line %d never ended", (int) group_line); + mp_back_error( + mp, + msg, + "I saw a 'begingroup' back there that hasn't been matched by 'endgroup'. So I've\n" + "inserted 'endgroup' now." + ); + set_cur_cmd(mp_end_group_command); + } + mp_unsave(mp); + /* this might change |cur_type|, if independent variables are recycled */ + if (number_positive(internal_value(mp_tracing_commands_internal))) { + mp_show_cmd_mod(mp, cur_cmd, cur_mod); + } + } + break; + case mp_string_command: + /* Scan a string constant */ + mp->cur_exp.type = mp_string_type; + mp_set_cur_exp_str(mp, cur_mod_str); + break; + case mp_numeric_command: + { + /* + Scan a primary that starts with a numeric token. 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. + */ + mp_number num, denom; /* for primaries that are fractions, like `1/2' */ + mp_set_cur_exp_value_number(mp, &cur_mod_number); + mp->cur_exp.type = mp_known_type; + mp_get_x_next(mp); +//new_number(num); +//new_number(denom); + if (cur_cmd != mp_slash_command) { + new_number(num); + new_number(denom); + } else { + mp_get_x_next(mp); + if (cur_cmd != mp_numeric_command) { + mp_back_input(mp); + set_cur_cmd(mp_slash_command); + set_cur_mod(mp_over_operation); + set_cur_sym(mp->frozen_slash); +// goto DONOTHING; + goto DONE; + } else { + new_number_clone(num, cur_exp_value_number); + new_number_clone(denom, cur_mod_number); +//number_clone(num, cur_exp_value_number); +//number_clone(denom, cur_mod_number); + if (number_zero(denom)) { + mp_error(mp, "Division by zero", "I'll pretend that you meant to divide by 1."); + } else { + mp_number ret; + new_number(ret); + make_scaled(ret, num, denom); + mp_set_cur_exp_value_number(mp, &ret); + free_number(ret); + } + check_arith(); + mp_get_x_next(mp); + } + } + if (cur_cmd >= mp_min_primary_command && cur_cmd < mp_numeric_command) { + /* in particular, |cur_cmd<>plus_or_minus| */ + mp_number absnum, absdenom; + mp_node p = mp_stash_cur_exp(mp); + mp_scan_primary(mp); + new_number_abs(absnum, num); + new_number_abs(absdenom, denom); + if (number_greaterequal(absnum, absdenom) || (mp->cur_exp.type < mp_color_type)) { + mp_do_binary(mp, p, mp_times_operation); + } else { + mp_frac_mult(mp, &num, &denom); + mp_free_value_node(mp, p); + } + free_number(absnum); + free_number(absdenom); + } +// DONOTHING: + free_number(num); + free_number(denom); + goto DONE; + } + case mp_nullary_command: + /* Scan a nullary operation */ + mp_do_nullary(mp, (int) cur_mod); + break; + case mp_unary_command: + case mp_type_name_command: + case mp_cycle_command: + case mp_plus_or_minus_command: + { + /* Scan a unary operation */ + int c = (int) cur_mod; /* a primitive operation code */ + mp_get_x_next(mp); + mp_scan_primary(mp); + mp_do_unary(mp, c); + goto DONE; + } + case mp_of_binary_command: + { + /* Scan a binary operation with |of| between its operands */ + mp_node p; /* for list manipulation */ + int c = (int) cur_mod; /* a primitive operation code */ + mp_get_x_next(mp); + mp_scan_expression(mp); + if (cur_cmd != mp_of_command) { + char msg[256]; + mp_string sname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_cmd_mod(mp, mp_of_binary_command, c); + mp->selector = selector; + sname = mp_make_string(mp); + mp_snprintf(msg, 256, "Missing 'of' has been inserted for %s", mp_str(mp, sname)); + delete_str_ref(sname); + mp_back_error(mp, msg, "I've got the first argument; will look now for the other."); + } + p = mp_stash_cur_exp(mp); + mp_get_x_next(mp); + mp_scan_primary(mp); + mp_do_binary(mp, p, c); + goto DONE; + } + case mp_str_command: + { + /* Convert a suffix to a string */ + int selector = mp->selector; + mp_get_x_next(mp); + mp_scan_suffix(mp); + mp->selector = mp_new_string_selector; + /* Here the periods creep in, we could have a simple one. */ + mp_show_token_list(mp, cur_exp_node, NULL); + /* */ + mp_flush_token_list(mp, cur_exp_node); + mp_set_cur_exp_str(mp, mp_make_string(mp)); + mp->selector = selector; + mp->cur_exp.type = mp_string_type; + goto DONE; + } + case mp_void_command: + { + /* Convert a suffix to a boolean */ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_get_x_next(mp); + mp_scan_suffix(mp); + if (cur_exp_node == NULL) { + set_number_from_boolean(new_expr.data.n, mp_true_operation); + } else { + set_number_from_boolean(new_expr.data.n, mp_false_operation); + } + mp_flush_cur_exp(mp, new_expr); + cur_exp_node = NULL; /* !! do not replace with |mp_set_cur_exp_node(mp, )| !! */ + mp->cur_exp.type = mp_boolean_type; + goto DONE; + } + case mp_internal_command: + /* + Scan an internal numeric quantity. 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_operation|. (This + accords with the conventions of the save stack, as described + earlier.) + */ + { + int qq = cur_mod; + if (my_var_flag == mp_assignment_command) { + mp_get_x_next(mp); + if (cur_cmd == mp_assignment_command) { + mp_set_cur_exp_node(mp, mp_new_symbolic_node(mp)); + mp_set_sym_info(cur_exp_node, qq); + mp_name_type(cur_exp_node) = mp_internal_operation; + mp->cur_exp.type = mp_token_list_type; + goto DONE; + } + mp_back_input(mp); + } + if (internal_type(qq) == mp_string_type) { + mp_set_cur_exp_str(mp, internal_string(qq)); + } else { + mp_set_cur_exp_value_number(mp, &(internal_value(qq))); + // if (qq == mp_tracing_online_internal) { + // mp->run_internal(mp, 3, qq, number_to_int(internal_value(qq)), internal_name(qq)); + // } + } + mp->cur_exp.type = internal_type(qq); + } + break; + case mp_capsule_command: + mp_make_exp_copy(mp, cur_mod_node); + break; + case mp_tag_command: + @<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; + } + mp_get_x_next(mp); /* the routines |goto done| if they don't want this */ + DONE: + check_for_mediation(mp); +} + +@ 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. + +@<Declare the basic parsing subroutines@>= +static void check_for_mediation (MP mp) +{ + if (cur_cmd == mp_left_bracket_command && mp->cur_exp.type >= mp_known_type) { + /* Scan a mediation construction */ + mp_node p = mp_stash_cur_exp(mp); + mp_get_x_next(mp); + mp_scan_expression(mp); + if (cur_cmd != mp_comma_command) { + /* + Put the left bracket and the expression back to be rescanned. + 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. + */ + mp_back_input(mp); + /* that was the token following the current expression */ + mp_back_expr(mp); + set_cur_cmd(mp_left_bracket_command); + set_cur_mod_number(zero_t); + set_cur_sym(mp->frozen_left_bracket); + mp_unstash_cur_exp(mp, p); + } else { + mp_node q = mp_stash_cur_exp(mp); + mp_node r; + mp_get_x_next(mp); + mp_scan_expression(mp); + if (cur_cmd != mp_right_bracket_command) { + mp_back_error( + mp, + "Missing ']' has been inserted", + "I've scanned an expression of the form 'a[b,c', so a right bracket should have\n" + "come next. I shall pretend that one was there." + ); + } + r = mp_stash_cur_exp(mp); + mp_make_exp_copy(mp, q); + mp_do_binary(mp, r, mp_minus_operation); + mp_do_binary(mp, p, mp_times_operation); + mp_do_binary(mp, q, mp_plus_operation); + mp_get_x_next(mp); + } + } +} + +@ Errors at the beginning of expressions are flagged by |bad_exp|. + +@c +static void mp_bad_exp (MP mp, const char *s) +{ + char msg[256]; + int save_flag; + @:METAFONTbook}{\sl The {\logos METAFONT}book@> + { + mp_string cm; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_cmd_mod(mp, cur_cmd, cur_mod); + mp->selector = selector; + cm = mp_make_string(mp); + mp_snprintf(msg, 256, "%s expression can't begin with '%s'", s, mp_str(mp, cm)); + delete_str_ref(cm); + } + mp_back_input(mp); + set_cur_sym(NULL); + set_cur_cmd(mp_numeric_command); + set_cur_mod_number(zero_t); + mp_ins_error( + mp, + msg, + "I'm afraid I need some sort of value in order to continue, so I've tentatively\n" + "inserted '0'." + ); + save_flag = mp->var_flag; + mp->var_flag = 0; + mp_get_x_next(mp); + mp->var_flag = save_flag; +} + + +@ The |stash_in| subroutine puts the current (numeric) expression into a field +within a \quote {big node.} + +@c +static void mp_stash_in (MP mp, mp_node p) +{ + mp_type(p) = mp->cur_exp.type; + if (mp->cur_exp.type == mp_known_type) { + mp_set_value_number(p, cur_exp_value_number); + } else if (mp->cur_exp.type == mp_independent_type) { + /* + Stash an independent |cur_exp| into a big node. 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. + */ + mp_value_node q = mp_single_dependency(mp, cur_exp_node); + if (q == mp->dep_final) { + mp_type(p) = mp_known_type; + mp_set_value_number(p, zero_t); + mp_free_dep_node(mp, q); + } else { + mp_new_dep(mp, p, mp_dependent_type, q); + } + mp_recycle_value(mp, cur_exp_node); + mp_free_value_node(mp, cur_exp_node); + } else { + mp_set_dep_list((mp_value_node) p, mp_get_dep_list((mp_value_node) cur_exp_node)); + mp_set_prev_dep((mp_value_node) p, mp_get_prev_dep((mp_value_node) cur_exp_node)); + mp_set_link(mp_get_prev_dep((mp_value_node) p), p); + mp_free_dep_node(mp, (mp_value_node) cur_exp_node); + } + mp->cur_exp.type = mp_vacuous_type; +} + +@ 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 \quote {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. + +@ @<Scan a variable primary...@>= +{ + mp_node p = 0; /* for list manipulation */ + mp_node q = 0; /* for list manipulation */ + mp_node t = 0; + mp_node macro_ref = 0; /* reference count for a suffixed macro */ + int tt = mp_vacuous_type; /* approximation to the type of the variable-so-far */ + mp_node pre_head = mp_new_symbolic_node(mp); + mp_node tail = pre_head; + mp_node post_head = NULL; + while (1) { + t = mp_cur_tok(mp); + mp_link(tail) = t; + if (tt != mp_undefined_type) { + /* + Find the approximate type |tt| and corresponding~|q|. 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. + */ + mp_sym qq; + p = mp_link(pre_head); + qq = mp_get_sym_sym(p); + tt = mp_undefined_type; + // if (eq_type(qq) % mp_outer_tag_command == mp_tag_command) { + if (eq_type(qq) == mp_tag_command) { + 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_type) { + goto DONE2; + } + q = mp_link(mp_get_attribute_head(q)); /* the |mp_collective_subscript| attribute */ + if (mp_type(p) == mp_symbol_node_type) { + /* it's not a subscript */ + do { + q = mp_link(q); + } while (! (mp_get_hashloc(q) >= mp_get_sym_sym(p))); + if (mp_get_hashloc(q) > mp_get_sym_sym(p)) { + goto DONE2; + } + } + } + } + DONE2: + if (tt >= mp_unsuffixed_macro_type) { + /* Either begin an unsuffixed macro call or prepare for a suffixed one */ + mp_link(tail) = NULL; + if (tt > mp_unsuffixed_macro_type) { + /* |tt=mp_suffixed_macro| */ + post_head = mp_new_symbolic_node(mp); + tail = post_head; + mp_link(tail) = t; + tt = mp_undefined_type; + macro_ref = mp_get_value_node(q); + mp_add_mac_ref(macro_ref); + } else { + /* + Set up unsuffixed macro call and |goto restart|. The only + complication associated with macro calling is that the + prefix and \quote {at} parameters must be packaged in an + appropriate list of lists. + */ + p = mp_new_symbolic_node(mp); + mp_set_sym_sym(pre_head, mp_link(pre_head)); + mp_link(pre_head) = p; + mp_set_sym_sym(p, t); + mp_macro_call(mp, mp_get_value_node(q), pre_head, NULL); + mp_get_x_next(mp); + goto RESTART; + } + } + } + mp_get_x_next(mp); + tail = t; + if (cur_cmd == mp_left_bracket_command) { + /* Scan for a subscript; replace |cur_cmd| by |numeric_token| if found */ + mp_get_x_next(mp); + mp_scan_expression(mp); + if (cur_cmd != mp_right_bracket_command) { + /* + Put the left bracket and the expression back to be rescanned. + 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. + */ + mp_back_input(mp); /* that was the token following the current expression */ + mp_back_expr(mp); + set_cur_cmd(mp_left_bracket_command); + set_cur_mod_number(zero_t); + set_cur_sym(mp->frozen_left_bracket); + } else { + if (mp->cur_exp.type != mp_known_type) { + mp_bad_subscript(mp); + } + set_cur_cmd(mp_numeric_command); + set_cur_mod_number(cur_exp_value_number); + set_cur_sym(NULL); + } + } + if (cur_cmd > mp_max_suffix_token) { + break; + } else if (cur_cmd < mp_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| 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. + */ + if (post_head != NULL) { + /* + Set up suffixed macro call and |goto restart|. If the \quote {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. + */ + mp_back_input(mp); + p = mp_new_symbolic_node(mp); + q = mp_link(post_head); + mp_set_sym_sym(pre_head, mp_link(pre_head)); + mp_link(pre_head) = post_head; + mp_set_sym_sym(post_head, q); + mp_link(post_head) = p; + mp_set_sym_sym(p, mp_link(q)); + mp_link(q) = NULL; + mp_macro_call(mp, macro_ref, pre_head, NULL); + mp_decr_mac_ref(macro_ref); + mp_get_x_next(mp); + goto RESTART; + } + q = mp_link(pre_head); + mp_free_symbolic_node(mp, pre_head); + if (cur_cmd == my_var_flag) { + mp->cur_exp.type = mp_token_list_type; + mp_set_cur_exp_node(mp, q); + goto DONE; + } + p = mp_find_variable(mp, q); + if (p != NULL) { + mp_make_exp_copy(mp, p); + } else { + mp_value new_expr; + char *msg = mp_obliterated (mp, q); + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_back_error( + mp, + msg, + "While I was evaluating the suffix of this variable, something was redefined, and\n" + "it's no longer a variable! In order to get back on my feet, I've inserted '0'\n" + "instead." + ); + mp_memory_free(msg); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } + mp_flush_node_list(mp, q); + goto DONE; +} + +@ 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 = mp_stash_cur_exp(mp); /* capsule token */ + mp_link(p) = NULL; + mp_begin_token_list(mp, p, mp_backed_up_text); +} + +@ 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)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_error( + mp, + "Improper subscript has been replaced by zero", + "A bracketed subscript must have a known numeric value; unfortunately, what I\n" + "found was the value that appears just above this error message. So I'll try a\n" + "zero subscript." + ); + @.Improper subscript...@> + mp_flush_cur_exp(mp, new_expr); +} + +@ 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 creates an error message for when a variable unexpectedly +disappears. + +@c +static char *mp_obliterated (MP mp, mp_node q) +{ + char msg[256]; + mp_string sname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_show_token_list(mp, q, NULL); + sname = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "Variable %s has been obliterated", mp_str(mp, sname)); + @.Variable...obliterated@> + delete_str_ref(sname); + return mp_strdup(msg); +} + +@ 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) +{ + RESTART: + mp->cur_exp.type = mp_type(p); + switch (mp->cur_exp.type) { + case mp_vacuous_type: + case mp_boolean_type: + case mp_known_type: + mp_set_cur_exp_value_number(mp, &(mp_get_value_number(p))); + break; + case mp_unknown_boolean_type: + case mp_unknown_string_type: + case mp_unknown_pen_type: + case mp_unknown_nep_type: + case mp_unknown_path_type: + case mp_unknown_picture_type: + { + mp_node t = mp_new_ring_entry(mp, p); + mp_set_cur_exp_node(mp, t); + } + break; + case mp_string_type: + mp_set_cur_exp_str(mp, mp_get_value_str(p)); + break; + case mp_picture_type: + mp_set_cur_exp_node(mp, mp_get_value_node(p)); + mp_add_edge_ref(mp, cur_exp_node); + break; + case mp_pen_type: + case mp_nep_type: + mp_set_cur_exp_knot(mp, mp_copy_pen(mp, mp_get_value_knot(p))); + break; + case mp_path_type: + mp_set_cur_exp_knot(mp, mp_copy_path(mp, mp_get_value_knot(p))); + break; + case mp_transform_type: + case mp_color_type: + case mp_cmykcolor_type: + case mp_pair_type: + { + /* + Copy the big node |p|. 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|. + */ + mp_node t; + mp_value_node q; + if (mp_get_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, mp_color_type); + break; + case mp_cmykcolor_type: + mp_init_color_node(mp, p, mp_cmykcolor_type); + break; + case mp_transform_type: + mp_init_transform_node(mp, p); + break; + default: + break; + } + } + t = mp_new_value_node(mp); + mp_name_type(t) = mp_capsule_operation; + q = (mp_value_node) mp_get_value_node(p); + switch (mp->cur_exp.type) { + case mp_pair_type: + mp_init_pair_node(mp, t); + mp_install(mp, mp_y_part(mp_get_value_node(t)), mp_y_part(q)); + mp_install(mp, mp_x_part(mp_get_value_node(t)), mp_x_part(q)); + break; + case mp_color_type: + mp_init_color_node(mp, t, mp_color_type); + mp_install(mp, mp_blue_part(mp_get_value_node(t)), mp_blue_part(q)); + mp_install(mp, mp_green_part(mp_get_value_node(t)), mp_green_part(q)); + mp_install(mp, mp_red_part(mp_get_value_node(t)), mp_red_part(q)); + break; + case mp_cmykcolor_type: + mp_init_color_node(mp, t, mp_cmykcolor_type); + mp_install(mp, mp_black_part(mp_get_value_node(t)), mp_black_part(q)); + mp_install(mp, mp_yellow_part(mp_get_value_node(t)), mp_yellow_part(q)); + mp_install(mp, mp_magenta_part(mp_get_value_node(t)), mp_magenta_part(q)); + mp_install(mp, mp_cyan_part(mp_get_value_node(t)), mp_cyan_part(q)); + break; + case mp_transform_type: + mp_init_transform_node(mp, t); + mp_install(mp, mp_yy_part(mp_get_value_node(t)), mp_yy_part(q)); + mp_install(mp, mp_yx_part(mp_get_value_node(t)), mp_yx_part(q)); + mp_install(mp, mp_xy_part(mp_get_value_node(t)), mp_xy_part(q)); + mp_install(mp, mp_xx_part(mp_get_value_node(t)), mp_xx_part(q)); + mp_install(mp, mp_ty_part(mp_get_value_node(t)), mp_ty_part(q)); + mp_install(mp, mp_tx_part(mp_get_value_node(t)), mp_tx_part(q)); + break; + default: + break; + } + mp_set_cur_exp_node(mp, t); + } + break; + case mp_dependent_type: + case mp_proto_dependent_type: + mp_encapsulate (mp, mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) p))); + break; + case mp_numeric_type: + mp_new_indep(mp, p); + goto RESTART; + case mp_independent_type: + { + mp_value_node q = mp_single_dependency(mp, p); + if (q == mp->dep_final) { + mp->cur_exp.type = mp_known_type; + mp_set_cur_exp_value_number(mp, &zero_t); + mp_free_dep_node(mp, q); + } else { + mp->cur_exp.type = mp_dependent_type; + mp_encapsulate (mp, q); + } + } + break; + case mp_undefined_type: + mp_confusion(mp, "undefined copy"); + 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_new_value_node(mp); + mp_name_type(q) = mp_capsule_operation; + mp_new_dep(mp, q, mp->cur_exp.type, p); + mp_set_cur_exp_node(mp, q); +} + +@ 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) +{ + if (mp_type(q) == mp_known_type) { + mp_type(r) = mp_known_type; + mp_set_value_number(r, mp_get_value_number(q)); + } else if (mp_type(q) == mp_independent_type) { + mp_value_node p = mp_single_dependency(mp, q); + if (p == mp->dep_final) { + mp_type(r) = mp_known_type; + mp_set_value_number(r, zero_t); + mp_free_dep_node(mp, p); + } else { + mp_new_dep(mp, r, mp_dependent_type, p); + } + } else { + mp_new_dep(mp, r, mp_type(q), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) q))); + } +} + +@ 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 = mp_new_symbolic_node(mp); /* head of the list being built */ + mp_node t = h; /* tail of the list being built */ + while (1) { + mp_node p; + if (cur_cmd == mp_left_bracket_command) { + /* 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_type) { + mp_bad_subscript(mp); + } + if (cur_cmd != mp_right_bracket_command) { + mp_back_error( + mp, + "Missing ']' has been inserted", + "I've seen a '[' and a subscript value, in a suffix, so a right bracket should\n" + "have come next. I shall pretend that one was there." + ); + } + set_cur_cmd(mp_numeric_command); + set_cur_mod_number(cur_exp_value_number); + } + if (cur_cmd == mp_numeric_command) { + mp_number arg1; + new_number_clone(arg1, cur_mod_number); + p = mp_new_num_tok(mp, &arg1); + free_number(arg1); + } else if ((cur_cmd == mp_tag_command) || (cur_cmd == mp_internal_command)) { + p = mp_new_symbolic_node(mp); + mp_set_sym_sym(p, cur_sym); + mp_name_type(p) = cur_sym_mod; + } else { + break; + } + mp_link(t) = p; + t = p; + mp_get_x_next(mp); + } + mp_set_cur_exp_node(mp, mp_link(h)); + mp_free_symbolic_node(mp, h); + mp->cur_exp.type = mp_token_list_type; +} + +@* 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 cc = NULL; + mp_sym mac_name = NULL; /* token defined with |primarydef| */ + RESTART: + if ((cur_cmd < mp_min_primary_command) || (cur_cmd > mp_max_primary_command)) { + mp_bad_exp(mp, "A secondary"); + } + @.A secondary expression...@> + mp_scan_primary(mp); + CONTINUE: + if (cur_cmd <= mp_max_secondary_command && cur_cmd >= mp_min_secondary_command) { + mp_node p = mp_stash_cur_exp(mp); + int d = cur_cmd; + int c = cur_mod; + if (d == mp_primary_def_command) { + cc = cur_mod_node; + mac_name = cur_sym; + mp_add_mac_ref(cc); + } + mp_get_x_next(mp); + mp_scan_primary(mp); + if (d != mp_primary_def_command) { + mp_do_binary(mp, p, c); + } else { + mp_back_input(mp); + mp_binary_mac(mp, p, cc, mac_name); + mp_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 = mp_new_symbolic_node(mp); + mp_node r = mp_new_symbolic_node(mp); + mp_link(q) = r; + mp_set_sym_sym(q, p); + mp_set_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 cc = NULL; + mp_sym mac_name = NULL; /* token defined with |secondarydef| */ + RESTART: + if ((cur_cmd < mp_min_primary_command) || (cur_cmd > mp_max_primary_command)) { + mp_bad_exp(mp, "A tertiary"); + } + @.A tertiary expression...@> + mp_scan_secondary(mp); + CONTINUE: + if (cur_cmd <= mp_max_tertiary_command && cur_cmd >= mp_min_tertiary_command) { + mp_node p = mp_stash_cur_exp(mp); + int c = cur_mod; + int d = cur_cmd; + if (d == mp_secondary_def_command) { + cc = cur_mod_node; + mac_name = cur_sym; + mp_add_mac_ref(cc); + } + mp_get_x_next(mp); + mp_scan_secondary(mp); + if (d != mp_secondary_def_command) { + mp_do_binary(mp, p, c); + } else { + mp_back_input(mp); + mp_binary_mac(mp, p, cc, mac_name); + mp_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. + +@<Declare the basic parsing subroutines@>= + +static int mp_scan_path (MP mp); + +static void mp_scan_expression (MP mp) +{ + int my_var_flag = mp->var_flag; + mp_check_expansion_depth(mp); + RESTART: + if ((cur_cmd < mp_min_primary_command) || (cur_cmd > mp_max_primary_command)) { + mp_bad_exp(mp, "An"); + } + @.An expression...@> + mp_scan_tertiary(mp); + CONTINUE: + if (cur_cmd <= mp_max_expression_command) { + if (cur_cmd >= mp_min_expression_command) { + if ((cur_cmd != mp_equals_command) || (my_var_flag != mp_assignment_command)) { + mp_node cc = NULL; + mp_sym mac_name; /* token defined with |tertiarydef| */ + mac_name = NULL; + mp_node p = mp_stash_cur_exp(mp); + int d = cur_cmd; + int c = cur_mod; + if (d == mp_tertiary_def_command) { + cc = cur_mod_node; + mac_name = cur_sym; + mp_add_mac_ref(cc); + } + if ((d < mp_ampersand_command) || ((d == mp_ampersand_command) && ((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 */ + mp_unstash_cur_exp(mp, p); + if (! mp_scan_path(mp)) { + mp->expand_depth_count--; + return; + } + } else { + mp_get_x_next(mp); + mp_scan_tertiary(mp); + if (d != mp_tertiary_def_command) { + mp_do_binary(mp, p, c); + } else { + mp_back_input(mp); + mp_binary_mac(mp, p, cc, mac_name); + mp_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. + +@d min_tension three_quarter_unit_t + +@<Declare the basic parsing subroutines@>= +static void force_valid_tension_setting (MP mp) +{ + if ((mp->cur_exp.type != mp_known_type) || number_less(cur_exp_value_number, min_tension)) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + number_clone(new_expr.data.n, unity_t); + mp_back_error( + mp, + "Improper tension has been set to 1", + "The expression above should have been a number >= 3/4." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } +} + +static int mp_scan_path (MP mp) +{ + mp_knot path_p, path_q, r; + mp_knot pp = NULL; + mp_knot qq = NULL; + int d, dd; /* operation code or modifier */ + int cycle_hit = 0; /* did a path expression just end with |cycle|? */ + mp_number x, y; /* explicit coordinates or tension at a path join */ + int t = mp_endpoint_knot; /* knot type following a path join */ + /* + Convert the left operand, |p|, into a partial path ending at~|q|; but + |return| if |p| doesn't have a suitable type + */ + 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 0; + } + 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_knot) { + /* open up a cycle */ + r = mp_copy_knot(mp, path_p); + mp_prev_knot(r) = path_q; + mp_next_knot(path_q) = r; + path_q = r; + } + mp_left_type(path_p) = mp_open_knot; + mp_right_type(path_q) = mp_open_knot; + new_number(y); + new_number(x); + CONTINUE_PATH: + /* + Determine the path join parameters; but |goto finish_path| if there's only a + direction specifier At this point |cur_cmd| is either |ampersand|, + |left_brace|, or |path_join|. + */ + if (cur_cmd == mp_left_brace_command) { + /* + Put the pre-join direction information into node |q|. 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\}..}'. + */ + t = mp_scan_direction(mp); + if (t != mp_open_knot) { + mp_right_type(path_q) = (unsigned char) t; + number_clone(path_q->right_given, cur_exp_value_number); + if (mp_left_type(path_q) == mp_open_knot) { + mp_left_type(path_q) = (unsigned char) t; + number_clone(path_q->left_given, cur_exp_value_number); + } /* note that |left_given(q)=left_curl(q)| */ + } + } + d = cur_cmd; + dd = cur_mod; + if (d == mp_path_join_command) { + /* Determine the tension and/or control points */ + mp_get_x_next(mp); + switch (cur_cmd) { + case mp_tension_command: + /* Set explicit tensions */ + mp_get_x_next(mp); + set_number_from_scaled(y, cur_cmd); + if (cur_cmd == mp_at_least_command) { + mp_get_x_next(mp); + } + mp_scan_primary(mp); + force_valid_tension_setting(mp); + if (number_to_scaled(y) == mp_at_least_command && is_number(cur_exp_value_number)) { + number_negate(cur_exp_value_number); + } + number_clone(path_q->right_tension, cur_exp_value_number); + if (cur_cmd == mp_and_command) { + mp_get_x_next(mp); + set_number_from_scaled(y, cur_cmd); + if (cur_cmd == mp_at_least_command) { + mp_get_x_next(mp); + } + mp_scan_primary(mp); + force_valid_tension_setting(mp); + if (number_to_scaled(y) == mp_at_least_command && is_number(cur_exp_value_number)) { + number_negate(cur_exp_value_number); + } + } + number_clone(y, cur_exp_value_number); + break; + case mp_controls_command: + /* Set explicit control points */ + mp_right_type(path_q) = mp_explicit_knot; + t = mp_explicit_knot; + mp_get_x_next(mp); + mp_scan_primary(mp); + mp_known_pair(mp); + number_clone(path_q->right_x, mp->cur_x); + number_clone(path_q->right_y, mp->cur_y); + if (cur_cmd != mp_and_command) { + number_clone(x, path_q->right_x); + number_clone(y, path_q->right_y); + } else { + mp_get_x_next(mp); + mp_scan_primary(mp); + mp_known_pair(mp); + number_clone(x, mp->cur_x); + number_clone(y, mp->cur_y); + } + break; + default: + set_number_to_unity(path_q->right_tension); + set_number_to_unity(y); + /* default tension */ + mp_back_input(mp); + goto DONE; + break; + } + if (cur_cmd != mp_path_join_command) { + mp_back_error( + mp, + "Missing '..' has been inserted", + "A path join command should end with two dots." + ); + } + DONE: + ; /* needed */ + } else if (d != mp_ampersand_command) { + goto FINISH_PATH; + } + mp_get_x_next(mp); + if (cur_cmd == mp_left_brace_command) { + /* + Put the post-join direction information into |x| and |t|. Since + |left_tension| and |mp_left_y| share the same position in knot nodes, + and since |left_given| is similarly equivalent to |left_x|, we use + |x| and |y| to hold the given direction and tension information when + there are no explicit control points. + */ + t = mp_scan_direction(mp); + if (mp_right_type(path_q) != mp_explicit_knot) { + number_clone(x, cur_exp_value_number); + } else { + /* the direction information is superfluous */ + t = mp_explicit_knot; + } + } else if (mp_right_type(path_q) != mp_explicit_knot) { + t = mp_open_knot; + set_number_to_zero(x); + } + if (cur_cmd == mp_cycle_command) { + /* + Get ready to close a cycle. 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. + */ + if (cur_mod == mp_cycle_operation) { + cycle_hit = 1; + mp_get_x_next(mp); + pp = path_p; + qq = path_p; + if (d == mp_ampersand_command && path_p == path_q) { + d = mp_path_join_command; + set_number_to_unity(path_q->right_tension); + set_number_to_unity(y); + } + } else { + mp_get_x_next(mp); + qq = pp; + goto FINISH_PATH; + } + } else { + mp_scan_tertiary(mp); + /* + Convert the right operand, |cur_exp|, into a partial path from |pp| + to~|qq| + */ + 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_knot) { /* open up a cycle */ + r = mp_copy_knot(mp, pp); + mp_prev_knot(r) = qq; + mp_next_knot(qq) = r; + qq = r; + } + mp_left_type(pp) = mp_open_knot; + mp_right_type(qq) = mp_open_knot; + } + /* + Join the partial paths and reset |p| and |q| to the head and tail of the + result + */ + if (d == mp_ampersand_command && dd != mp_just_append_operation) { + if (! (number_equal(path_q->x_coord, pp->x_coord)) || ! (number_equal(path_q->y_coord, pp->y_coord))) { + mp_back_error( + mp, + "Paths don't touch; '&' will be changed to '..'", + "When you join paths 'p & q', the ending point of p must be exactly equal to the\n" + "starting point of q. So I'm going to pretend that you said 'p .. q' instead." + ); + @.Paths don't touch@> + mp_get_x_next(mp); + d = mp_path_join_command; + set_number_to_unity(path_q->right_tension); + set_number_to_unity(y); + } + } + /* Plug an opening in |mp_right_type(pp)|, if possible */ + if (mp_right_type(pp) == mp_open_knot && ((t == mp_curl_knot) || (t == mp_given_knot))) { + mp_right_type(pp) = (unsigned char) t; + number_clone(pp->right_given, x); + } + if (d == mp_ampersand_command) { + /* Splice independent paths together */ + if (dd == mp_just_append_operation) { + mp_left_type(pp) = mp_explicit_knot; + mp_right_type(path_q) = mp_explicit_knot; + mp_prev_knot(pp) = path_q; + mp_next_knot(path_q) = pp; + number_clone(pp->left_x, path_q->x_coord); + number_clone(pp->left_y, path_q->y_coord); + number_clone(path_q->right_x, pp->x_coord); + number_clone(path_q->right_y, pp->y_coord); + mp_knotstate(pp) = mp_begin_knot; + mp_knotstate(path_q) = mp_end_knot; + path_q = pp; + } else { + if (mp_left_type(path_q) == mp_open_knot && mp_right_type(path_q) == mp_open_knot) { + mp_left_type(path_q) = mp_curl_knot; + set_number_to_unity(path_q->left_curl); + } + if (mp_right_type(pp) == mp_open_knot && t == mp_open_knot) { + mp_right_type(pp) = mp_curl_knot; + set_number_to_unity(pp->right_curl); + } + mp_right_type(path_q) = mp_right_type(pp); + mp_prev_knot(pp) = mp_next_knot(path_q); + mp_next_knot(path_q) = mp_next_knot(pp); + number_clone(path_q->right_x, pp->right_x); + number_clone(path_q->right_y, pp->right_y); + mp_memory_free(pp); + } + if (qq == pp) { + qq = path_q; + } + } else { + /* Plug an opening in |mp_right_type(q)|, if possible */ + if (mp_right_type(path_q) == mp_open_knot && ((mp_left_type(path_q) == mp_curl_knot) || (mp_left_type(path_q) == mp_given_knot))) { + mp_right_type(path_q) = mp_left_type(path_q); + number_clone(path_q->right_given, path_q->left_given); + } + mp_prev_knot(pp) = path_q; + mp_next_knot(path_q) = pp; + number_clone(pp->left_y, y); + if (t != mp_open_knot) { + number_clone(pp->left_x, x); + mp_left_type(pp) = (unsigned char) t; + }; + } + path_q = qq; + if (cur_cmd >= mp_min_expression_command && cur_cmd <= mp_ampersand_command && ! cycle_hit) { + goto CONTINUE_PATH; + } + FINISH_PATH: + /* + Choose control points for the path and put the result into |cur_exp| + */ + if (cycle_hit) { + if (d == mp_ampersand_command) { + path_p = path_q; + } + } else { + mp_left_type(path_p) = mp_endpoint_knot; + if (mp_right_type(path_p) == mp_open_knot) { + mp_right_type(path_p) = mp_curl_knot; + set_number_to_unity(path_p->right_curl); + } + mp_right_type(path_q) = mp_endpoint_knot; + if (mp_left_type(path_q) == mp_open_knot) { + mp_left_type(path_q) = mp_curl_knot; + set_number_to_unity(path_q->left_curl); + } + mp_prev_knot(path_p) = path_q; + mp_next_knot(path_q) = path_p; + } + mp_make_choices(mp, path_p); + mp->cur_exp.type = mp_path_type; + mp_set_cur_exp_knot(mp, path_p); + free_number(x); + free_number(y); + return 1; +} + +@ 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 = mp_new_knot(mp); + mp_left_type(q) = mp_endpoint_knot; + mp_right_type(q) = mp_endpoint_knot; + mp_originator(q) = mp_metapost_user; + mp_knotstate(q) = mp_regular_knot; + mp_prev_knot(q) = q; + mp_next_knot(q) = q; + mp_known_pair(mp); + number_clone(q->x_coord, mp->cur_x); + number_clone(q->y_coord, 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; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + if (mp->cur_exp.type != mp_pair_type) { + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Undefined coordinates have been replaced by (0,0)", + "I need x and y numbers for this part of the path. The value I found (see above)\n" + "was no good; so I'll try to keep going by using zero instead." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + set_number_to_zero(mp->cur_x); + set_number_to_zero(mp->cur_y); + } else { + mp_node p = mp_get_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| + */ + if (mp_type(mp_x_part(p)) == mp_known_type) { + number_clone(mp->cur_x, mp_get_value_number(mp_x_part(p))); + } else { + mp_disp_err(mp, mp_x_part(p)); + mp_back_error( + mp, + "Undefined x coordinate has been replaced by 0", + "I need a 'known' x value for this part of the path. The value I found (see above)\n" + "was no good; so I'll try to keep going by using zero instead." + ); + mp_get_x_next(mp); + mp_recycle_value(mp, mp_x_part(p)); + set_number_to_zero(mp->cur_x); + } + if (mp_type(mp_y_part(p)) == mp_known_type) { + number_clone(mp->cur_y, mp_get_value_number(mp_y_part(p))); + } else { + mp_disp_err(mp, mp_y_part(p)); + mp_back_error( + mp, + "Undefined y coordinate has been replaced by 0", + "I need a 'known' y value for this part of the path. The value I found (see above)\n" + "was no good; so I'll try to keep going by using zero instead." + ); + mp_get_x_next(mp); + mp_recycle_value(mp, mp_y_part(p)); + set_number_to_zero(mp->cur_y); + } + mp_flush_cur_exp(mp, new_expr); + } +} + +@ 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 int mp_scan_direction (MP mp) +{ + int t; /* the type of information found */ + mp_get_x_next(mp); + if (cur_cmd == mp_curl_command) { + /* Scan a curl specification */ + mp_get_x_next(mp); + mp_scan_expression(mp); + if ((mp->cur_exp.type != mp_known_type) || (number_negative(cur_exp_value_number))) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + set_number_to_unity(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Improper curl has been replaced by 1", + "A curl must be a known, nonnegative number." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } + t = mp_curl_knot; + } else { + /* Scan a given direction */ + mp_scan_expression(mp); + if (mp->cur_exp.type > mp_pair_type) { + /* Get given directions separated by commas */ + mp_number xx; + new_number(xx); + if (mp->cur_exp.type != mp_known_type) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Undefined x coordinate has been replaced by 0", + "I need a 'known' x value for this part of the path. The value I found (see above)\n" + "was no good; so I'll try to keep going by using zero instead." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } + number_clone(xx, cur_exp_value_number); + if (cur_cmd != mp_comma_command) { + mp_back_error( + mp, + "Missing ',' has been inserted", + "I've got the x coordinate of a path direction; will look for the y coordinate\n" + "next." + ); + } + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type != mp_known_type) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Undefined y coordinate has been replaced by 0", + "I need a 'known' y value for this part of the path. The value I found (see above)\n" + "was no good; so I'll try to keep going by using zero instead." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } + number_clone(mp->cur_y, cur_exp_value_number); + number_clone(mp->cur_x, xx); + free_number(xx); + } else { + mp_known_pair(mp); + } + if (number_zero(mp->cur_x) && number_zero(mp->cur_y)) { + t = mp_open_knot; + } else { + mp_number narg; + new_angle(narg); + n_arg(narg, mp->cur_x, mp->cur_y); + t = mp_given_knot; + mp_set_cur_exp_value_number(mp, &narg); + free_number(narg); + } + } + if (cur_cmd != mp_right_brace_command) { + mp_back_error( + mp, + "Missing '}' has been inserted", + "I've scanned a direction spec for part of a path, so a right brace should have\n" + "come next. I shall pretend that one was there." + ); + } + mp_get_x_next(mp); + return t; +} + +@<Declare the basic parsing subroutines@>= +static void do_boolean_error (MP mp) +{ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + set_number_from_boolean(new_expr.data.n, mp_false_operation); + mp_back_error( + mp, + "Undefined condition will be treated as 'false'", + "The expression shown above should have had a definite true-or-false value. I'm\n" + "changing it to 'false'." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_boolean_type; +} + +@ @<Declarations@>= +static void do_boolean_error (MP mp); + +@* 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", mp_nullary_command, mp_true_operation); +@:true_}{|true| primitive@> +mp_primitive(mp, "false", mp_nullary_command, mp_false_operation); +@:false_}{|false| primitive@> +mp_primitive(mp, "nullpicture", mp_nullary_command, mp_null_picture_operation); +@:null_picture_}{|nullpicture| primitive@> +mp_primitive(mp, "nullpen", mp_nullary_command, mp_null_pen_operation); +@:null_pen_}{|nullpen| primitive@> +mp_primitive(mp, "readstring", mp_nullary_command, mp_read_string_operation); +@:read_string_}{|readstring| primitive@> +mp_primitive(mp, "pencircle", mp_nullary_command, mp_pen_circle_operation); +@:pen_circle_}{|pencircle| primitive@> +mp_primitive(mp, "normaldeviate", mp_nullary_command, mp_normal_deviate_operation); +@:normal_deviate_}{|normaldeviate| primitive@> +mp_primitive(mp, "readfrom", mp_unary_command, mp_read_from_operation); +@:read_from_}{|readfrom| primitive@> +mp_primitive(mp, "closefrom", mp_unary_command, mp_close_from_operation); +@:close_from_}{|closefrom| primitive@> +mp_primitive(mp, "odd", mp_unary_command, mp_odd_operation); +@:odd_}{|odd| primitive@> +mp_primitive(mp, "known", mp_unary_command, mp_known_operation); +@:known_}{|known| primitive@> +mp_primitive(mp, "unknown", mp_unary_command, mp_unknown_operation); +@:unknown_}{|unknown| primitive@> +mp_primitive(mp, "not", mp_unary_command, mp_not_operation); +@:not_}{|not| primitive@> +mp_primitive(mp, "decimal", mp_unary_command, mp_decimal_operation); +@:decimal_}{|decimal| primitive@> +mp_primitive(mp, "reverse", mp_unary_command, mp_reverse_operation); +@:reverse_}{|reverse| primitive@> +mp_primitive(mp, "uncycle", mp_unary_command, mp_uncycle_operation); +@:uncycle_}{|uncycle| primitive@> +mp_primitive(mp, "makepath", mp_unary_command, mp_make_path_operation); +@:make_path_}{|makepath| primitive@> +mp_primitive(mp, "makepen", mp_unary_command, mp_make_pen_operation); +@:make_pen_}{|makepen| primitive@> +mp_primitive(mp, "makenep", mp_unary_command, mp_make_nep_operation); +@:make_nep_}{|makenep| primitive@> +mp_primitive(mp, "convexed", mp_unary_command, mp_convexed_operation); +@:convexed_}{|convexed| primitive@> +mp_primitive(mp, "uncontrolled", mp_unary_command, mp_uncontrolled_operation); +@:convexed_}{|uncontrolled| primitive@> +mp_primitive(mp, "oct", mp_unary_command, mp_oct_operation); +@:oct_}{|oct| primitive@> +mp_primitive(mp, "hex", mp_unary_command, mp_hex_operation); +@:hex_}{|hex| primitive@> +mp_primitive(mp, "ASCII", mp_unary_command, mp_ASCII_operation); +@:ASCII_}{|ASCII| primitive@> +mp_primitive(mp, "char", mp_unary_command, mp_char_operation); +@:char_}{|char| primitive@> +mp_primitive(mp, "length", mp_unary_command, mp_length_operation); +@:length_}{|length| primitive@> +mp_primitive(mp, "turningnumber", mp_unary_command, mp_turning_operation); +@:turning_number_}{|turningnumber| primitive@> +mp_primitive(mp, "xpart", mp_unary_command, mp_x_part_operation); +@:x_part_}{|xpart| primitive@> +mp_primitive(mp, "ypart", mp_unary_command, mp_y_part_operation); +@:y_part_}{|ypart| primitive@> +mp_primitive(mp, "xxpart", mp_unary_command, mp_xx_part_operation); +@:xx_part_}{|xxpart| primitive@> +mp_primitive(mp, "xypart", mp_unary_command, mp_xy_part_operation); +@:xy_part_}{|xypart| primitive@> +mp_primitive(mp, "yxpart", mp_unary_command, mp_yx_part_operation); +@:yx_part_}{|yxpart| primitive@> +mp_primitive(mp, "yypart", mp_unary_command, mp_yy_part_operation); +@:yy_part_}{|yypart| primitive@> +mp_primitive(mp, "redpart", mp_unary_command, mp_red_part_operation); +@:red_part_}{|redpart| primitive@> +mp_primitive(mp, "greenpart", mp_unary_command, mp_green_part_operation); +@:green_part_}{|greenpart| primitive@> +mp_primitive(mp, "bluepart", mp_unary_command, mp_blue_part_operation); +@:blue_part_}{|bluepart| primitive@> +mp_primitive(mp, "cyanpart", mp_unary_command, mp_cyan_part_operation); +@:cyan_part_}{|cyanpart| primitive@> +mp_primitive(mp, "magentapart", mp_unary_command, mp_magenta_part_operation); +@:magenta_part_}{|magentapart| primitive@> +mp_primitive(mp, "yellowpart", mp_unary_command, mp_yellow_part_operation); +@:yellow_part_}{|yellowpart| primitive@> +mp_primitive(mp, "blackpart", mp_unary_command, mp_black_part_operation); +@:black_part_}{|blackpart| primitive@> +mp_primitive(mp, "greypart", mp_unary_command, mp_grey_part_operation); +@:grey_part_}{|greypart| primitive@> +mp_primitive(mp, "colormodel", mp_unary_command, mp_color_model_operation); +@:color_model_part_}{|colormodel| primitive@> +mp_primitive(mp, "prescriptpart", mp_unary_command, mp_prescript_part_operation); +@:prescript_part_}{|prescriptpart| primitive@> +mp_primitive(mp, "postscriptpart", mp_unary_command, mp_postscript_part_operation); +@:postscript_part_}{|postscriptpart| primitive@> +mp_primitive(mp, "stackingpart", mp_unary_command, mp_stacking_part_operation); +@:stacking_part_}{|stackingpart| primitive@> +mp_primitive(mp, "pathpart", mp_unary_command, mp_path_part_operation); +@:path_part_}{|pathpart| primitive@> +mp_primitive(mp, "penpart", mp_unary_command, mp_pen_part_operation); +@:pen_part_}{|penpart| primitive@> +mp_primitive(mp, "dashpart", mp_unary_command, mp_dash_part_operation); +@:dash_part_}{|dashpart| primitive@> +mp_primitive(mp, "sqrt", mp_unary_command, mp_sqrt_operation); +@:sqrt_}{|sqrt| primitive@> +mp_primitive(mp, "mexp", mp_unary_command, mp_m_exp_operation); +@:m_exp_}{|mexp| primitive@> +mp_primitive(mp, "mlog", mp_unary_command, mp_m_log_operation); +@:m_log_}{|mlog| primitive@> +mp_primitive(mp, "sind", mp_unary_command, mp_sin_d_operation); +@:sin_d_}{|sind| primitive@> +mp_primitive(mp, "cosd", mp_unary_command, mp_cos_d_operation); +@:cos_d_}{|cosd| primitive@> +mp_primitive(mp, "floor", mp_unary_command, mp_floor_operation); +@:floor_}{|floor| primitive@> +mp_primitive(mp, "uniformdeviate", mp_unary_command, mp_uniform_deviate_operation); +@:uniform_deviate_}{|uniformdeviate| primitive@> +mp_primitive(mp, "llcorner", mp_unary_command, mp_ll_corner_operation); +@:ll_corner_}{|llcorner| primitive@> +mp_primitive(mp, "lrcorner", mp_unary_command, mp_lr_corner_operation); +@:lr_corner_}{|lrcorner| primitive@> +mp_primitive(mp, "ulcorner", mp_unary_command, mp_ul_corner_operation); +@:ul_corner_}{|ulcorner| primitive@> +mp_primitive(mp, "urcorner", mp_unary_command, mp_ur_corner_operation); +@:ur_corner_}{|urcorner| primitive@> +mp_primitive(mp, "centerof", mp_unary_command, mp_center_of_operation); +@:center_}{|center| primitive@> +mp_primitive(mp, "centerofmass", mp_unary_command, mp_center_of_mass_operation); +@:center_}{|centerofmass| primitive@> +mp_primitive(mp, "corners", mp_unary_command, mp_corners_operation); +@:corners_}{|corners| primitive@> +mp_primitive(mp, "xrange", mp_unary_command, mp_x_range_operation); +@:xrange_}{|xrange| primitive@> +mp_primitive(mp, "yrange", mp_unary_command, mp_y_range_operation); +@:yrange_}{|xrange| primitive@> +mp_primitive(mp, "deltapoint", mp_unary_command, mp_delta_point_operation); +@:deltapoint_}{|deltapoint| primitive@> +mp_primitive(mp, "deltaprecontrol", mp_unary_command, mp_delta_precontrol_operation); +@:deltaprecontrol_}{|deltaprecontrol| primitive@> +mp_primitive(mp, "deltapostcontrol", mp_unary_command, mp_delta_postcontrol_operation); +@:deltapostcontrol_}{|deltapostcontrol| primitive@> +mp_primitive(mp, "deltadirection", mp_unary_command, mp_delta_direction_operation); +@:deltadirection_}{|deltadirection| primitive@> +mp_primitive(mp, "arclength", mp_unary_command, mp_arc_length_operation); +@:arc_length_}{|arclength| primitive@> +mp_primitive(mp, "angle", mp_unary_command, mp_angle_operation); +@:angle_}{|angle| primitive@> +mp_primitive(mp, "cycle", mp_cycle_command, mp_cycle_operation); +@:cycle_}{|cycle| primitive@> +mp_primitive(mp, "nocycle", mp_cycle_command, mp_no_cycle_operation); +@:nocycle_}{|nocycle| primitive@> +mp_primitive(mp, "stroked", mp_unary_command, mp_stroked_operation); +@:stroked_}{|stroked| primitive@> +mp_primitive(mp, "filled", mp_unary_command, mp_filled_operation); +@:filled_}{|filled| primitive@> +mp_primitive(mp, "clipped", mp_unary_command, mp_clipped_operation); +@:clipped_}{|clipped| primitive@> +mp_primitive(mp, "grouped", mp_unary_command, mp_grouped_operation); +@:clipped_}{|grouped| primitive@> +mp_primitive(mp, "bounded", mp_unary_command, mp_bounded_operation); +@:bounded_}{|bounded| primitive@> +mp_primitive(mp, "+", mp_plus_or_minus_command, mp_plus_operation); +@:+ }{|+| primitive@> +mp_primitive(mp, "-", mp_plus_or_minus_command, mp_minus_operation); +@:- }{|-| primitive@> +mp_primitive(mp, "*", mp_secondary_binary_command, mp_times_operation); +@:* }{|*| primitive@> +mp_primitive(mp, "/", mp_slash_command, mp_over_operation); +mp->frozen_slash = mp_frozen_primitive (mp, "/", mp_slash_command, mp_over_operation); +@:/ }{|/| primitive@> +mp_primitive(mp, "^", mp_secondary_binary_command, mp_power_operation); +@:^ }{|^| primitive@> +mp_primitive(mp, "++", mp_tertiary_binary_command, mp_pythag_add_operation); +@:++_}{|++| primitive@> +mp_primitive(mp, "+-+", mp_tertiary_binary_command, mp_pythag_sub_operation); +@:+-+_}{|+-+| primitive@> +mp_primitive(mp, "or", mp_tertiary_binary_command, mp_or_operation); +@:or_}{|or| primitive@> +mp_primitive(mp, "and", mp_and_command, mp_and_operation); +@:and_}{|and| primitive@> +mp_primitive(mp, "<", mp_primary_binary_command, mp_less_than_operation); +@:< }{|<| primitive@> +mp_primitive(mp, "<=", mp_primary_binary_command, mp_less_or_equal_operation); +@:<=_}{|<=| primitive@> +mp_primitive(mp, ">", mp_primary_binary_command, mp_greater_than_operation); +@:> }{|>| primitive@> +mp_primitive(mp, ">=", mp_primary_binary_command, mp_greater_or_equal_operation); +@:>=_}{|>=| primitive@> +mp_primitive(mp, "=", mp_equals_command, mp_equal_operation); +@:= }{|=| primitive@> +mp_primitive(mp, "<>", mp_primary_binary_command, mp_unequal_operation); +@:<>_}{|<>| primitive@> +mp_primitive(mp, "substring", mp_of_binary_command, mp_substring_operation); +@:substring_}{|substring| primitive@> +mp_primitive(mp, "subpath", mp_of_binary_command, mp_subpath_operation); +@:subpath_}{|subpath| primitive@> +mp_primitive(mp, "directiontime", mp_of_binary_command, mp_direction_time_operation); +@:direction_time_}{|directiontime| primitive@> +mp_primitive(mp, "point", mp_of_binary_command, mp_point_operation); +@:point_}{|point| primitive@> +mp_primitive(mp, "precontrol", mp_of_binary_command, mp_precontrol_operation); +@:precontrol_}{|precontrol| primitive@> +mp_primitive(mp, "postcontrol", mp_of_binary_command, mp_postcontrol_operation); +@:direction_}{|direction| primitive@> +mp_primitive(mp, "direction", mp_of_binary_command, mp_direction_operation); +@:postcontrol_}{|postcontrol| primitive@> +mp_primitive(mp, "pathpoint", mp_nullary_command, mp_path_point_operation); +@:pathpoint_}{|pathpoint| primitive@> +mp_primitive(mp, "pathprecontrol", mp_nullary_command, mp_path_precontrol_operation); +@:pathprecontrol_}{|pathprecontrol| primitive@> +mp_primitive(mp, "pathpostcontrol", mp_nullary_command, mp_path_postcontrol_operation); +@:pathpostcontrol_}{|pathpostcontrol| primitive@> +mp_primitive(mp, "pathdirection", mp_nullary_command, mp_path_direction_operation); +@:pathdirection_}{|pathdirection| primitive@> +mp_primitive(mp, "penoffset", mp_of_binary_command, mp_pen_offset_operation); +@:pen_offset_}{|penoffset| primitive@> +mp_primitive(mp, "arctime", mp_of_binary_command, mp_arc_time_operation); +@:arc_time_of_}{|arctime| primitive@> +mp_primitive(mp, "arcpoint", mp_of_binary_command, mp_arc_point_operation); +@:arc_point_of_}{|arcpoint| primitive@> +mp_primitive(mp, "arcpointlist", mp_of_binary_command, mp_arc_point_list_operation); +@:arc_point_list_of_}{|arcpointlist| primitive@> +mp_primitive(mp, "subarclength", mp_of_binary_command, mp_subarc_length_operation); +@:subarc_length_of_}{|subarclength| primitive@> +mp_primitive(mp, "mpversion", mp_nullary_command, mp_version_operation); +@:mp_version_}{|mpversion| primitive@> +mp_primitive(mp, "&", mp_ampersand_command, mp_concatenate_operation); +@:!!!}{|\&| primitive@> +mp_primitive(mp, "&&", mp_ampersand_command, mp_just_append_operation); +@:!!!!!!}{|\&\&| primitive@> +mp_primitive(mp, "rotated", mp_secondary_binary_command, mp_rotated_operation); +@:rotated_}{|rotated| primitive@> +mp_primitive(mp, "slanted", mp_secondary_binary_command, mp_slanted_operation); +@:slanted_}{|slanted| primitive@> +mp_primitive(mp, "scaled", mp_secondary_binary_command, mp_scaled_operation); +@:scaled_}{|scaled| primitive@> +mp_primitive(mp, "shifted", mp_secondary_binary_command, mp_shifted_operation); +@:shifted_}{|shifted| primitive@> +mp_primitive(mp, "transformed", mp_secondary_binary_command, mp_transformed_operation); +@:transformed_}{|transformed| primitive@> +mp_primitive(mp, "xscaled", mp_secondary_binary_command, mp_x_scaled_operation); +@:x_scaled_}{|xscaled| primitive@> +mp_primitive(mp, "yscaled", mp_secondary_binary_command, mp_y_scaled_operation); +@:y_scaled_}{|yscaled| primitive@> +mp_primitive(mp, "zscaled", mp_secondary_binary_command, mp_z_scaled_operation); +@:z_scaled_}{|zscaled| primitive@> +mp_primitive(mp, "intersectiontimes", mp_tertiary_binary_command, mp_intertimes_operation); +@:intersection_times_}{|intersectiontimes| primitive@> +mp_primitive(mp, "intersectiontimeslist", mp_tertiary_binary_command, mp_intertimes_list_operation); +@:intersection_times_list_}{|intersectiontimeslist| primitive@> +mp_primitive(mp, "envelope", mp_of_binary_command, mp_envelope_operation); +@:envelope_}{|envelope| primitive@> +mp_primitive(mp, "boundingpath", mp_of_binary_command, mp_boundingpath_operation); +@:boundingpath_}{|boundingpath| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_nullary_command: +case mp_unary_command: +case mp_of_binary_command: +case mp_secondary_binary_command: +case mp_tertiary_binary_command: +case mp_primary_binary_command: +case mp_cycle_command: +case mp_plus_or_minus_command: +case mp_slash_command: +case mp_ampersand_command: +case mp_equals_command: +case mp_and_command: + return mp_op_string((int) m); + +@ @<Declarations@>= +static void push_of_path_result (MP mp, int what, mp_knot p); + +@ @c +static void push_of_path_result (MP mp, int what, mp_knot p) +{ + switch (what) { + case 0: + mp_pair_value(mp, &(p->x_coord), &(p->y_coord)); + break; + case 1: + if (mp_left_type(p) == mp_endpoint_knot) { + mp_pair_value(mp, &(p->x_coord), &(p->y_coord)); + } else { + mp_pair_value(mp, &(p->left_x), &(p->left_y)); + } + break; + case 2: + if (mp_right_type(p) == mp_endpoint_knot) { + mp_pair_value(mp, &(p->x_coord), &(p->y_coord)); + } else { + mp_pair_value(mp, &(p->right_x), &(p->right_y)); + } + break; + case 3: + { + mp_number x, y; + if (mp_right_type(p) == mp_endpoint_knot) { + new_number_clone(x, p->x_coord); + new_number_clone(y, p->y_coord); + } else { + new_number_clone(x, p->right_x); + new_number_clone(y, p->right_y); + } + if (mp_left_type(p) == mp_endpoint_knot) { + number_subtract(x, p->x_coord); + number_subtract(y, p->y_coord); + } else { + number_subtract(x, p->left_x); + number_subtract(y, p->left_y); + } + mp_pair_value(mp, &x, &y); + free_number(x); + free_number(y); + } + break; + } +} + +@ OK, let's look at the simplest |do| procedure first. + +@c +@<Declare nullary action procedure@> +static void mp_do_nullary (MP mp, int c) +{ + check_arith(); + if (number_greater(internal_value(mp_tracing_commands_internal), two_t)) { + mp_show_cmd_mod(mp, mp_nullary_command, c); + } + switch (c) { + case mp_true_operation: + case mp_false_operation: + mp->cur_exp.type = mp_boolean_type; + mp_set_cur_exp_value_boolean(mp, c); + break; + case mp_null_picture_operation: + mp->cur_exp.type = mp_picture_type; + mp_set_cur_exp_node(mp, (mp_node) mp_get_edge_header_node(mp)); + mp_init_edges(mp, (mp_edge_header_node) cur_exp_node); + break; + case mp_null_pen_operation: + mp->cur_exp.type = mp_pen_type; + mp_set_cur_exp_knot(mp, mp_get_pen_circle(mp, &zero_t)); + break; + case mp_normal_deviate_operation: + { + mp_number r; + new_number(r); + /*|mp_norm_rand (mp, &r)|;*/ + m_norm_rand(r); + mp->cur_exp.type = mp_known_type; + mp_set_cur_exp_value_number(mp, &r); + free_number(r); + } + break; + case mp_pen_circle_operation: + mp->cur_exp.type = mp_pen_type; + mp_set_cur_exp_knot(mp, mp_get_pen_circle(mp, &unity_t)); + break; + case mp_version_operation: + mp->cur_exp.type = mp_string_type; + mp_set_cur_exp_str(mp, mp_intern(mp, metapost_version)); + break; + /* these are new */ + case mp_path_point_operation: + case mp_path_precontrol_operation: + case mp_path_postcontrol_operation: + case mp_path_direction_operation: + if (mp->loop_ptr && mp->loop_ptr->point != NULL) { + push_of_path_result(mp, c - mp_path_point_operation, mp->loop_ptr->point); + } else { + mp_pair_value(mp, &zero_t, &zero_t); + } + break; + } + check_arith(); +} + +@ @<Declare nullary action procedure@>= +static void mp_finish_read (MP mp) +{ + /* copy |buffer| line to |cur_exp| */ + mp_str_room(mp, (int) mp->last - (int) start); + for (size_t k = (size_t) start; k < mp->last; k++) { + mp_append_char(mp, mp->buffer[k]); + } + mp_end_file_reading(mp); + mp->cur_exp.type = mp_string_type; + mp_set_cur_exp_str(mp, 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|. + +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) . + +@c +static int mp_pict_color_type (MP mp, int c) +{ + /* cur_pic_item = mp_link(mp_edge_list(cur_exp_node)) */ + return ( + (mp_link(mp_edge_list(cur_exp_node)) != NULL) + && + ( + (! mp_has_color(mp_link(mp_edge_list(cur_exp_node)))) + || + (( + (mp_color_model(mp_link(mp_edge_list(cur_exp_node))) == c) + || + ( + (mp_color_model(mp_link(mp_edge_list(cur_exp_node))) == mp_uninitialized_model) + && + (number_to_scaled(internal_value(mp_default_color_model_internal))/number_to_scaled(unity_t)) == c + ) + )) + ) + ); +} + +@<Declarations@>= +static mp_knot mp_simple_knot(MP mp, mp_number *x, mp_number *y); +static mp_knot mp_complex_knot(MP mp, mp_knot o); + +@ @c +static mp_knot mp_simple_knot(MP mp, mp_number *x, mp_number *y) +{ + mp_knot k = mp_new_knot(mp); + mp_left_type(k) = mp_explicit_knot; + mp_right_type(k) = mp_explicit_knot; + mp_originator(k) = mp_program_code; + mp_knotstate(k) = mp_regular_knot; + number_clone(k->x_coord, *x); + number_clone(k->y_coord, *y); + number_clone(k->left_x, *x); + number_clone(k->left_y, *y); + number_clone(k->right_x, *x); + number_clone(k->right_y, *y); + return k; +} + +static mp_knot mp_complex_knot(MP mp, mp_knot o) +{ + mp_knot k = mp_new_knot(mp); + mp_left_type(k) = mp_explicit_knot; + mp_right_type(k) = mp_explicit_knot; + mp_originator(k) = mp_program_code; + mp_knotstate(k) = mp_regular_knot; + number_clone(k->x_coord, o->x_coord); + number_clone(k->y_coord, o->y_coord); + number_clone(k->left_x, o->left_x); + number_clone(k->left_y, o->left_y); + number_clone(k->right_x, o->right_x); + number_clone(k->right_y, o->right_y); + return k; +} + +@<Declarations@>= +static int mp_pict_color_type (MP mp, int c); + +@c +@<Declare unary action procedures@> + +static void mp_do_unary (MP mp, int c) +{ + check_arith(); + if (number_greater(internal_value(mp_tracing_commands_internal), two_t)) { + /* Trace the current unary operation */ + mp_begin_diagnostic(mp); + mp_print_nl(mp, "{"); + mp_print_op(mp, c); + mp_print_chr(mp, '('); + mp_print_exp(mp, NULL, 0); /* show the operand, but not verbosely */ + mp_print_str(mp, ")}"); + mp_end_diagnostic(mp, 0); + } + /* + This is a mix of combined and not combined. We could combine more or less + and let the compiler deal with it. + */ + switch (c) { + case mp_plus_operation: + if (mp->cur_exp.type < mp_color_type) { + mp_bad_unary(mp, mp_plus_operation); + } + break; + case mp_minus_operation: + negate_cur_expr(mp); + break; + case mp_not_operation: + if (mp->cur_exp.type != mp_boolean_type) { + mp_bad_unary(mp, mp_not_operation); + } else { + mp_set_cur_exp_value_boolean(mp, (cur_exp_value_boolean == mp_true_operation) ? mp_false_operation : mp_true_operation); + } + break; + /* We could use something function[mp_sqrt_operation] here: */ + case mp_sqrt_operation: + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, c); + } else { + mp_number n; + new_number(n); + square_rt(n, cur_exp_value_number); + mp_set_cur_exp_value_number(mp, &n); + free_number(n); + } + break; + case mp_m_exp_operation: + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, c); + } else { + mp_number n; + new_number(n); + m_exp(n, cur_exp_value_number); + mp_set_cur_exp_value_number(mp, &n); + free_number(n); + } + break; + case mp_m_log_operation: + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, c); + } else { + mp_number n; + new_number(n); + m_log(n, cur_exp_value_number); + mp_set_cur_exp_value_number(mp, &n); + free_number(n); + } + break; + case mp_sin_d_operation: + case mp_cos_d_operation: + /* + This is rather inefficient, esp decimal, to calculate both each time. We could + pass NULL as signal to do only one. + */ + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, c); + } else { + mp_number n_sin, n_cos, arg1, arg2; + new_number(arg1); + new_number(arg2); + new_fraction(n_sin); + new_fraction(n_cos); + number_clone(arg1, cur_exp_value_number); + number_clone(arg2, unity_t); + number_multiply_int(arg2, 360); + number_modulo(arg1, arg2); + convert_scaled_to_angle(arg1); + n_sin_cos(arg1, n_cos, n_sin); + if (c == mp_sin_d_operation) { + fraction_to_round_scaled(n_sin); + mp_set_cur_exp_value_number(mp, &n_sin); + } else { + fraction_to_round_scaled(n_cos); + mp_set_cur_exp_value_number(mp, &n_cos); + } + free_number(arg1); + free_number(arg2); + free_number(n_sin); + free_number(n_cos); + } + break; + case mp_floor_operation: + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, c); + } else { + mp_number n; + new_number(n); + number_clone(n, cur_exp_value_number); + floor_scaled(n); + mp_set_cur_exp_value_number(mp, &n); + free_number(n); + } + break; + case mp_uniform_deviate_operation: + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, c); + } else { + mp_number n; + new_number(n); + m_unif_rand(n, cur_exp_value_number); + mp_set_cur_exp_value_number(mp, &n); + free_number(n); + } + break; + case mp_odd_operation: + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, c); + } else { + mp_set_cur_exp_value_boolean(mp, number_odd(cur_exp_value_number) ? mp_true_operation : mp_false_operation); + mp->cur_exp.type = mp_boolean_type; + } + break; + case mp_angle_operation: + if (mp_nice_pair (mp, cur_exp_node, mp->cur_exp.type)) { + mp_value expr; + mp_node p; /* for list manipulation */ + mp_number narg; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + new_angle(narg); + p = mp_get_value_node(cur_exp_node); + n_arg(narg, mp_get_value_number(mp_x_part(p)), mp_get_value_number(mp_y_part(p))); + number_clone(expr.data.n, narg); + convert_angle_to_scaled(expr.data.n); + free_number(narg); + mp_flush_cur_exp(mp, expr); + } else { + mp_bad_unary(mp, mp_angle_operation); + } + break; + case mp_x_part_operation: + case mp_y_part_operation: + switch (mp->cur_exp.type) { + case mp_pair_type: + case mp_transform_type: + mp_take_part(mp, c); + break; + case mp_picture_type: + mp_take_pict_part(mp, c); + break; + default: + mp_bad_unary(mp, c); + break; + } + break; + case mp_xx_part_operation: + case mp_xy_part_operation: + case mp_yx_part_operation: + case mp_yy_part_operation: + switch (mp->cur_exp.type) { + case mp_transform_type: + mp_take_part(mp, c); + break; + case mp_picture_type: + mp_take_pict_part(mp, c); + break; + default: + mp_bad_unary(mp, c); + break; + } + break; + case mp_red_part_operation: + case mp_green_part_operation: + case mp_blue_part_operation: + switch (mp->cur_exp.type) { + case mp_color_type: + mp_take_part(mp, c); + break; + case mp_picture_type: + if (mp_pict_color_type(mp, mp_rgb_model)) { + mp_take_pict_part(mp, c); + } else { + mp_bad_color_part(mp, c); + } + break; + default: + mp_bad_unary(mp, c); + break; + } + break; + case mp_cyan_part_operation: + case mp_magenta_part_operation: + case mp_yellow_part_operation: + case mp_black_part_operation: + switch (mp->cur_exp.type) { + case mp_cmykcolor_type: + mp_take_part(mp, c); + break; + case mp_picture_type: + if (mp_pict_color_type(mp, mp_cmyk_model)) { + mp_take_pict_part(mp, c); + } else { + mp_bad_color_part(mp, c); + } + break; + default: + mp_bad_unary(mp, c); + break; + } + break; + case mp_grey_part_operation: + switch (mp->cur_exp.type) { + case mp_known_type: + break; + case mp_picture_type: + if (mp_pict_color_type(mp, mp_grey_model)) { + mp_take_pict_part(mp, c); + } else { + mp_bad_color_part(mp, c); + } + break; + default: + mp_bad_unary(mp, c); + break; + } + break; + case mp_color_model_operation: + case mp_path_part_operation: + case mp_pen_part_operation: + case mp_dash_part_operation: + case mp_prescript_part_operation: + case mp_postscript_part_operation: + case mp_stacking_part_operation: + if (mp->cur_exp.type == mp_picture_type) { + mp_take_pict_part(mp, c); + } else { + mp_bad_unary(mp, c); + } + break; + case mp_char_operation: + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, mp_char_operation); + } else { + int n = round_unscaled(cur_exp_value_number) % 256; + unsigned char s[2]; + mp_set_cur_exp_value_scaled(mp, n); + mp->cur_exp.type = mp_string_type; + if (number_negative(cur_exp_value_number)) { + n = number_to_scaled(cur_exp_value_number) + 256; + mp_set_cur_exp_value_scaled(mp, n); + } + s[0] = (unsigned char) number_to_scaled(cur_exp_value_number); + s[1] = '\0'; + mp_set_cur_exp_str(mp, mp_rtsl (mp, (char *) s, 1)); + } + break; + case mp_decimal_operation: + if (mp->cur_exp.type != mp_known_type) { + mp_bad_unary(mp, mp_decimal_operation); + } else { + int selector = mp->selector; + mp->selector = mp_new_string_selector; + print_number(cur_exp_value_number); + mp_set_cur_exp_str(mp, mp_make_string(mp)); + mp->selector = selector; + mp->cur_exp.type = mp_string_type; + } + break; + case mp_oct_operation: + case mp_hex_operation: + case mp_ASCII_operation: + if (mp->cur_exp.type != mp_string_type) { + mp_bad_unary(mp, c); + } else { + mp_str_to_num(mp); + } + break; + case mp_length_operation: + /* + The length operation is somewhat unusual in that it applies to a variety of + different types of operands. * + */ + switch (mp->cur_exp.type) { + case mp_string_type: + { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + number_clone(expr.data.n, unity_t); + number_multiply_int(expr.data.n, (int) cur_exp_str->len); + mp_flush_cur_exp(mp, expr); + break; + } + case mp_path_type: + { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + mp_path_length(mp, &expr.data.n); + mp_flush_cur_exp(mp, expr); + break; + } + case mp_known_type: + { + mp_set_cur_exp_value_number(mp, &cur_exp_value_number); + number_abs(cur_exp_value_number); + break; + } + case mp_picture_type: + { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + mp_picture_length(mp, &expr.data.n); + mp_flush_cur_exp(mp, expr); + break; + } + default: + if (mp_nice_pair (mp, cur_exp_node, mp->cur_exp.type)) { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + pyth_add(expr.data.n, + mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node))), + mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node))) + ); + mp_flush_cur_exp(mp, expr); + } else { + mp_bad_unary(mp, c); + } + break; + } + break; + case mp_turning_operation: + if (mp->cur_exp.type == mp_pair_type) { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + mp_flush_cur_exp(mp, expr); + } else if (mp->cur_exp.type != mp_path_type) { + mp_bad_unary(mp, mp_turning_operation); + } else if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + expr.data.p = NULL; + mp_flush_cur_exp(mp, expr); /* not a cyclic path */ + } else { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + mp_turn_cycles_wrapper(mp, &expr.data.n, cur_exp_knot); + mp_flush_cur_exp(mp, expr); + } + break; + /* Here we could do some delta(operation,type) trickery as with filled. */ + + case mp_boolean_type_operation: + case mp_string_type_operation: + case mp_pen_type_operation: + case mp_nep_type_operation: + case mp_path_type_operation: + case mp_picture_type_operation: + { + mp_value expr; + /*they are parallel but with 2 increments (known and unknown): */ + int type = (c - mp_boolean_type_operation) * 2 + mp_boolean_type ; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + set_number_from_boolean(expr.data.n, (mp->cur_exp.type == type || mp->cur_exp.type == (type + 1)) ? mp_true_operation : mp_false_operation); + mp_flush_cur_exp(mp, expr); + mp->cur_exp.type = mp_boolean_type; + break; + } + case mp_transform_type_operation: + case mp_color_type_operation: + case mp_cmykcolor_type_operation: + case mp_pair_type_operation: + { + mp_value expr; + /* they are parallel: */ + int type = (c - mp_transform_type_operation) + mp_transform_type; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + set_number_from_boolean(expr.data.n, mp->cur_exp.type == type ? mp_true_operation : mp_false_operation); + mp_flush_cur_exp(mp, expr); + mp->cur_exp.type = mp_boolean_type; + break; + } + case mp_numeric_type_operation: + { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + set_number_from_boolean(expr.data.n, (mp->cur_exp.type >= mp_known_type && mp->cur_exp.type <= mp_independent_type) ? mp_true_operation : mp_false_operation); + mp_flush_cur_exp(mp, expr); + mp->cur_exp.type = mp_boolean_type; + break; + } + case mp_known_operation: + case mp_unknown_operation: + { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + set_number_from_boolean(expr.data.n, mp_test_known(mp, c)); + mp_flush_cur_exp(mp, expr); + /* !! do not replace with |mp_set_cur_exp_node(mp, )| !! */ + cur_exp_node = NULL; + mp->cur_exp.type = mp_boolean_type; + break; + } + case mp_cycle_operation: + case mp_no_cycle_operation: + { + mp_value expr; + int b = 0; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + if (mp->cur_exp.type != mp_path_type) { + b = (c == mp_cycle_operation) ? mp_false_operation : mp_true_operation; + } else if (mp_left_type(cur_exp_knot) != mp_endpoint_knot) { + b = (c == mp_cycle_operation) ? mp_true_operation : mp_false_operation; + } else { + b = (c == mp_cycle_operation) ? mp_false_operation : mp_true_operation; + } + set_number_from_boolean(expr.data.n, b); + mp_flush_cur_exp(mp, expr); + mp->cur_exp.type = mp_boolean_type; + break; + } + case mp_arc_length_operation: + 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, mp_arc_length_operation); + } else { + mp_value expr; + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + mp_get_arc_length(mp, &expr.data.n, cur_exp_knot); + mp_flush_cur_exp(mp, expr); + } + break; + case mp_filled_operation: + case mp_stroked_operation: + case mp_clipped_operation: + case mp_grouped_operation: + case mp_bounded_operation: + { + mp_value expr; + @^data structure assumptions@> + memset(&expr, 0, sizeof(mp_value)); + new_number(expr.data.n); + if (mp->cur_exp.type != mp_picture_type) { + set_number_from_boolean(expr.data.n, mp_false_operation); + } else if (mp_link(mp_edge_list(cur_exp_node)) == NULL) { + set_number_from_boolean(expr.data.n, mp_false_operation); + } else { + /* they are parallel: */ + int type = c - mp_filled_operation + mp_fill_node_type; + set_number_from_boolean(expr.data.n, mp_type(mp_link(mp_edge_list(cur_exp_node))) == type ? mp_true_operation: mp_false_operation); + } + mp_flush_cur_exp(mp, expr); + mp->cur_exp.type = mp_boolean_type; + break; + } + case mp_make_pen_operation: + 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, mp_make_pen_operation); + } else { + mp->cur_exp.type = mp_pen_type; + mp_set_cur_exp_knot(mp, mp_make_pen(mp, cur_exp_knot, 1)); + } + break; + case mp_make_nep_operation: + 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, c); + } else { + mp->cur_exp.type = mp_nep_type; + mp_set_cur_exp_knot(mp, cur_exp_knot); + } + break; + case mp_convexed_operation: + if (mp->cur_exp.type != mp_path_type) { + mp_bad_unary(mp, mp_convexed_operation); + } else { + mp->cur_exp.type = mp_path_type; + mp_set_cur_exp_knot(mp, mp_convex_hull(mp, cur_exp_knot)); + mp_simplify_path(mp, cur_exp_knot); + } + break; + case mp_uncontrolled_operation: + if (mp->cur_exp.type != mp_path_type) { + mp_bad_unary(mp, mp_uncontrolled_operation); + } else { + mp->cur_exp.type = mp_path_type; + mp_simplify_path(mp, cur_exp_knot); + } + break; + case mp_make_path_operation: + if (mp->cur_exp.type != mp_pen_type && mp->cur_exp.type != mp_nep_type) { + mp_bad_unary(mp, mp_make_path_operation); + } else { + mp->cur_exp.type = mp_path_type; + mp_make_path(mp, cur_exp_knot); + } + break; + case mp_reverse_operation: + switch (mp->cur_exp.type) { + case mp_path_type: + { + mp_knot pk = mp_htap_ypoc(mp, cur_exp_knot); + if (mp_right_type(pk) == mp_endpoint_knot) { + pk = mp_next_knot(pk); + } + mp_toss_knot_list(mp, cur_exp_knot); + mp_set_cur_exp_knot(mp, pk); + } + break; + case mp_pair_type: + mp_pair_to_path(mp); + break; + default: + mp_bad_unary(mp, mp_reverse_operation); + break; + } + break; + case mp_uncycle_operation: + switch (mp->cur_exp.type) { + case mp_path_type: + mp_right_type(mp_prev_knot(cur_exp_knot)) = mp_endpoint_knot; + mp_left_type(cur_exp_knot) = mp_endpoint_knot; + break; + case mp_pair_type: + mp_pair_to_path(mp); + break; + default: + mp_bad_unary(mp, mp_uncycle_operation); + break; + } + break; + case mp_ll_corner_operation: + if (mp_get_cur_bbox(mp)) { + mp_pair_value(mp, &mp_minx, &mp_miny); + } else { + mp_bad_unary(mp, mp_ll_corner_operation); + } + break; + case mp_lr_corner_operation: + if (mp_get_cur_bbox(mp)) { + mp_pair_value(mp, &mp_maxx, &mp_miny); + } else { + mp_bad_unary(mp, mp_lr_corner_operation); + } + break; + case mp_ul_corner_operation: + if (mp_get_cur_bbox(mp)) { + mp_pair_value(mp, &mp_minx, &mp_maxy); + } else { + mp_bad_unary(mp, mp_ul_corner_operation); + } + break; + case mp_ur_corner_operation: + if (! mp_get_cur_bbox(mp)) { + mp_bad_unary(mp, mp_ur_corner_operation); + } else { + mp_pair_value(mp, &mp_maxx, &mp_maxy); + } + break; + case mp_center_of_operation: + if (mp->cur_exp.type == mp_pair_type) { + /* keep the pair */ + } else if (mp_get_cur_bbox(mp)) { + /* todo: make this a function call */ + mp_number x, y; + new_number(x); + new_number(y); + set_number_half_from_subtraction(x, mp_maxx, mp_minx); + set_number_half_from_subtraction(y, mp_maxy, mp_miny); + number_add(x, mp_minx); + number_add(y, mp_miny); + mp_pair_value(mp, &x, &y); + } else { + mp_bad_unary(mp, mp_center_of_operation); + } + break; + case mp_center_of_mass_operation: + if (mp->cur_exp.type == mp_pair_type) { + /* keep the pair */ + } else if (mp->cur_exp.type == mp_path_type) { + /* no overflow detection here .. todo: make this a function call */ + mp_knot p = cur_exp_knot; + int l = 0; + mp_number x, y; + new_number(x); + new_number(y); + do { + ++l; + p = mp_next_knot(p); + number_add(x, p->x_coord); + number_add(y, p->y_coord); + } while (p != cur_exp_knot); + number_divide_int(x, l); + number_divide_int(y, l); + mp_pair_value(mp, &x, &y); + free_number(x); + free_number(y); + } else { + mp_bad_unary(mp, mp_center_of_mass_operation); + } + break; + case mp_corners_operation: + if (! mp_get_cur_bbox(mp)) { + mp_bad_unary(mp, mp_corners_operation); + } else { + mp_knot ll = mp_simple_knot(mp, &mp_minx, &mp_miny); + mp_knot lr = mp_simple_knot(mp, &mp_maxx, &mp_miny); + mp_knot ur = mp_simple_knot(mp, &mp_maxx, &mp_maxy); + mp_knot ul = mp_simple_knot(mp, &mp_minx, &mp_maxy); + mp_prev_knot(lr) = ll; + mp_next_knot(ll) = lr; + mp_prev_knot(ur) = lr; + mp_next_knot(lr) = ur; + mp_prev_knot(ul) = ur; + mp_next_knot(ur) = ul; + mp_prev_knot(ll) = ul; + mp_next_knot(ul) = ll; + mp->cur_exp.type = mp_path_type; + mp_set_cur_exp_knot(mp, ll); + } + break; + case mp_x_range_operation: + if (mp_get_cur_xbox(mp)) { + mp_pair_value(mp, &mp_minx, &mp_maxx); + } else { + mp_bad_unary(mp, mp_x_range_operation); + } + break; + case mp_y_range_operation: + if (mp_get_cur_ybox(mp)) { + mp_pair_value(mp, &mp_miny, &mp_maxy); + } else { + mp_bad_unary(mp, mp_y_range_operation); + } + break; + case mp_delta_point_operation: + case mp_delta_precontrol_operation: + case mp_delta_postcontrol_operation: + case mp_delta_direction_operation: + if (mp->cur_exp.type == mp_known_type) { + mp_set_cur_exp_value_number(mp, &cur_exp_value_number); + if (mp->loop_ptr && mp->loop_ptr->point != NULL) { + mp_knot p = mp->loop_ptr->point; + int n = round_unscaled(cur_exp_value_number); + if (n > 0) { + while (n--) { + p = mp_next_knot(p); + } + } else if (n < 0) { + while (n++) { + p = mp_prev_knot(p); + } + } + push_of_path_result(mp, c - mp_delta_point_operation, p); + } + } else { + mp_bad_unary(mp, c); + } + break; + case mp_read_from_operation: + case mp_close_from_operation: + if (mp->cur_exp.type != mp_string_type) { + mp_bad_unary(mp, c); + } else { + mp_do_read_or_close(mp, c); + } + break; + } + check_arith(); +} + +@ The |nice_pair| function returns |true| if both components of a pair are known. + +@<Declare unary action procedures@>= +static int mp_nice_pair (MP mp, mp_node p, int t) +{ + (void) mp; + if (t == mp_pair_type) { + p = mp_get_value_node(p); + if (mp_type(mp_x_part(p)) == mp_known_type && mp_type(mp_y_part(p)) == mp_known_type) + return 1; + } + return 0; +} + +@ The |nice_color_or_pair| function is analogous except that it also accepts +fully known colors. + +@<Declare unary action procedures@>= +static int mp_nice_color_or_pair (MP mp, mp_node p, int t) +{ + mp_node q; + (void) mp; + switch (t) { + case mp_pair_type: + q = mp_get_value_node(p); + if (mp_type(mp_x_part(q)) == mp_known_type + && mp_type(mp_y_part(q)) == mp_known_type) + return 1; + break; + case mp_color_type: + q = mp_get_value_node(p); + if (mp_type(mp_red_part (q)) == mp_known_type + && mp_type(mp_green_part(q)) == mp_known_type + && mp_type(mp_blue_part (q)) == mp_known_type) + return 1; + break; + case mp_cmykcolor_type: + q = mp_get_value_node(p); + if (mp_type(mp_cyan_part (q)) == mp_known_type + && mp_type(mp_magenta_part(q)) == mp_known_type + && mp_type(mp_yellow_part (q)) == mp_known_type + && mp_type(mp_black_part (q)) == mp_known_type) + return 1; + break; + } + return 0; +} + +@ @<Declare unary action...@>= +static void mp_print_known_or_unknown_type (MP mp, int t, mp_node v) +{ + mp_print_chr(mp, '('); + if (t > mp_known_type) { + mp_print_str(mp, "unknown numeric"); + } else { + switch (t) { + case mp_pair_type: + case mp_color_type: + case mp_cmykcolor_type: + if (! mp_nice_color_or_pair (mp, v, t)) { + mp_print_str(mp, "unknown "); + } + break; + } + mp_print_type(mp, t); + } + mp_print_chr(mp, ')'); +} + +@ @<Declare unary action...@>= +static void mp_bad_unary (MP mp, int c) +{ + char msg[256]; + mp_string sname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_op(mp, c); + mp_print_known_or_unknown_type(mp, mp->cur_exp.type, cur_exp_node); + sname = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "Not implemented: %s", mp_str(mp, sname)); + delete_str_ref(sname); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + msg, + "I'm afraid I don't know how to apply that operation to that particular type.\n" + "Continue, and I'll simply return the argument (shown above) as the result of the\n" + "operation." + ); + @.Not implemented...@> + mp_get_x_next(mp); +} + +@ Negation is easy except when the current expression is of type |independent|, +or when it is a pair with one or more |independent| components. + +@<Declare unary action...@>= +static void mp_negate_dep_list (MP mp, mp_value_node p) +{ + (void) mp; + while (1) { + number_negate(mp_get_dep_value(p)); + if (mp_get_dep_info(p) == NULL) + return; + p = (mp_value_node) mp_link(p); + } +} + +@ 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). + +@<Declare unary action...@>= + +static void mp_negate_value(MP mp, mp_node r) +{ + if (mp_type(r) == mp_known_type) { + mp_set_value_number(r, mp_get_value_number(r)); /* to clear the rest */ + number_negate(mp_get_value_number(r)); + } else { + mp_negate_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) r)); + } +} + +static void negate_cur_expr (MP mp) +{ + switch (mp->cur_exp.type) { + case mp_color_type: + case mp_cmykcolor_type: + case mp_pair_type: + case mp_independent_type: + { + mp_node q = cur_exp_node; + mp_make_exp_copy(mp, q); + if (mp->cur_exp.type == mp_dependent_type) { + mp_negate_dep_list(mp, (mp_value_node) mp_get_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| */ + mp_node p = mp_get_value_node(cur_exp_node); + // mp_node r; /* for list manipulation */ + switch (mp->cur_exp.type) { + case mp_pair_type: + mp_negate_value(mp, mp_x_part(p)); + mp_negate_value(mp, mp_y_part(p)); + break; + case mp_color_type: + mp_negate_value(mp, mp_red_part(p)); + mp_negate_value(mp, mp_green_part(p)); + mp_negate_value(mp, mp_blue_part(p)); + break; + case mp_cmykcolor_type: + mp_negate_value(mp, mp_cyan_part(p)); + mp_negate_value(mp, mp_magenta_part(p)); + mp_negate_value(mp, mp_yellow_part(p)); + mp_negate_value(mp, mp_black_part(p)); + break; + default: + break; + } + } + /* if |cur_type=mp_known| then |cur_exp=0| */ + mp_recycle_value(mp, q); + mp_free_value_node(mp, q); + } + break; + case mp_dependent_type: + case mp_proto_dependent_type: + mp_negate_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) cur_exp_node)); + break; + case mp_known_type: + if (is_number(cur_exp_value_number)) { + number_negate(cur_exp_value_number); + } + break; + default: + mp_bad_unary(mp, mp_minus_operation); + 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) { + mp_set_cur_exp_knot(mp, mp_pair_to_knot(mp)); + mp->cur_exp.type = mp_path_type; +} + +@ @<Declarations@>= +static void mp_bad_color_part (MP mp, int c); + +@ @c +static void mp_bad_color_part (MP mp, int c) +{ + mp_node p; /* the big node */ + mp_value new_expr; + char msg[256]; + int selector; + mp_string sname; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + p = mp_link(mp_edge_list(cur_exp_node)); + mp_disp_err(mp, NULL); + selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_op(mp, c); + sname = mp_make_string(mp); + mp->selector = selector; + @.Wrong picture color model...@> + switch (mp_color_model(p)) { + case mp_grey_model: mp_snprintf(msg, 256, "Wrong picture color model: %s of grey object", mp_str(mp, sname)); break; + case mp_cmyk_model: mp_snprintf(msg, 256, "Wrong picture color model: %s of cmyk object", mp_str(mp, sname)); break; + case mp_rgb_model: mp_snprintf(msg, 256, "Wrong picture color model: %s of rgb object", mp_str(mp, sname)); break; + case mp_no_model: mp_snprintf(msg, 256, "Wrong picture color model: %s of marking object", mp_str(mp, sname)); break; + default: mp_snprintf(msg, 256, "Wrong picture color model: %s of defaulted object", mp_str(mp, sname)); break; + } + delete_str_ref(sname); + mp_error( + mp, + msg, + "You can only ask for the redpart, greenpart, bluepart of a rgb object, the\n" + "cyanpart, magentapart, yellowpart or blackpart of a cmyk object, or the greypart\n" + "of a grey object. No mixing and matching, please." + ); + if (c == mp_black_part_operation) { + number_clone(new_expr.data.n, unity_t); + } else { + set_number_to_zero(new_expr.data.n); + } + 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, int c) +{ + mp_node p = mp_get_value_node(cur_exp_node); /* the big node */ + mp_set_value_node(mp->temp_val, p); + mp_type(mp->temp_val) = mp->cur_exp.type; + mp_link(p) = mp->temp_val; + mp_free_value_node(mp, cur_exp_node); + switch (c) { + case mp_x_part_operation: + if (mp->cur_exp.type == mp_pair_type) { + mp_make_exp_copy(mp, mp_x_part(p)); + } else { + mp_make_exp_copy(mp, mp_tx_part(p)); + } + break; + case mp_y_part_operation: + if (mp->cur_exp.type == mp_pair_type) { + mp_make_exp_copy(mp, mp_y_part(p)); + } else { + mp_make_exp_copy(mp, mp_ty_part(p)); + } + break; + case mp_xx_part_operation: + mp_make_exp_copy(mp, mp_xx_part(p)); + break; + case mp_xy_part_operation: + mp_make_exp_copy(mp, mp_xy_part(p)); + break; + case mp_yx_part_operation: + mp_make_exp_copy(mp, mp_yx_part(p)); + break; + case mp_yy_part_operation: + mp_make_exp_copy(mp, mp_yy_part(p)); + break; + case mp_red_part_operation: + mp_make_exp_copy(mp, mp_red_part(p)); + break; + case mp_green_part_operation: + mp_make_exp_copy(mp, mp_green_part(p)); + break; + case mp_blue_part_operation: + mp_make_exp_copy(mp, mp_blue_part(p)); + break; + case mp_cyan_part_operation: + mp_make_exp_copy(mp, mp_cyan_part(p)); + break; + case mp_magenta_part_operation: + mp_make_exp_copy(mp, mp_magenta_part(p)); + break; + case mp_yellow_part_operation: + mp_make_exp_copy(mp, mp_yellow_part(p)); + break; + case mp_black_part_operation: + mp_make_exp_copy(mp, mp_black_part(p)); + break; + case mp_grey_part_operation: + mp_make_exp_copy(mp, mp_grey_part(p)); + break; + } + mp_recycle_value(mp, mp->temp_val); +} + +@ @<Initialize table entries@>= +mp->temp_val = mp_new_value_node(mp); +mp_name_type(mp->temp_val) = mp_capsule_operation; + +@ @<Free table entries@>= +mp_free_value_node(mp, mp->temp_val); + +@ @<Declarations@>= +static mp_edge_header_node mp_scale_edges (MP mp, mp_number *se_sf, mp_edge_header_node se_pic); + +@ @<Declare unary action...@>= +static void mp_take_pict_part (MP mp, int c) +{ + mp_node p; /* first graphical object in |cur_exp| */ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + p = mp_link(mp_edge_list(cur_exp_node)); + if (p != NULL) { + switch (c) { + case mp_x_part_operation: + case mp_y_part_operation: + case mp_xx_part_operation: + case mp_xy_part_operation: + case mp_yx_part_operation: + case mp_yy_part_operation: + goto NOT_FOUND; + case mp_red_part_operation: + case mp_green_part_operation: + case mp_blue_part_operation: + if (mp_has_color(p)) { + switch (c) { + case mp_red_part_operation: + number_clone(new_expr.data.n, ((mp_shape_node) p)->red); + break; + case mp_green_part_operation: + number_clone(new_expr.data.n, ((mp_shape_node) p)->green); + break; + case mp_blue_part_operation: + number_clone(new_expr.data.n, ((mp_shape_node) p)->blue); + break; + } + mp_flush_cur_exp(mp, new_expr); + } else + goto NOT_FOUND; + break; + case mp_cyan_part_operation: + case mp_magenta_part_operation: + case mp_yellow_part_operation: + case mp_black_part_operation: + if (mp_has_color(p)) { + if (mp_color_model(p) == mp_uninitialized_model && c == mp_black_part_operation) { + set_number_to_unity(new_expr.data.n); + } else { + switch (c) { + case mp_cyan_part_operation: + number_clone(new_expr.data.n, ((mp_shape_node) p)->cyan); + break; + case mp_magenta_part_operation: + number_clone(new_expr.data.n, ((mp_shape_node) p)->magenta); + break; + case mp_yellow_part_operation: + number_clone(new_expr.data.n, ((mp_shape_node) p)->yellow); + break; + case mp_black_part_operation: + number_clone(new_expr.data.n, ((mp_shape_node) p)->black); + break; + } + } + mp_flush_cur_exp(mp, new_expr); + } else + goto NOT_FOUND; + break; + case mp_grey_part_operation: + if (mp_has_color(p)) { + number_clone(new_expr.data.n, ((mp_shape_node) p)->grey); + mp_flush_cur_exp(mp, new_expr); + } else + goto NOT_FOUND; + break; + case mp_color_model_operation: + if (mp_has_color(p)) { + if (mp_color_model(p) == mp_uninitialized_model) { + /* could use the else branch with int variant */ + number_clone(new_expr.data.n, internal_value(mp_default_color_model_internal)); + } else { + number_clone(new_expr.data.n, unity_t); + number_multiply_int(new_expr.data.n, mp_color_model(p)); + } + mp_flush_cur_exp(mp, new_expr); + } else + goto NOT_FOUND; + break; + case mp_prescript_part_operation: + if (! mp_has_script(p)) { + goto NOT_FOUND; + } else { + if (mp_pre_script(p)) { + new_expr.data.str = mp_pre_script(p); + add_str_ref(new_expr.data.str); + } else { + new_expr.data.str = mp_rts(mp,""); + } + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_string_type; + }; + break; + case mp_postscript_part_operation: + if (! mp_has_script(p)) { + goto NOT_FOUND; + } else { + if (mp_post_script(p)) { + new_expr.data.str = mp_post_script(p); + add_str_ref(new_expr.data.str); + } else { + new_expr.data.str = mp_rts(mp,""); + } + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_string_type; + }; + break; + case mp_stacking_part_operation: + number_clone(new_expr.data.n, unity_t); + number_multiply_int(new_expr.data.n, mp_stacking(p)); + mp_flush_cur_exp(mp, new_expr); + break; + case mp_path_part_operation: + if (mp_is_stop(p)) { + mp_confusion(mp, "picture"); + } else { + new_expr.data.node = NULL; + switch (mp_type(p)) { + case mp_fill_node_type: + case mp_stroked_node_type: + new_expr.data.p = mp_copy_path(mp, mp_path_ptr((mp_shape_node) p)); + break; + case mp_start_clip_node_type: + case mp_start_group_node_type: + case mp_start_bounds_node_type: + new_expr.data.p = mp_copy_path(mp, mp_path_ptr((mp_start_node) p)); + break; + default: + break; + } + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_path_type; + } + break; + case mp_pen_part_operation: + if (! mp_has_pen(p)) { + goto NOT_FOUND; + } else { + switch (mp_type(p)) { + case mp_fill_node_type: + case mp_stroked_node_type: + if (mp_pen_ptr((mp_shape_node) p) == NULL) { + goto NOT_FOUND; + } else { + new_expr.data.p = mp_copy_pen(mp, mp_pen_ptr((mp_shape_node) p)); + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_pen_type((mp_shape_node) p) ? mp_nep_type : mp_pen_type ; + } + break; + default: + break; + } + } + break; + case mp_dash_part_operation: + if (mp_type(p) != mp_stroked_node_type) { + goto NOT_FOUND; + } else if (mp_dash_ptr(p) == NULL) { + goto NOT_FOUND; + } else { + mp_add_edge_ref(mp, mp_dash_ptr(p)); + new_expr.data.node = (mp_node) mp_scale_edges(mp, + &(((mp_shape_node) p)->dashscale), (mp_edge_header_node) mp_dash_ptr(p)); + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_picture_type; + } + break; + } + return; + }; + NOT_FOUND: + /* Convert the current expression to a NULL value appropriate for |c| */ + switch (c) { + case mp_prescript_part_operation: + case mp_postscript_part_operation: + new_expr.data.str = mp_rts(mp,""); + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_string_type; + break; + case mp_path_part_operation: + new_expr.data.p = mp_new_knot(mp); + mp_flush_cur_exp(mp, new_expr); + mp_left_type(cur_exp_knot) = mp_endpoint_knot; + mp_right_type(cur_exp_knot) = mp_endpoint_knot; + mp_prev_knot(cur_exp_knot) = cur_exp_knot; + mp_next_knot(cur_exp_knot) = cur_exp_knot; + set_number_to_zero(cur_exp_knot->x_coord); + set_number_to_zero(cur_exp_knot->y_coord); + mp_originator(cur_exp_knot) = mp_metapost_user; + mp_knotstate(cur_exp_knot) = mp_regular_knot; + mp->cur_exp.type = mp_path_type; + break; + case mp_pen_part_operation: + new_expr.data.p = mp_get_pen_circle(mp, &zero_t); + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_pen_type; /* todo: mp_nep_type */ + break; + case mp_dash_part_operation: + new_expr.data.node = (mp_node) mp_get_edge_header_node(mp); + mp_flush_cur_exp(mp, new_expr); + mp_init_edges(mp, (mp_edge_header_node) cur_exp_node); + mp->cur_exp.type = mp_picture_type; + break; + default: + set_number_to_zero(new_expr.data.n); + mp_flush_cur_exp(mp, new_expr); + break; + } +} + +@ This one is stripped because it only handles |ASCII|. Watch out, the |ASCII| +operator only looks at the first character and then just interprets the character +as byte. One can implement a \UTF\ interpreter in \LUA. + +@<Declare unary action...@>= +static void mp_str_to_num (MP mp) +{ + /* converts a string to a number */ + int n; /* accumulator */ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + if (cur_exp_str->len == 0) { + n = -1; + } else { + n = cur_exp_str->str[0]; + } + number_clone(new_expr.data.n, unity_t); + number_multiply_int(new_expr.data.n, n); + mp_flush_cur_exp(mp, new_expr); +} + +@ This computes the length of the current path or picture. The only benefit from +not using the numbers but a temporary |int| instead is .5K smaller which is due +to less interfacing. But it also demonstrates that on the one hand the number +system indirectness adds quite some bytes but on the other hand todays compilers +do a pretty good job at optimizing (for performance). Which of course doesn't +mean that scaled outperforms double manyfold while decimal is always way slower. + +@<Declarations@>= +static void mp_path_length (MP mp, mp_number *n); + +@ @<Declare unary action...@>= +static void mp_path_length (MP mp, mp_number *n) +{ + mp_knot p = cur_exp_knot; + int l = mp_left_type(p) == mp_endpoint_knot ? -1 : 0; + do { + p = mp_next_knot(p); + ++l; + } while (p != cur_exp_knot); + set_number_from_int(*n, l); +} + +static void mp_picture_length (MP mp, mp_number *n) +{ + /* counts interior components in picture |cur_exp| */ + mp_node p = mp_link(mp_edge_list(cur_exp_node)); + int l = 0; + if (p != NULL) { + if (mp_is_start_or_stop(p) && mp_skip_1component(mp, p) == NULL) { + p = mp_link(p); + } + while (p != NULL) { + if (! mp_is_start_or_stop(p)) { + p = mp_link(p); + } else if (! mp_is_stop(p)) { + p = mp_skip_1component(mp, p); + } else { + break; + } + ++l; + } + } + set_number_from_int(*n, l); +} + +@ The function |an_angle| returns the value of the |angle| primitive, or $0$ if +the argument is |origin|. + +@<Declare unary action...@>= +static void mp_an_angle (MP mp, mp_number *ret, mp_number *xpar, mp_number *ypar) +{ + set_number_to_zero(*ret); + if (! (number_zero(*xpar) && number_zero(*ypar))) { + n_arg(*ret, *xpar, *ypar); + } +} + +@ 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)/16) + +@<Declare unary action...@>= +static void mp_bezier_slope (MP mp, + mp_number *ret, mp_number *AX, mp_number *AY, mp_number *BX, + mp_number *BY, mp_number *CX, mp_number *CY, mp_number *DX, + mp_number *DY +); + +@ @c +static void mp_bezier_slope (MP mp, + mp_number *ret, mp_number *AX, mp_number *AY, mp_number *BX, + mp_number *BY, mp_number *CX, mp_number *CY, mp_number *DX, + mp_number *DY +) +{ + double a, b, c; + mp_number deltax, deltay; + mp_number xi, xo, xm; + double res = 0.0; + double ax = number_to_double(*AX); + double ay = number_to_double(*AY); + double bx = number_to_double(*BX); + double by = number_to_double(*BY); + double cx = number_to_double(*CX); + double cy = number_to_double(*CY); + double dx = number_to_double(*DX); + double dy = number_to_double(*DY); + new_number(deltax); + new_number(deltay); + set_number_from_subtraction(deltax, *BX, *AX); + set_number_from_subtraction(deltay, *BY, *AY); + if (number_zero(deltax) && number_zero(deltay)) { + set_number_from_subtraction(deltax, *CX, *AX); + set_number_from_subtraction(deltay, *CY, *AY); + } + if (number_zero(deltax) && number_zero(deltay)) { + set_number_from_subtraction(deltax, *DX, *AX); + set_number_from_subtraction(deltay, *DY, *AY); + } + new_number(xi); + new_number(xm); + new_number(xo); + mp_an_angle(mp, &xi, &deltax, &deltay); + set_number_from_subtraction(deltax, *CX, *BX); + set_number_from_subtraction(deltay, *CY, *BY); + mp_an_angle(mp, &xm, &deltax, &deltay); /* !!! never used? */ + set_number_from_subtraction(deltax, *DX, *CX); + set_number_from_subtraction(deltay, *DY, *CY); + if (number_zero(deltax) && number_zero(deltay)) { + set_number_from_subtraction(deltax, *DX, *BX); + set_number_from_subtraction(deltay, *DY, *BY); + } + if (number_zero(deltax) && number_zero(deltay)) { + set_number_from_subtraction(deltax, *DX, *AX); + set_number_from_subtraction(deltay, *DY, *AY); + } + mp_an_angle(mp, &xo, &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.0) && (c == 0.0)) { + res = (b == 0.0 ? 0.0 : (mp_out(number_to_double(xo)) - mp_out(number_to_double(xi)))); + } else if ((a == 0.0) || (c == 0.0)) { + if ((mp_sign (b) == mp_sign (a)) || (mp_sign (b) == mp_sign (c))) { + res = mp_out(number_to_double(xo)) - mp_out(number_to_double(xi)); /* ? */ + if (res < -180.0) { + res += 360.0; + } else if (res > 180.0) { + res -= 360.0; + } + } else { + res = mp_out(number_to_double(xo)) - mp_out(number_to_double(xi)); /* ? */ + } + } else if ((mp_sign (a) * mp_sign (c)) < 0.0) { + res = mp_out(number_to_double(xo)) - mp_out(number_to_double(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(number_to_double(xo)) - mp_out(number_to_double(xi)); + if (res < -180.0) { + res += 360.0; + } else if (res > 180.0) { + res -= 360.0; + } + } else if ((b * b) == (4.0 * a * c)) { + res = (double) bezier_error; + } else if ((b * b) < (4.0 * a * c)) { + res = mp_out(number_to_double(xo)) - mp_out(number_to_double(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(number_to_double(xo)) - mp_out(number_to_double(xi)); + if (res < -180.0) { + res += 360.0; + } else if (res > 180.0) { + res -= 360.0; + } + } + free_number(deltax); + free_number(deltay); + free_number(xi); + free_number(xo); + free_number(xm); + set_number_from_double(*ret, res); + convert_scaled_to_angle(*ret); +} + +@d p_nextnext mp_next_knot(mp_next_knot(p)) +@d p_next mp_next_knot(p) + +@<Declare unary action...@>= +static void mp_turn_cycles (MP mp, mp_number *turns, mp_knot c) +{ + int selector; /* saved |selector| setting */ + mp_angle res, ang; /* the angles of intermediate results */ + mp_knot p; /* for running around the path */ + mp_number xp, yp; /* coordinates of next point */ + mp_number x, y; /* helper coordinates */ + mp_number arg1, arg2; + mp_angle in_angle, out_angle; /* helper angles */ + mp_angle seven_twenty_deg_t; + set_number_to_zero(*turns); + new_number(arg1); + new_number(arg2); + new_number(xp); + new_number(yp); + new_number(x); + new_number(y); + new_angle(in_angle); + new_angle(out_angle); + new_angle(ang); + new_angle(res); + new_angle(seven_twenty_deg_t); + number_clone(seven_twenty_deg_t, three_sixty_deg_t); + number_double(seven_twenty_deg_t); + p = c; + selector = mp->selector; + mp->selector = mp_term_only_selector; + if (number_greater(internal_value(mp_tracing_commands_internal), unity_t)) { + mp_begin_diagnostic(mp); + mp_print_nl(mp, ""); + mp_end_diagnostic(mp, 0); + } + do { + number_clone(xp, p_next->x_coord); + number_clone(yp, p_next->y_coord); + mp_bezier_slope(mp, &ang, &(p->x_coord), &(p->y_coord), &(p->right_x), &(p->right_y), &(p_next->left_x), &(p_next->left_y), &xp, &yp); + if (number_greater(ang, seven_twenty_deg_t)) { + mp_error(mp, "Strange path", NULL); + mp->selector = selector; + set_number_to_zero(*turns); + goto DONE; + } + number_add(res, ang); + if (number_greater(res, one_eighty_deg_t)) { + number_subtract(res, three_sixty_deg_t); + number_add(*turns, unity_t); + } + if (number_lessequal(res, negative_one_eighty_deg_t)) { + number_add(res, three_sixty_deg_t); + number_subtract(*turns, unity_t); + } + /* incoming angle at next point */ + number_clone(x, p_next->left_x); + number_clone(y, p_next->left_y); + if (number_equal(xp, x) && number_equal(yp, y)) { + number_clone(x, p->right_x); + number_clone(y, p->right_y); + } + if (number_equal(xp, x) && number_equal(yp, y)) { + number_clone(x, p->x_coord); + number_clone(y, p->y_coord); + } + set_number_from_subtraction(arg1, xp, x); + set_number_from_subtraction(arg2, yp, y); + mp_an_angle(mp, &in_angle, &arg1, &arg2); + /* outgoing angle at next point */ + number_clone(x, p_next->right_x); + number_clone(y, p_next->right_y); + if (number_equal(xp, x) && number_equal(yp, y)) { + number_clone(x, p_nextnext->left_x); + number_clone(y, p_nextnext->left_y); + } + if (number_equal(xp, x) && number_equal(yp, y)) { + number_clone(x, p_nextnext->x_coord); + number_clone(y, p_nextnext->y_coord); + } + set_number_from_subtraction(arg1, x, xp); + set_number_from_subtraction(arg2, y, yp); + mp_an_angle(mp, &out_angle, &arg1, &arg2); + set_number_from_subtraction(ang, out_angle, in_angle); + mp_reduce_angle(mp, &ang); + if (number_nonzero(ang)) { + number_add(res, ang); + if (number_greaterequal(res, one_eighty_deg_t)) { + number_subtract(res, three_sixty_deg_t); + number_add(*turns, unity_t); + } + if (number_lessequal(res, negative_one_eighty_deg_t)) { + number_add(res, three_sixty_deg_t); + number_subtract(*turns, unity_t); + } + } + p = mp_next_knot(p); + } while (p != c); + mp->selector = selector; + DONE: + free_number(xp); + free_number(yp); + free_number(x); + free_number(y); + free_number(seven_twenty_deg_t); + free_number(in_angle); + free_number(out_angle); + free_number(ang); + free_number(res); + free_number(arg1); + free_number(arg2); +} + +@ @<Declare unary action...@>= +static void mp_turn_cycles_wrapper (MP mp, mp_number *ret, mp_knot c) +{ + if (mp_next_knot(c) == c) { + /* one-knot paths always have a turning number of 1 */ + set_number_to_unity(*ret); + } else { + mp_turn_cycles (mp, ret, c); + } +} + +@ @<Declare unary action procedures@>= +static int mp_test_known (MP mp, int c) +{ + int b = mp_false_operation; /* is the current expression known? */ + switch (mp->cur_exp.type) { + case mp_vacuous_type: + case mp_boolean_type: + case mp_string_type: + case mp_pen_type: + case mp_nep_type: + case mp_path_type: + case mp_picture_type: + case mp_known_type: + b = mp_true_operation; + break; + case mp_transform_type: + { + mp_node p = mp_get_value_node(cur_exp_node); + if ( (mp_type(mp_tx_part(p)) == mp_known_type) && + (mp_type(mp_ty_part(p)) == mp_known_type) && + (mp_type(mp_xx_part(p)) == mp_known_type) && + (mp_type(mp_xy_part(p)) == mp_known_type) && + (mp_type(mp_yx_part(p)) == mp_known_type) && + (mp_type(mp_yy_part(p)) == mp_known_type) ) { + b = mp_true_operation; + } + } + break; + case mp_color_type: + { + mp_node p = mp_get_value_node(cur_exp_node); + if ( (mp_type(mp_red_part(p)) == mp_known_type) && + (mp_type(mp_green_part(p)) == mp_known_type) && + (mp_type(mp_blue_part(p)) == mp_known_type) ) { + b = mp_true_operation; + } + } + break; + case mp_cmykcolor_type: + { + mp_node p = mp_get_value_node(cur_exp_node); + if ( (mp_type(mp_cyan_part(p)) == mp_known_type) && + (mp_type(mp_magenta_part(p)) == mp_known_type) && + (mp_type(mp_yellow_part(p)) == mp_known_type) && + (mp_type(mp_black_part(p)) == mp_known_type) ) { + b = mp_true_operation; + } + } + break; + case mp_pair_type: + { + mp_node p = mp_get_value_node(cur_exp_node); + if ( (mp_type(mp_x_part(p)) == mp_known_type) && + (mp_type(mp_y_part(p)) == mp_known_type) ) { + b = mp_true_operation; + } + } + break; + default: + break; + } + if (c == mp_known_operation) { + return b; + } else { + return b == mp_true_operation ? mp_false_operation : mp_true_operation; + } +} + +@ The |pair_value| routine changes the current expression to a given ordered pair +of values. + +@<Declarations@>= +static void mp_pair_value (MP mp, mp_number *x, mp_number *y); + +@ @<Declare unary action procedures@>= +static void mp_pair_value (MP mp, mp_number *x, mp_number *y) +{ + mp_node p; /* a pair node */ + mp_value new_expr; + mp_number x1, y1; + new_number_clone(x1, *x); + new_number_clone(y1, *y); + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + p = mp_new_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_operation; + mp_init_pair_node(mp, p); + p = mp_get_value_node(p); + mp_type(mp_x_part(p)) = mp_known_type; + mp_set_value_number(mp_x_part(p), x1); + mp_type(mp_y_part(p)) = mp_known_type; + mp_set_value_number(mp_y_part(p), y1); + free_number(x1); + free_number(y1); +} + +@ 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 int mp_get_cur_bbox (MP mp) +{ + switch (mp->cur_exp.type) { + case mp_picture_type: + { + mp_edge_header_node p = (mp_edge_header_node) cur_exp_node; + mp_set_bbox(mp, p, 1); + if (number_greater(p->minx, p->maxx)) { + set_number_to_zero(mp_minx); + set_number_to_zero(mp_maxx); + set_number_to_zero(mp_miny); + set_number_to_zero(mp_maxy); + } else { + number_clone(mp_minx, p->minx); + number_clone(mp_maxx, p->maxx); + number_clone(mp_miny, p->miny); + number_clone(mp_maxy, p->maxy); + } + } + break; + case mp_path_type: + mp_path_bbox(mp, cur_exp_knot); + break; + case mp_pen_type: + case mp_nep_type: + mp_pen_bbox(mp, cur_exp_knot); + break; + default: + return 0; + } + return 1; +} + +static int mp_get_cur_xbox (MP mp) +{ + if (mp->cur_exp.type == mp_path_type) { + mp_path_xbox(mp, cur_exp_knot); + return 1; + } else { + return mp_get_cur_bbox(mp); + } +} + +static int mp_get_cur_ybox (MP mp) +{ + if (mp->cur_exp.type == mp_path_type) { + mp_path_ybox(mp, cur_exp_knot); + return 1; + } else { + return mp_get_cur_bbox(mp); + } +} + +@ 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, int c) +{ + int n = mp->read_files; + int n0 = mp->read_files; + char *fn = mp_strdup(mp_str(mp, cur_exp_str)); + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + /* + 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|. Free slots in + the |rd_file| and |rd_fname| arrays are marked with NULL's in |rd_fname|. + */ + while (mp_strcmp(fn, mp->rd_fname[n]) != 0) { + if (n > 0) { + --n; + } else if (c == mp_close_from_operation) { + goto CLOSE_FILE; + } else { + if (n0 == mp->read_files) { + if (mp->read_files < mp->max_read_files) { + ++mp->read_files; + } else { + void **rd_file; + char **rd_fname; + int l; + l = mp->max_read_files + (mp->max_read_files / 4); + rd_file = mp_memory_allocate((size_t) (l + 1) * sizeof(void *)); + rd_fname = mp_memory_allocate((size_t) (l + 1) * sizeof(char *)); + for (int 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; + } + } + mp_memory_free(mp->rd_file); + mp_memory_free(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 == mp_close_from_operation) { + (mp->close_file) (mp, mp->rd_file[n]); + goto 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 */ + mp_memory_free(mp->rd_fname[n]); + mp->rd_fname[n] = NULL; + if (n == mp->read_files - 1) { + mp->read_files = n; + } + if (c == mp_close_from_operation) { + goto CLOSE_FILE; + } + // new_expr.data.str = mp->eof_line; + new_expr.data.str = mp->eof_file; + add_str_ref(new_expr.data.str); + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_string_type; + return; + CLOSE_FILE: + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_vacuous_type; + return; + FOUND: + mp_flush_cur_exp(mp, new_expr); + mp_finish_read(mp); +} + +@ 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...@>= +mp_string eof_line; +mp_string eof_file; + +@ @<Set init...@>= +mp->eof_line = mp_rtsl (mp, "\0", 1); +mp->eof_line->refs = MAX_STR_REF; +mp->eof_file = mp_rtsl (mp, "%", 1); +mp->eof_file->refs = MAX_STR_REF; + +@ Finally, we have the operations that combine a capsule~|p| with the current +expression. + +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. + +@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 */ + if (old_p != NULL) { + mp_recycle_value(mp, old_p); + mp_free_value_node(mp, old_p); + } + if (old_exp != NULL) { + mp_recycle_value(mp, old_exp); + mp_free_value_node(mp, old_exp); + } +} + +static void mp_do_binary (MP mp, mp_node p, int c) +{ + mp_node old_p, old_exp; /* capsules to recycle */ + mp_value new_expr; + check_arith(); + if (number_greater(internal_value(mp_tracing_commands_internal), two_t)) { + /* Trace the current binary operation */ + mp_begin_diagnostic(mp); + mp_print_nl(mp, "{("); + /* show the operand, but not verbosely */ + mp_print_exp(mp, p, 0); + mp_print_chr(mp, ')'); + mp_print_op(mp, (int) c); + mp_print_chr(mp, '('); + mp_print_exp(mp, NULL, 0); + mp_print_str(mp, ")}"); + mp_end_diagnostic(mp, 0); + } + /* + Sidestep |independent| cases in capsule |p|. A big node is considered to be + \quote {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. + */ + 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_type: + old_p = MP_VOID; + break; + default: + old_p = NULL; + break; + } + if (old_p != NULL) { + mp_node 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_type: + 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); + } + switch (c) { + case mp_plus_operation: + case mp_minus_operation: + /* 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, c); + } else { + if ((mp->cur_exp.type > mp_pair_type) && (mp_type(p) > mp_pair_type)) { + mp_add_or_subtract(mp, p, NULL, c); + } else if (mp->cur_exp.type != mp_type(p)) { + /* + We catch a mismatch, so we can handle intermediates (assuming a flexible withcolor); + if we would go double only live would be easier ... I might eventually make a more + generic color type. + */ + /* + if (mp->cur_exp.type == mp_color_type && mp_type(p) == mp_cmykcolor_type) { + mp_node q = mp_get_value_node(p); + mp_node r = mp_get_value_node(cur_exp_node); + number_negate((mp_cyan_part(q))->data.n); + number_negate((mp_magenta_part(q))->data.n); + number_negate((mp_yellow_part(q))->data.n); + number_add((mp_cyan_part(q))->data.n, unity_t); + number_add((mp_magenta_part(q))->data.n, unity_t); + number_add((mp_yellow_part(q))->data.n, unity_t); + mp_add_or_subtract(mp, mp_cyan_part(q), mp_red_part(r), c); + mp_add_or_subtract(mp, mp_magenta_part(q), mp_green_part(r), c); + mp_add_or_subtract(mp, mp_yellow_part(q), mp_blue_part(r), c); + } else if (mp->cur_exp.type == mp_cmykcolor_type && mp_type(p) == mp_color_type) { + mp_node q = mp_get_value_node(p); + mp_node r = mp_get_value_node(cur_exp_node); + number_negate((mp_red_part(q))->data.n); + number_negate((mp_green_part(q))->data.n); + number_negate((mp_blue_part(q))->data.n); + number_add((mp_red_part(q))->data.n, unity_t); + number_add((mp_green_part(q))->data.n, unity_t); + number_add((mp_blue_part(q))->data.n, unity_t); + mp_add_or_subtract(mp, mp_red_part(q), mp_cyan_part(r), c); + mp_add_or_subtract(mp, mp_green_part(q), mp_magenta_part(r), c); + mp_add_or_subtract(mp, mp_blue_part(q), mp_yellow_part(r), c); + } else { + */ + mp_bad_binary(mp, p, c); + /* } */ + } else { + mp_node q = mp_get_value_node(p); + mp_node r = mp_get_value_node(cur_exp_node); + switch (mp->cur_exp.type) { + case mp_pair_type: + mp_add_or_subtract(mp, mp_x_part(q), mp_x_part(r), c); + mp_add_or_subtract(mp, mp_y_part(q), mp_y_part(r), c); + break; + case mp_color_type: + mp_add_or_subtract(mp, mp_red_part(q), mp_red_part(r), c); + mp_add_or_subtract(mp, mp_green_part(q), mp_green_part(r), c); + mp_add_or_subtract(mp, mp_blue_part(q), mp_blue_part(r), c); + break; + case mp_cmykcolor_type: + mp_add_or_subtract(mp, mp_cyan_part(q), mp_cyan_part(r), c); + mp_add_or_subtract(mp, mp_magenta_part(q), mp_magenta_part(r), c); + mp_add_or_subtract(mp, mp_yellow_part(q), mp_yellow_part(r), c); + mp_add_or_subtract(mp, mp_black_part(q), mp_black_part(r), c); + break; + case mp_transform_type: + mp_add_or_subtract(mp, mp_tx_part(q), mp_tx_part(r), c); + mp_add_or_subtract(mp, mp_ty_part(q), mp_ty_part(r), c); + mp_add_or_subtract(mp, mp_xx_part(q), mp_xx_part(r), c); + mp_add_or_subtract(mp, mp_xy_part(q), mp_xy_part(r), c); + mp_add_or_subtract(mp, mp_yx_part(q), mp_yx_part(r), c); + mp_add_or_subtract(mp, mp_yy_part(q), mp_yy_part(r), c); + break; + default: + break; + } + } + } + break; + case mp_less_than_operation: + case mp_less_or_equal_operation: + case mp_greater_than_operation: + case mp_greater_or_equal_operation: + case mp_equal_operation: + case mp_unequal_operation: + check_arith(); + /* at this point |arith_error| should be |false|? */ + if ((mp->cur_exp.type > mp_pair_type) && (mp_type(p) > mp_pair_type)) { + /* |cur_exp:=(p)-cur_exp| */ + mp_add_or_subtract(mp, p, NULL, mp_minus_operation); + } else if (mp->cur_exp.type != mp_type(p)) { + mp_bad_binary(mp, p, (int) c); + goto DONE; + } else { + /* + Reduce comparison of big nodes to comparison of scalars. In the + following, the |while| loops exist just so that |break| can be + used, each loop runs exactly once. + */ + switch (mp->cur_exp.type) { + case mp_string_type: + { + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + set_number_from_scaled(new_expr.data.n, mp_str_vs_str(mp, mp_get_value_str(p), cur_exp_str)); + mp_flush_cur_exp(mp, new_expr); + } + break; + case mp_unknown_string_type: + case mp_unknown_boolean_type: + { + /* + Check if unknowns have been equated. 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. + */ + mp_node q = mp_get_value_node(cur_exp_node); + while ((q != cur_exp_node) && (q != p)) { + q = mp_get_value_node(q); + } + if (q == p) { + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_set_cur_exp_node(mp, NULL); + mp_flush_cur_exp(mp, new_expr); + } + } + break; + case mp_pair_type: + { + int part_type = 0; + mp_node q = mp_get_value_node(p); + mp_node r = mp_get_value_node(cur_exp_node); + while (part_type == 0) { + mp_node rr = mp_x_part(r); + part_type = mp_x_part_operation; + mp_add_or_subtract(mp, mp_x_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_y_part(r); + part_type = mp_y_part_operation; + mp_add_or_subtract(mp, mp_y_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + } + mp_take_part(mp, part_type); + } + break; + case mp_color_type: + { + int part_type = 0; + mp_node q = mp_get_value_node(p); + mp_node r = mp_get_value_node(cur_exp_node); + while (part_type == 0) { + mp_node rr = mp_red_part(r); + part_type = mp_red_part_operation; + mp_add_or_subtract(mp, mp_red_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_green_part(r); + part_type = mp_green_part_operation; + mp_add_or_subtract(mp, mp_green_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_blue_part(r); + part_type = mp_blue_part_operation; + mp_add_or_subtract(mp, mp_blue_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + } + mp_take_part(mp, part_type); + } + break; + case mp_cmykcolor_type: + { + int part_type = 0; + mp_node q = mp_get_value_node(p); + mp_node r = mp_get_value_node(cur_exp_node); + while (part_type == 0) { + mp_node rr = mp_cyan_part(r); + part_type = mp_cyan_part_operation; + mp_add_or_subtract(mp, mp_cyan_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_magenta_part(r); + part_type = mp_magenta_part_operation; + mp_add_or_subtract(mp, mp_magenta_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_yellow_part(r); + part_type = mp_yellow_part_operation; + mp_add_or_subtract(mp, mp_yellow_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_black_part(r); + part_type = mp_black_part_operation; + mp_add_or_subtract(mp, mp_black_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + } + mp_take_part(mp, part_type); + } + break; + case mp_transform_type: + { + int part_type = 0; + mp_node q = mp_get_value_node(p); + mp_node r = mp_get_value_node(cur_exp_node); + while (part_type == 0) { + mp_node rr = mp_tx_part(r); + part_type = mp_x_part_operation; + mp_add_or_subtract(mp, mp_tx_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_ty_part(r); + part_type = mp_y_part_operation; + mp_add_or_subtract(mp, mp_ty_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_xx_part(r); + part_type = mp_xx_part_operation; + mp_add_or_subtract(mp, mp_xx_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_xy_part(r); + part_type = mp_xy_part_operation; + mp_add_or_subtract(mp, mp_xy_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_yx_part(r); + part_type = mp_yx_part_operation; + mp_add_or_subtract(mp, mp_yx_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + rr = mp_yy_part(r); + part_type = mp_yy_part_operation; + mp_add_or_subtract(mp, mp_yy_part(q), rr, mp_minus_operation); + if (mp_type(rr) != mp_known_type || ! number_zero(mp_get_value_number(rr))) { + break; + } + } + mp_take_part(mp, part_type); + } + break; + case mp_boolean_type: + { + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + set_number_from_boolean(new_expr.data.n, number_to_scaled(cur_exp_value_number) - number_to_scaled(mp_get_value_number(p))); + mp_flush_cur_exp(mp, new_expr); + } + break; + default: + mp_bad_binary(mp, p, (int) c); + goto DONE; + break; + } + } + /* Compare the current expression with zero */ + if (mp->cur_exp.type != mp_known_type) { + const char *hlp = NULL; + if (mp->cur_exp.type < mp_known_type) { + mp_disp_err(mp, p); + hlp = "The quantities shown above have not been equated."; + } else { + hlp = + "Oh dear. I can't decide if the expression above is positive, negative, or zero.\n" + "So this comparison test won't be 'true'."; + } + mp_disp_err(mp, NULL); + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + set_number_from_boolean(new_expr.data.n, mp_false_operation); + mp_back_error(mp, "Unknown relation will be considered false", hlp); + @.Unknown relation...@> + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else { + int b = 0; + switch (c) { + case mp_less_than_operation: + b = number_negative(cur_exp_value_number); + break; + case mp_less_or_equal_operation: + b = number_nonpositive(cur_exp_value_number); + break; + case mp_greater_than_operation: + b = number_positive(cur_exp_value_number); + break; + case mp_greater_or_equal_operation: + b = number_nonnegative(cur_exp_value_number); + break; + case mp_equal_operation: + b = number_zero(cur_exp_value_number); + break; + case mp_unequal_operation: + b = number_nonzero(cur_exp_value_number); + break; + }; + mp_set_cur_exp_value_boolean(mp, b ? mp_true_operation : mp_false_operation); + } + mp->cur_exp.type = mp_boolean_type; + DONE: + /* ignore overflow in comparisons */ + mp->arith_error = 0; + break; + case mp_and_operation: + case mp_or_operation: + /* Here we use the sneaky fact that |and_op-false_code=or_op-true_code| */ + if ((mp_type(p) != mp_boolean_type) || (mp->cur_exp.type != mp_boolean_type)) { + mp_bad_binary(mp, p, (int) c); + } else if (number_to_boolean(p->data.n) == c + mp_false_operation - mp_and_operation) { + mp_set_cur_exp_value_boolean(mp, number_to_boolean(p->data.n)); + } + break; + case mp_times_operation: + if ((mp->cur_exp.type < mp_color_type) || (mp_type(p) < mp_color_type)) { + mp_bad_binary(mp, p, mp_times_operation); + } else if ((mp->cur_exp.type == mp_known_type) || (mp_type(p) == mp_known_type)) { + /* Multiply when at least one operand is known */ + mp_number vv; + new_fraction(vv); + if (mp_type(p) == mp_known_type) { + number_clone(vv, mp_get_value_number(p)); + mp_free_value_node(mp, p); + } else { + number_clone(vv, cur_exp_value_number); + mp_unstash_cur_exp(mp, p); + } + switch (mp->cur_exp.type) { + case mp_known_type: + { + mp_number ret; + new_number(ret); + take_scaled(ret, cur_exp_value_number, vv); + mp_set_cur_exp_value_number(mp, &ret); + free_number(ret); + } + break; + case mp_pair_type: + { + mp_dep_mult(mp, (mp_value_node) mp_x_part(mp_get_value_node(cur_exp_node)), &vv, 1); + mp_dep_mult(mp, (mp_value_node) mp_y_part(mp_get_value_node(cur_exp_node)), &vv, 1); + } + break; + case mp_color_type: + { + mp_dep_mult(mp, (mp_value_node) mp_red_part (mp_get_value_node(cur_exp_node)), &vv, 1); + mp_dep_mult(mp, (mp_value_node) mp_green_part(mp_get_value_node(cur_exp_node)), &vv, 1); + mp_dep_mult(mp, (mp_value_node) mp_blue_part (mp_get_value_node(cur_exp_node)), &vv, 1); + } + break; + case mp_cmykcolor_type: + { + mp_dep_mult(mp, (mp_value_node) mp_cyan_part (mp_get_value_node(cur_exp_node)), &vv, 1); + mp_dep_mult(mp, (mp_value_node) mp_magenta_part(mp_get_value_node(cur_exp_node)), &vv, 1); + mp_dep_mult(mp, (mp_value_node) mp_yellow_part (mp_get_value_node(cur_exp_node)), &vv, 1); + mp_dep_mult(mp, (mp_value_node) mp_black_part (mp_get_value_node(cur_exp_node)), &vv, 1); + } + break; + default: + { + mp_dep_mult(mp, NULL, &vv, 1); + } + break; + } + free_number(vv); + mp_finish_binary(mp, old_p, old_exp); + return; + } 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); + mp_finish_binary(mp, old_p, old_exp); + return; + } else { + mp_bad_binary(mp, p, mp_times_operation); + } + break; + case mp_over_operation: + if ((mp->cur_exp.type != mp_known_type) || (mp_type(p) < mp_color_type)) { + mp_bad_binary(mp, p, mp_over_operation); + } else { + mp_number v_n; + new_number_clone(v_n, cur_exp_value_number); + mp_unstash_cur_exp(mp, p); + if (number_zero(v_n)) { + /* Squeal about division by zero */ + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Division by zero", + "You're trying to divide the quantity shown above the error message by zero. I'm\n" + "going to divide it by one instead." + ); + mp_get_x_next(mp); + } else { + switch (mp->cur_exp.type) { + case mp_known_type: + { + mp_number ret; + new_number(ret); + make_scaled(ret, cur_exp_value_number, v_n); + mp_set_cur_exp_value_number(mp, &ret); + free_number(ret); + } + break; + case mp_pair_type: + { + mp_dep_div(mp, (mp_value_node) mp_x_part(mp_get_value_node(cur_exp_node)), &v_n); + mp_dep_div(mp, (mp_value_node) mp_y_part(mp_get_value_node(cur_exp_node)), &v_n); + } + break; + case mp_color_type: + { + mp_dep_div(mp, (mp_value_node) mp_red_part (mp_get_value_node(cur_exp_node)), &v_n); + mp_dep_div(mp, (mp_value_node) mp_green_part(mp_get_value_node(cur_exp_node)), &v_n); + mp_dep_div(mp, (mp_value_node) mp_blue_part (mp_get_value_node(cur_exp_node)), &v_n); + } + break; + case mp_cmykcolor_type: + { + mp_dep_div(mp, (mp_value_node) mp_cyan_part (mp_get_value_node(cur_exp_node)), &v_n); + mp_dep_div(mp, (mp_value_node) mp_magenta_part(mp_get_value_node(cur_exp_node)), &v_n); + mp_dep_div(mp, (mp_value_node) mp_yellow_part (mp_get_value_node(cur_exp_node)), &v_n); + mp_dep_div(mp, (mp_value_node) mp_black_part (mp_get_value_node(cur_exp_node)), &v_n); + } + break; + default: + { + mp_dep_div(mp, NULL, &v_n); + } + break; + } + } + free_number(v_n); + mp_finish_binary(mp, old_p, old_exp); + return; + } + break; + case mp_power_operation: + if ((mp->cur_exp.type == mp_known_type) && (mp_type(p) == mp_known_type)) { + mp_number r; + new_number(r); + power_of(r, mp_get_value_number(p), cur_exp_value_number); + check_arith(); + mp_set_cur_exp_value_number(mp, &r); + free_number(r); + } else + mp_bad_binary(mp, p, (int) c); + break; + case mp_pythag_add_operation: + case mp_pythag_sub_operation: + if ((mp->cur_exp.type == mp_known_type) && (mp_type(p) == mp_known_type)) { + mp_number r; + new_number(r); + if (c == mp_pythag_add_operation) { + pyth_add(r, mp_get_value_number(p), cur_exp_value_number); + } else { + pyth_sub(r, mp_get_value_number(p), cur_exp_value_number); + } + mp_set_cur_exp_value_number(mp, &r); + free_number(r); + } else + mp_bad_binary(mp, p, (int) c); + break; + case mp_rotated_operation: + case mp_slanted_operation: + case mp_scaled_operation: + case mp_shifted_operation: + case mp_transformed_operation: + case mp_x_scaled_operation: + case mp_y_scaled_operation: + case mp_z_scaled_operation: + /* + The next few sections of the program deal with affine transformations + of coordinate data. + */ + switch (mp_type(p)) { + case mp_path_type: + mp_set_up_known_trans(mp, (int) c); + mp_unstash_cur_exp(mp, p); + mp_do_path_trans(mp, cur_exp_knot); + mp_finish_binary(mp, old_p, old_exp); + return; + case mp_pen_type: + mp_set_up_known_trans(mp, (int) c); + mp_unstash_cur_exp(mp, p); + mp_do_pen_trans(mp, cur_exp_knot); + /* rounding error could destroy convexity */ + mp_set_cur_exp_knot(mp, mp_convex_hull(mp, cur_exp_knot)); + mp_finish_binary(mp, old_p, old_exp); + return; + case mp_nep_type: + mp_set_up_known_trans(mp, (int) c); + mp_unstash_cur_exp(mp, p); + mp_do_pen_trans(mp, cur_exp_knot); + mp_set_cur_exp_knot(mp, cur_exp_knot); + mp_finish_binary(mp, old_p, old_exp); + return; + case mp_pair_type: + case mp_transform_type: + mp_big_trans(mp, p, (int) c); + break; + case mp_picture_type: + mp_do_edges_trans(mp, p, (int) c); + mp_finish_binary(mp, old_p, old_exp); + return; + default: + mp_bad_binary(mp, p, (int) c); + break; + } + break; + case mp_concatenate_operation: + case mp_just_append_operation: + if ((mp->cur_exp.type == mp_string_type) && (mp_type(p) == mp_string_type)) { + mp_string str = mp_cat(mp, mp_get_value_str(p), cur_exp_str); + delete_str_ref(cur_exp_str) ; + mp_set_cur_exp_str(mp, str); + } else { + mp_bad_binary(mp, p, c); + } + break; + case mp_substring_operation: + if (mp_nice_pair(mp, p, mp_type(p)) && (mp->cur_exp.type == mp_string_type)) { + mp_string str = mp_chop_string (mp, + cur_exp_str, + round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(p)))), + round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(p)))) + ); + delete_str_ref(cur_exp_str) ; + mp_set_cur_exp_str(mp, str); + } else { + mp_bad_binary(mp, p, mp_substring_operation); + } + break; + case mp_subpath_operation: + 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, mp_get_value_node(p)); + } else { + mp_bad_binary(mp, p, mp_subpath_operation); + } + break; + case mp_point_operation: + case mp_precontrol_operation: + case mp_postcontrol_operation: + case mp_direction_operation: + 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_type)) { + mp_find_point(mp, &(mp_get_value_number(p)), (int) c); + } else { + mp_bad_binary(mp, p, c); + } + break; + case mp_pen_offset_operation: + if ((mp->cur_exp.type == mp_pen_type || mp->cur_exp.type == mp_nep_type) && mp_nice_pair(mp, p, mp_type(p))) { + mp_set_up_offset(mp, mp_get_value_node(p)); + } else { + mp_bad_binary(mp, p, mp_pen_offset_operation); + } + break; + case mp_direction_time_operation: + 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, mp_get_value_node(p)); + } else { + mp_bad_binary(mp, p, mp_direction_time_operation); + } + break; + case mp_envelope_operation: + if ((mp_type(p) != mp_pen_type && mp_type(p) != mp_nep_type) || (mp->cur_exp.type != mp_path_type)) { + mp_bad_binary(mp, p, mp_envelope_operation); + } else { + mp_set_up_envelope(mp, p); + } + break; + case mp_boundingpath_operation: + if ((mp_type(p) != mp_pen_type && mp_type(p) != mp_nep_type) || (mp->cur_exp.type != mp_path_type)) { + mp_bad_binary(mp, p, mp_boundingpath_operation); + } else { + mp_set_up_boundingpath(mp, p); + } + break; + case mp_arc_time_operation: + 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_type)) { + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_get_arc_time(mp, &new_expr.data.n, cur_exp_knot, &(mp_get_value_number(p)), 0); + mp_flush_cur_exp(mp, new_expr); + } else { + mp_bad_binary(mp, p, (int) c); + } + break; + case mp_arc_point_operation: + /* todo: make a function */ + 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_type || mp_type(p) == mp_pair_type)) { + mp_knot k; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + if (mp_type(p) == mp_pair_type) { + mp_knot f = cur_exp_knot; + mp_node q = mp_get_value_node(p); + mp_number x; + new_number_clone(x, mp_get_value_number(mp_x_part(q))); + if (number_greater(x, zero_t)) { + while (number_greater(x, zero_t)) { + f = mp_next_knot(f); + number_subtract(x, unity_t); + } + } else { + while (number_less(x, zero_t)) { + f = mp_next_knot(f); + number_add(x, unity_t); + } + } + k = mp_get_arc_time(mp, &new_expr.data.n, f, &(mp_get_value_number(mp_y_part(q))), 1); + free_number(x); + } else { + k = mp_get_arc_time(mp, &new_expr.data.n, cur_exp_knot, &(mp_get_value_number(p)), 1); + } + if (k) { + int toss = 0; + if (number_equal(new_expr.data.n, unity_t)) { + k = mp_next_knot(k); + } else if (! number_equal(new_expr.data.n, zero_t)) { + convert_scaled_to_fraction(new_expr.data.n); + k = mp_split_cubic_knot(mp, k, &new_expr.data.n); + toss = 1; + } + mp_pair_value(mp, &(k->x_coord), &(k->y_coord)); + if (toss) { + mp_toss_knot(mp, k); + } + } else { + mp_bad_unary(mp, mp_arc_point_operation); + } + } else { + mp_bad_unary(mp, mp_arc_point_operation); + } + break; + case mp_arc_point_list_operation: + /* todo: make a function */ + /* + vardef arcpoints_a(expr thepath, cnt) = + numeric len ; len := length thepath ; + numeric aln ; aln := arclength thepath ; + numeric seg ; seg := 0 ; + numeric tot ; tot := 0 ; + numeric tim ; tim := 0 ; + numeric stp ; stp := aln / cnt; + numeric acc ; acc := subarclength (0,1) of thepath ; + point 0 of thepath + for tot = stp step stp until aln : + hide( + forever : + exitif tot < acc ; + seg := seg + 1 ; + tim := acc ; + acc := acc + subarclength (seg,seg+1) of thepath ; + endfor ; + ) + -- (arcpoint (seg,tot-tim) of thepath) + endfor if cycle thepath : -- cycle fi + enddef ; + */ + 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_type) { + // we can consider using ints as we have discrete points + mp_knot cur = cur_exp_knot; + mp_number len, aln, seg, tot, tim, stp, acc, tmp; + mp_knot last = NULL; + mp_knot list = NULL; + int iscycle = mp_left_type(cur_exp_knot) == mp_explicit_knot; + new_number(len); + mp_path_length(mp, &len); + new_number(aln); + mp_get_arc_length(mp, &aln, cur_exp_knot); + new_number(seg); + new_number(tot); + new_number(tim); + new_number(stp); + set_number_from_div(stp, aln, mp_get_value_number(p)); + new_number(acc); + mp_get_subarc_length(mp, &acc, cur_exp_knot, &zero_t, &unity_t); + /* */ + new_number(tmp); + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + /* first point */ + list = mp_complex_knot(mp, cur_exp_knot); + mp_prev_knot(list) = list; + mp_next_knot(list) = list; + last = list; + /* second and following points */ + number_clone(tot, stp); + while (number_lessequal(tot, aln)) { + mp_knot k; + while (1) { + if (number_lessequal(tot, acc)) { + break; + } else { + number_add(seg, unity_t); + number_clone(tim, acc); + cur = mp_next_knot(cur); + mp_get_subarc_length(mp, &tmp, cur, &zero_t, &unity_t); + number_add(acc, tmp) ; + } + } + /* still from the start, can be improved with offset */ + number_clone(tmp, tot); + number_subtract(tmp, tim); + k = mp_get_arc_time(mp, &new_expr.data.n, cur, &tmp, 1); + /* */ + if (k) { + int toss = 0; + mp_knot kk; + if (number_equal(new_expr.data.n, unity_t)) { + k = mp_next_knot(k); + } else if (! number_equal(new_expr.data.n, zero_t)) { + convert_scaled_to_fraction(new_expr.data.n); + k = mp_split_cubic_knot(mp, k, &new_expr.data.n); + toss = 1; + } + kk = mp_complex_knot(mp, k); + mp_prev_knot(list) = kk; + mp_next_knot(kk) = list; + mp_prev_knot(kk) = last; + mp_next_knot(last) = kk; + last = kk; + if (toss) { + mp_toss_knot(mp, k); + } + number_add(tot, stp); + } else { + break; + } + } + + free_number(len); + free_number(aln); + free_number(seg); + free_number(tot); + free_number(tim); + free_number(stp); + free_number(acc); + free_number(tmp); + if (list) { + if (iscycle) { + mp_left_type(list) = mp_explicit_knot; + mp_right_type(last) = mp_explicit_knot; + } else { + mp_left_type(list) = mp_endpoint_knot; + mp_right_type(last) = mp_endpoint_knot; + } + mp->cur_exp.type = mp_path_type; + mp_set_cur_exp_knot(mp, list); + } else { + mp_bad_unary(mp, mp_arc_point_list_operation); + } + } else { + mp_bad_unary(mp, mp_arc_point_list_operation); + } + break; + case mp_subarc_length_operation: + 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_pair_type) { + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_node q = mp_get_value_node(p); + mp_get_subarc_length(mp, &new_expr.data.n, cur_exp_knot, &(mp_get_value_number(mp_x_part(q))), &(mp_get_value_number(mp_y_part(q)))); + mp_flush_cur_exp(mp, new_expr); + } else { + mp_bad_unary(mp, mp_subarc_length_operation); + } + break; + case mp_intertimes_operation: + case mp_intertimes_list_operation: + if (mp_type(p) == mp_pair_type) { + mp_node 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)) { + if (c == mp_intertimes_operation) { + // mp_number arg1, arg2; + // mp_path_intersection(mp, mp_get_value_knot(p), cur_exp_knot, 0, NULL); + // new_number_clone(arg1, mp->cur_t); + // new_number_clone(arg2, mp->cur_tt); + // mp_pair_value(mp, &arg1, &arg2); + // free_number(arg1); + // free_number(arg2); + mp_path_intersection(mp, mp_get_value_knot(p), cur_exp_knot, 0, NULL); + mp_pair_value(mp, &mp->cur_t, &mp->cur_tt); + } else { + mp_knot last = NULL; + mp_knot list = mp_path_intersection(mp, mp_get_value_knot(p), cur_exp_knot, 1, &last); + mp_left_type(list) = mp_endpoint_knot; + mp_right_type(last) = mp_endpoint_knot; + mp->cur_exp.type = mp_path_type; + mp_set_cur_exp_knot(mp, list); + } + } else { + mp_bad_binary(mp, p, c); + } + break; + } + mp_recycle_value(mp, p); + mp_free_value_node(mp, p); /* |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, int c) +{ + char msg[256]; + mp_string sname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + if (c >= mp_min_of_operation) { + mp_print_op(mp, c); + } + mp_print_known_or_unknown_type(mp, mp_type(p), p); + if (c >= mp_min_of_operation) { + mp_print_str(mp, "of"); + } else { + mp_print_op(mp, c); + } + mp_print_known_or_unknown_type(mp, mp->cur_exp.type, cur_exp_node); + sname = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "Not implemented: %s", mp_str(mp, sname)); + @.Not implemented...@> + delete_str_ref(sname); + mp_disp_err(mp, p); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + msg, + "I'm afraid I don't know how to apply that operation to that combination of types.\n" + "Continue, and I'll return the second argument (see above) as the result of the" + "operation." + ); + mp_get_x_next(mp); +} + +static void mp_bad_envelope_pen (MP mp) +{ + mp_disp_err(mp, NULL); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Not implemented: 'envelope(elliptical pen) of (path)'", + "I'm afraid I don't know how to apply that operation to that combination of types.\n" + "Continue, and I'll return the second argument (see above) as the result of the\n" + "operation." + ); + @.Not implemented...@> + mp_get_x_next(mp); +} + +@ @<Declare binary action...@>= +static mp_node mp_tarnished (MP mp, mp_node p) +{ + mp_node q = mp_get_value_node(p); + (void) mp; + switch (mp_type(p)) { + case mp_pair_type: + return ( + (mp_type(mp_x_part(q)) == mp_independent_type) || + (mp_type(mp_y_part(q)) == mp_independent_type) + ) ? MP_VOID : NULL; + case mp_color_type: + return ( + (mp_type(mp_red_part(q)) == mp_independent_type) || + (mp_type(mp_green_part(q)) == mp_independent_type) || + (mp_type(mp_blue_part(q)) == mp_independent_type) + ) ? MP_VOID : NULL; + case mp_cmykcolor_type: + return ( + (mp_type(mp_cyan_part(q)) == mp_independent_type) || + (mp_type(mp_magenta_part(q)) == mp_independent_type) || + (mp_type(mp_yellow_part(q)) == mp_independent_type) || + (mp_type(mp_black_part(q)) == mp_independent_type) + ) ? MP_VOID : NULL; + case mp_transform_type: + return ( + (mp_type(mp_tx_part(q)) == mp_independent_type) || + (mp_type(mp_ty_part(q)) == mp_independent_type) || + (mp_type(mp_xx_part(q)) == mp_independent_type) || + (mp_type(mp_xy_part(q)) == mp_independent_type) || + (mp_type(mp_yx_part(q)) == mp_independent_type) || + (mp_type(mp_yy_part(q)) == mp_independent_type) + ) ? MP_VOID : NULL; + default: + return NULL; + } +} + +@ 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, int 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 */ + mp_number vv; /* second operand value for known values */ + new_number(vv); + if (q == NULL) { + t = mp->cur_exp.type; + if (t < mp_dependent_type) { + number_clone(vv, cur_exp_value_number); + } else { + v = (mp_value_node) mp_get_dep_list((mp_value_node) cur_exp_node); + } + } else { + t = mp_type(q); + if (t < mp_dependent_type) { + number_clone(vv, mp_get_value_number(q)); + } else { + v = (mp_value_node) mp_get_dep_list((mp_value_node) q); + } + } + if (t == mp_known_type) { + mp_value_node qq = (mp_value_node) q; + if (c == mp_minus_operation) { + number_negate(vv); + } + if (mp_type(p) == mp_known_type) { + slow_add(vv, mp_get_value_number(p), vv); + if (q == NULL) { + mp_set_cur_exp_value_number(mp, &vv); + } else { + mp_set_value_number(q, vv); + } + free_number(vv); + return; + } else { + /* Add a known value to the constant term of |mp_get_dep_list(p)| */ + r = (mp_value_node) mp_get_dep_list((mp_value_node) p); + while (mp_get_dep_info(r) != NULL) { + r = (mp_value_node) mp_link(r); + } + slow_add(vv, mp_get_dep_value(r), vv); + mp_set_dep_value(r, vv); + if (qq == NULL) { + qq = mp_get_dep_node(mp); + mp_set_cur_exp_node(mp, (mp_node) qq); + mp->cur_exp.type = mp_type(p); + mp_name_type(qq) = mp_capsule_operation; + /* clang: never read: |q = (mp_node) qq;| */ + } + mp_set_dep_list(qq, mp_get_dep_list((mp_value_node) p)); + mp_type(qq) = mp_type(p); + mp_set_prev_dep(qq, mp_get_prev_dep((mp_value_node) p)); + mp_link(mp_get_prev_dep((mp_value_node) p)) = (mp_node) qq; + mp_type(p) = mp_known_type; /* this will keep the recycler from collecting non-garbage */ + } + } else { + if (c == mp_minus_operation) { + 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. + */ + if (mp_type(p) == mp_known_type) { + /* Add the known |value(p)| to the constant term of |v| */ + while (mp_get_dep_info(v) != NULL) { + v = (mp_value_node) mp_link(v); + } + slow_add(vv, mp_get_value_number(p), mp_get_dep_value(v)); + mp_set_dep_value(v, vv); + } else { + s = mp_type(p); + r = (mp_value_node) mp_get_dep_list((mp_value_node) p); + if (t == mp_dependent_type) { + if (s == mp_dependent_type) { + int b; + mp_number ret1, ret2; + new_fraction(ret1); + new_fraction(ret2); + mp_max_coef(mp, &ret1, r); + mp_max_coef(mp, &ret2, v); + number_add(ret1, ret2); + b = number_less(ret1, coef_bound_k); + free_number(ret1); + free_number(ret2); + if (b) { + v = mp_p_plus_q(mp, v, r, mp_dependent_type); + goto DONE; + } + } /* |fix_needed| will necessarily be false */ + t = mp_proto_dependent_type; + v = mp_p_over_v(mp, v, &unity_t, mp_dependent_type, mp_proto_dependent_type); + } + if (s == mp_proto_dependent_type) { + v = mp_p_plus_q(mp, v, r, mp_proto_dependent_type); + } else { + v = mp_p_plus_fq(mp, v, &unity_t, r, mp_proto_dependent_type, mp_dependent_type); + } + 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); + } + } + } + free_number(vv); +} + +@ 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, int t) +{ + mp_value_node p = (q == NULL) ? (mp_value_node) cur_exp_node : q; /* the destination */ + mp_set_dep_list(p, v); + mp_type(p) = t; + if (mp_get_dep_info(v) == NULL) { + mp_number vv; /* the value, if it is |known| */ + new_number_clone(vv, mp_get_value_number(v)); + if (q == NULL) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number_clone(new_expr.data.n, vv); + mp_flush_cur_exp(mp, new_expr); + } else { + mp_recycle_value(mp, (mp_node) p); + mp_type(q) = mp_known_type; + mp_set_value_number(q, vv); + } + free_number(vv); + } else if (q == NULL) { + mp->cur_exp.type = t; + } + if (mp->fix_needed) { + mp_fix_dependencies(mp); + } +} + +@ @<Declare binary action...@>= +static void mp_dep_mult (MP mp, mp_value_node p, mp_number *v, int v_is_scaled) +{ + mp_value_node q; /* the dependency list being multiplied by |v| */ + int s, t; /* its type, before and after */ + if (p == NULL) { + q = (mp_value_node) cur_exp_node; + } else if (mp_type(p) != mp_known_type) { + q = p; + } else { + mp_number r1, arg1; + new_number_clone(arg1, mp_get_dep_value(p)); + if (v_is_scaled) { + new_number(r1); + take_scaled(r1, arg1, *v); + } else { + new_fraction(r1); + take_fraction(r1, arg1, *v); + } + mp_set_dep_value(p, r1); + free_number(r1); + free_number(arg1); + return; + } + t = mp_type(q); + q = (mp_value_node) mp_get_dep_list(q); + s = t; + if (t == mp_dependent_type && v_is_scaled) { + mp_number arg1, arg2; + new_fraction(arg1); + mp_max_coef(mp, &arg1, q); + new_number_abs(arg2, *v); + if (ab_vs_cd(arg1, arg2, coef_bound_minus_1, unity_t) >= 0) { + t = mp_proto_dependent_type; + } + free_number(arg1); + free_number(arg2); + } + 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, mp_number *n, mp_number *d) +{ + /* multiplies |cur_exp| by |n/d| */ + mp_node old_exp; /* a capsule to recycle */ + mp_number v; /* |n/d| */ + new_fraction(v); + if (number_greater(internal_value(mp_tracing_commands_internal), two_t)) { + @<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_type: + 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); + } + make_fraction(v, *n, *d); + switch (mp->cur_exp.type) { + case mp_known_type: + { + mp_number r1, arg1; + new_fraction(r1); + new_number_clone(arg1, cur_exp_value_number); + take_fraction(r1, arg1, v); + mp_set_cur_exp_value_number(mp, &r1); + free_number(r1); + free_number(arg1); + } + break; + case mp_pair_type: + { + mp_dep_mult(mp, (mp_value_node) mp_x_part(mp_get_value_node(cur_exp_node)), &v, 0); + mp_dep_mult(mp, (mp_value_node) mp_y_part(mp_get_value_node(cur_exp_node)), &v, 0); + } + break; + case mp_color_type: + { + mp_dep_mult(mp, (mp_value_node) mp_red_part (mp_get_value_node(cur_exp_node)), &v, 0); + mp_dep_mult(mp, (mp_value_node) mp_green_part(mp_get_value_node(cur_exp_node)), &v, 0); + mp_dep_mult(mp, (mp_value_node) mp_blue_part (mp_get_value_node(cur_exp_node)), &v, 0); + } + break; + case mp_cmykcolor_type: + { + mp_dep_mult(mp, (mp_value_node) mp_cyan_part (mp_get_value_node(cur_exp_node)), &v, 0); + mp_dep_mult(mp, (mp_value_node) mp_magenta_part(mp_get_value_node(cur_exp_node)), &v, 0); + mp_dep_mult(mp, (mp_value_node) mp_yellow_part (mp_get_value_node(cur_exp_node)), &v, 0); + mp_dep_mult(mp, (mp_value_node) mp_black_part (mp_get_value_node(cur_exp_node)), &v, 0); + } + break; + default: + { + mp_dep_mult(mp, NULL, &v, 0); + } + break; + } + if (old_exp != NULL) { + mp_recycle_value(mp, old_exp); + mp_free_value_node(mp, old_exp); + } + free_number(v); +} + +@ @<Trace the fraction multiplication@>= +mp_begin_diagnostic(mp); +mp_print_nl(mp, "{("); +print_number(*n); +mp_print_chr(mp, '/'); +print_number(*d); +mp_print_str(mp, ")*("); +mp_print_exp(mp, NULL, 0); +mp_print_str(mp, ")}"); +mp_end_diagnostic(mp, 0); + +@ 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_number v; /* the known value for |r| */ + new_number(v); + 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; + switch (mp->cur_exp.type) { + case mp_pair_type: + { + mp_node r = mp_x_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + r = mp_y_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + } + break; + case mp_color_type: + { + mp_node r = mp_red_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + r = mp_green_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + r = mp_blue_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + } + break; + case mp_cmykcolor_type: + { + mp_node r = mp_cyan_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + r = mp_yellow_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + r = mp_magenta_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + r = mp_black_part(mp_get_value_node(cur_exp_node)); + number_clone(v, mp_get_value_number(r)); + mp_new_dep(mp, r, mp_type(pp), mp_copy_dep_list(mp, (mp_value_node) mp_get_dep_list(pp))); + mp_dep_mult(mp, (mp_value_node) r, &v, 1); + } + break; + default: + break; + } + free_number(v); +} + +@ @<Declare binary action...@>= +static void mp_dep_div (MP mp, mp_value_node p, mp_number *v) +{ + mp_value_node q; /* the dependency list being divided by |v| */ + int s, t; /* its type, before and after */ + if (p == NULL) { + q = (mp_value_node) cur_exp_node; + } else if (mp_type(p) != mp_known_type) { + q = p; + } else { + mp_number ret; + new_number(ret); + make_scaled(ret, mp_get_value_number(p), *v); + mp_set_value_number(p, ret); + free_number(ret); + return; + } + t = mp_type(q); + q = (mp_value_node) mp_get_dep_list(q); + s = t; + if (t == mp_dependent_type) { + mp_number arg1, arg2; + new_number(arg2); + new_fraction(arg1); + mp_max_coef(mp, &arg1, q); + number_abs_clone(arg2, *v); + if (ab_vs_cd(arg1, unity_t, coef_bound_minus_1, arg2) >= 0) { + t = mp_proto_dependent_type; + } + free_number(arg1); + free_number(arg2); + } + q = mp_p_over_v(mp, q, v, s, t); + mp_dep_finish(mp, q, p, t); +} + +@ 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, int c) +{ + mp_node p, q, r; /* list manipulation registers */ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + if ((c != mp_transformed_operation) || (mp->cur_exp.type != mp_transform_type)) { + /* Put the current transform into |cur_exp| */ + p = mp_stash_cur_exp(mp); + mp_set_cur_exp_node(mp, mp_id_transform(mp)); + mp->cur_exp.type = mp_transform_type; + q = mp_get_value_node(cur_exp_node); + @<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@> + mp_disp_err(mp, p); + mp_back_error( + mp, + "Improper transformation argument", + "The expression shown above has the wrong type, so I can't transform anything\n" + "using it. Proceed, and I'll omit the transformation." + ); + mp_get_x_next(mp); + DONE: + mp_recycle_value(mp, p); + mp_free_value_node(mp, p); + } + /* + If the current transform is entirely known, stash it in global variables; + otherwise |return| + */ + q = mp_get_value_node(cur_exp_node); + if ( (mp_type(mp_tx_part(q)) == mp_known_type) && + (mp_type(mp_ty_part(q)) == mp_known_type) && + (mp_type(mp_xx_part(q)) == mp_known_type) && + (mp_type(mp_xy_part(q)) == mp_known_type) && + (mp_type(mp_yx_part(q)) == mp_known_type) && + (mp_type(mp_yy_part(q)) == mp_known_type) ) { + number_clone(mp->txx, mp_get_value_number(mp_xx_part(q))); + number_clone(mp->txy, mp_get_value_number(mp_xy_part(q))); + number_clone(mp->tyx, mp_get_value_number(mp_yx_part(q))); + number_clone(mp->tyy, mp_get_value_number(mp_yy_part(q))); + number_clone(mp->tx, mp_get_value_number(mp_tx_part(q))); + number_clone(mp->ty, mp_get_value_number(mp_ty_part(q))); + new_number(new_expr.data.n); + mp_flush_cur_exp(mp, new_expr); + } +} + +@ @<Glob...@>= +mp_number txx; +mp_number txy; +mp_number tyx; +mp_number tyy; +mp_number tx; +mp_number ty; /* current transform coefficients */ + +@ @<Initialize table...@>= +new_number(mp->txx); +new_number(mp->txy); +new_number(mp->tyx); +new_number(mp->tyy); +new_number(mp->tx); +new_number(mp->ty); + +@ @<Free table...@>= +free_number(mp->txx); +free_number(mp->txy); +free_number(mp->tyx); +free_number(mp->tyy); +free_number(mp->tx); +free_number(mp->ty); + +@ @<For each of the eight cases...@>= +switch (c) { + case mp_rotated_operation: + if (mp_type(p) == mp_known_type) { + @<Install sines and cosines, then |goto done|@> + } + break; + case mp_slanted_operation: + if (mp_type(p) > mp_pair_type) { + mp_install(mp, mp_xy_part(q), p); + goto DONE; + } + break; + case mp_scaled_operation: + if (mp_type(p) > mp_pair_type) { + mp_install(mp, mp_xx_part(q), p); + mp_install(mp, mp_yy_part(q), p); + goto DONE; + } + break; + case mp_shifted_operation: + if (mp_type(p) == mp_pair_type) { + r = mp_get_value_node(p); + mp_install(mp, mp_tx_part(q), mp_x_part(r)); + mp_install(mp, mp_ty_part(q), mp_y_part(r)); + goto DONE; + } + break; + case mp_x_scaled_operation: + if (mp_type(p) > mp_pair_type) { + mp_install(mp, mp_xx_part(q), p); + goto DONE; + } + break; + case mp_y_scaled_operation: + if (mp_type(p) > mp_pair_type) { + mp_install(mp, mp_yy_part(q), p); + goto DONE; + } + break; + case mp_z_scaled_operation: + if (mp_type(p) == mp_pair_type) { + @<Install a complex multiplier, then |goto done|@> + } + break; + case mp_transformed_operation: + break; +} + +@ @<Install sines and cosines, then |goto done|@>= +mp_number n_sin, n_cos, arg1, arg2; +new_fraction(n_sin); +new_fraction(n_cos); /* results computed by |n_sin_cos| */ +new_number_clone(arg2, unity_t); +new_number_clone(arg1, mp_get_value_number(p)); +number_multiply_int(arg2, 360); +number_modulo(arg1, arg2); +convert_scaled_to_angle(arg1); +n_sin_cos(arg1, n_cos, n_sin); +fraction_to_round_scaled(n_sin); +fraction_to_round_scaled(n_cos); +mp_set_value_number(mp_xx_part(q), n_cos); +mp_set_value_number(mp_yx_part(q), n_sin); +mp_set_value_number(mp_xy_part(q), mp_get_value_number(mp_yx_part(q))); +number_negate(mp_get_value_number(mp_xy_part(q))); +mp_set_value_number(mp_yy_part(q), mp_get_value_number(mp_xx_part(q))); +free_number(arg1); +free_number(arg2); +free_number(n_sin); +free_number(n_cos); +goto DONE; + +@ @<Install a complex multiplier, then |goto done|@>= +{ + r = mp_get_value_node(p); + mp_install(mp, mp_xx_part(q), mp_x_part(r)); + mp_install(mp, mp_yy_part(q), mp_x_part(r)); + mp_install(mp, mp_yx_part(q), mp_y_part(r)); + if (mp_type(mp_y_part(r)) == mp_known_type) { + mp_set_value_number(mp_y_part(r), mp_get_value_number(mp_y_part(r))); + number_negate(mp_get_value_number(mp_y_part(r))); + } else { + mp_negate_dep_list(mp, (mp_value_node) mp_get_dep_list((mp_value_node) mp_y_part(r))); + } + mp_install(mp, mp_xy_part(q), mp_y_part(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, int c) +{ + mp_set_up_trans(mp, c); + if (mp->cur_exp.type != mp_known_type) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Transform components aren't all known", + "I'm unable to apply a partially specified transformation except to a fully known\n" + "pair or transform. Proceed, and I'll omit the transformation." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + set_number_to_unity(mp->txx); + set_number_to_zero(mp->txy); + set_number_to_zero(mp->tyx); + set_number_to_unity(mp->tyy); + set_number_to_zero(mp->tx); + set_number_to_zero(mp->ty); + } +} + +@ 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_number_trans (MP mp, mp_number *p, mp_number *q) +{ + mp_number r1, r2, v; + new_number(r1); + new_number(r2); + new_number(v); + take_scaled(r1, *p, mp->txx); + take_scaled(r2, *q, mp->txy); + number_add(r1, r2); + set_number_from_addition(v, r1, mp->tx); + take_scaled(r1, *p, mp->tyx); + take_scaled(r2, *q, mp->tyy); + number_add(r1, r2); + set_number_from_addition(*q, r1, mp->ty); + number_clone(*p,v); + free_number(r1); + free_number(r2); + free_number(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|. The macro was used only once and has been inlined. + +@<Declare binary action...@>= +static void mp_do_path_trans (MP mp, mp_knot p) +{ + mp_knot q = p; + do { + if (mp_left_type(q) != mp_endpoint_knot) { + mp_number_trans(mp, &q->left_x, &q->left_y); + } + mp_number_trans(mp, &q->x_coord, &q->y_coord); + if (mp_right_type(q) != mp_endpoint_knot) { + mp_number_trans(mp, &q->right_x, &q->right_y); + } + 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. + +@<Declare binary action...@>= +static void mp_do_pen_trans (MP mp, mp_knot p) +{ + mp_knot q = p; /* list traverser */ + if (mp_pen_is_elliptical(p)) { + mp_number_trans(mp, &p->left_x, &p->left_y); + mp_number_trans(mp, &p->right_x, &p->right_y); + } + do { + mp_number_trans(mp, &q->x_coord, &q->y_coord); + q = mp_next_knot(q); + } while (q != p); +} + +@ Note that the shift parameters |(tx,ty)| apply only to the path being stroked. +The |dashscale| has to be adjusted to scale the dash lengths in |mp_dash_ptr(q)| +since the \ps\ output procedures will try to compensate for the transformation we +are applying to |mp_pen_ptr(q)|. Since this compensation is based on the square +root of the determinant, |sqdet| is the appropriate factor. + +@<Declare binary action...@>= +static void mp_do_path_pen_trans (MP mp, mp_shape_node p, mp_number *sqdet, int sgndet) +{ + mp_number sx, sy; + if (mp_pen_ptr(p) != NULL) { + new_number_clone(sx, mp->tx); + new_number_clone(sy, mp->ty); + set_number_to_zero(mp->tx); + set_number_to_zero(mp->ty); + mp_do_pen_trans(mp, mp_pen_ptr(p)); + if (number_nonzero(*sqdet) && ((mp_type(p) == mp_stroked_node_type) && (mp_dash_ptr(p) != NULL))) { + mp_number ret; + new_number(ret); + take_scaled(ret, ((mp_shape_node) p)->dashscale, *sqdet); + number_clone(((mp_shape_node) p)->dashscale, ret); + free_number(ret); + } + if (! mp_pen_is_elliptical(mp_pen_ptr(p)) && sgndet < 0) { + mp_pen_ptr(p) = mp_make_pen(mp, mp_copy_path(mp, mp_pen_ptr(p)), 1); + } + number_clone(mp->tx, sx); + number_clone(mp->ty, sy); + free_number(sx); + free_number(sy); + } +} + +@ 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_edge_header_node mp_edges_trans (MP mp, mp_edge_header_node h) +{ + mp_node q; /* the object being transformed */ + mp_dash_node r, s; /* for list manipulation */ + mp_number sqdet; /* square root of determinant for |dashscale| */ + int sgndet; /* sign of the determinant */ + h = mp_private_edges(mp, h); + new_number(sqdet); + mp_sqrt_det(mp, &sqdet, &(mp->txx), &(mp->txy), &(mp->tyx), &(mp->tyy)); + sgndet = ab_vs_cd(mp->txx, mp->tyy, mp->txy, mp->tyx); + if (mp_get_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(mp_edge_list(h)); + while (q != NULL) { + @<Transform graphical object |q|@> + q = mp_link(q); + } + free_number(sqdet); + return h; +} + +static void mp_do_edges_trans (MP mp, mp_node p, int c) +{ + mp_set_up_known_trans (mp, c); + mp_set_value_node(p, (mp_node) mp_edges_trans(mp, (mp_edge_header_node) mp_get_value_node(p))); + mp_unstash_cur_exp(mp, p); +} + +static mp_edge_header_node mp_scale_edges (MP mp, mp_number *se_sf, mp_edge_header_node se_pic) +{ + number_clone(mp->txx, *se_sf); + number_clone(mp->tyy, *se_sf); + set_number_to_zero(mp->txy); + set_number_to_zero(mp->tyx); + set_number_to_zero(mp->tx); + set_number_to_zero(mp->ty); + return mp_edges_trans(mp, se_pic); +} + +@ @<Try to transform the dash list of |h|@>= +if (number_nonzero(mp->txy) || number_nonzero(mp->tyx) || number_nonzero(mp->ty) || number_nonequalabs(mp->txx, mp->tyy)) { + mp_flush_dash_list(mp, h); +} else { + mp_number abs_tyy, ret; + new_number(abs_tyy); + if (number_negative(mp->txx)) { + @<Reverse the dash list of |h|@> + } + @<Scale the dash list by |txx| and shift it by |tx|@> + number_abs_clone(abs_tyy, mp->tyy); + new_number(ret); + take_scaled(ret, h->dash_y, abs_tyy); + number_clone(h->dash_y, ret); + free_number(ret); + free_number(abs_tyy); +} + +@ @<Reverse the dash list of |h|@>= +{ + r = mp_get_dash_list(h); + mp_set_dash_list(h, mp->null_dash); + while (r != mp->null_dash) { + s = r; + r = (mp_dash_node) mp_link(r); + number_swap(s->start_x, s->stop_x ); + mp_link(s) = (mp_node) mp_get_dash_list(h); + mp_set_dash_list(h, s); + } +} + +@ @<Scale the dash list by |txx| and shift it by |tx|@>= +r = mp_get_dash_list(h); +{ + mp_number arg1; + new_number(arg1); + while (r != mp->null_dash) { + take_scaled(arg1, r->start_x, mp->txx); + set_number_from_addition(r->start_x, arg1, mp->tx); + take_scaled(arg1, r->stop_x, mp->txx); + set_number_from_addition(r->stop_x, arg1, mp->tx); + r = (mp_dash_node) mp_link(r); + } + free_number(arg1); +} + +@ @<Make the bounding box of |h| unknown if it can't be updated properly...@>= +if (number_zero(mp->txx) && number_zero(mp->tyy)) { + @<Swap the $x$ and $y$ parameters in the bounding box of |h|@> +} else if (number_nonzero(mp->txy) || number_nonzero(mp->tyx)) { + mp_init_bbox(mp, h); + goto DONE1; +} +if (number_lessequal(h->minx, h->maxx)) { + @<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|@>= +number_swap(h->minx, h->miny); +number_swap(h->maxx, h->maxy); + +@ 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...@>= +mp_number tot, ret; +new_number(tot); +new_number(ret); +set_number_from_addition(tot,mp->txx,mp->txy); +take_scaled(ret, h->minx, tot); +set_number_from_addition(h->minx,ret, mp->tx); +take_scaled(ret, h->maxx, tot); +set_number_from_addition(h->maxx,ret, mp->tx); + +set_number_from_addition(tot,mp->tyx,mp->tyy); +take_scaled(ret, h->miny, tot); +set_number_from_addition(h->miny, ret, mp->ty); +take_scaled(ret, h->maxy, tot); +set_number_from_addition(h->maxy, ret, mp->ty); +set_number_from_addition(tot, mp->txx, mp->txy); +if (number_negative(tot)) { + number_swap(h->minx, h->maxx); +} +set_number_from_addition(tot, mp->tyx, mp->tyy); +if (number_negative(tot)) { + number_swap(h->miny, h->maxy); +} +free_number(ret); +free_number(tot); + +@ 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: + case mp_stroked_node_type: + mp_do_path_trans(mp, mp_path_ptr((mp_shape_node) q)); + mp_do_path_pen_trans(mp, (mp_shape_node) q, &sqdet, sgndet); + break; + case mp_start_clip_node_type: + case mp_start_group_node_type: + case mp_start_bounds_node_type: + mp_do_path_trans(mp, mp_path_ptr((mp_start_node) q)); + break; + case mp_stop_clip_node_type: + case mp_stop_group_node_type: + case mp_stop_bounds_node_type: + break; + default: + break; +} + +@ 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, int c) +{ + mp_node q = mp_get_value_node(p); + if (mp_type(q) == mp_pair_node_type) { + if (mp_type(mp_x_part(q)) != mp_known_type || mp_type(mp_y_part(q)) != mp_known_type) { + goto UNKNOWN; + } + } else if (mp_type(mp_tx_part(q)) != mp_known_type || mp_type(mp_ty_part(q)) != mp_known_type || + mp_type(mp_xx_part(q)) != mp_known_type || mp_type(mp_xy_part(q)) != mp_known_type || + mp_type(mp_yx_part(q)) != mp_known_type || mp_type(mp_yy_part(q)) != mp_known_type) { + goto UNKNOWN; + } + { + @<Transform a known big node@> + return; + } + UNKNOWN: + { + @<Transform an unknown big node and |return|@> + return; + } +} + +@ @<Transform an unknown big node and |return|@>= +mp_node r; +mp_set_up_known_trans(mp, c); +mp_make_exp_copy(mp, p); +r = mp_get_value_node(cur_exp_node); +if (mp->cur_exp.type == mp_transform_type) { + mp_bilin1(mp, mp_yy_part(r), &(mp->tyy), mp_xy_part(q), &(mp->tyx), &zero_t); + mp_bilin1(mp, mp_yx_part(r), &(mp->tyy), mp_xx_part(q), &(mp->tyx), &zero_t); + mp_bilin1(mp, mp_xy_part(r), &(mp->txx), mp_yy_part(q), &(mp->txy), &zero_t); + mp_bilin1(mp, mp_xx_part(r), &(mp->txx), mp_yx_part(q), &(mp->txy), &zero_t); +} +mp_bilin1(mp, mp_y_part(r), &(mp->tyy), mp_x_part(q), &(mp->tyx), &(mp->ty)); +mp_bilin1(mp, mp_x_part(r), &(mp->txx), mp_y_part(q), &(mp->txy), &(mp->tx)); + +@ 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, mp_number *t, mp_node q, mp_number *u, mp_number *delta_orig) +{ + mp_number delta; + new_number_clone(delta, *delta_orig); + if (! number_equal(*t, unity_t)) { + mp_dep_mult(mp, (mp_value_node) p, t, 1); + } + if (number_nonzero(*u)) { + if (mp_type(q) == mp_known_type) { + mp_number tmp; + new_number(tmp); + take_scaled(tmp, mp_get_value_number(q), *u); + number_add(delta, tmp); + free_number(tmp); + } else { + /* Ensure that |type(p)=mp_proto_dependent| */ + if (mp_type(p) != mp_proto_dependent_type) { + if (mp_type(p) == mp_known_type) { + mp_new_dep(mp, p, mp_type(p), mp_const_dependency(mp, &(mp_get_value_number(p)))); + } else { + mp_set_dep_list((mp_value_node) p, + mp_p_times_v(mp, + (mp_value_node) mp_get_dep_list((mp_value_node) p), &unity_t, + mp_dependent_type, mp_proto_dependent_type, 1)); + } + mp_type(p) = mp_proto_dependent_type; + } + mp_set_dep_list((mp_value_node) p, + mp_p_plus_fq(mp, + (mp_value_node) mp_get_dep_list((mp_value_node) p), u, + (mp_value_node) mp_get_dep_list((mp_value_node) q), + mp_proto_dependent_type, mp_type(q))); + } + } + if (mp_type(p) == mp_known_type) { + mp_set_value_number(p, mp_get_value_number(p)); + number_add(mp_get_value_number(p), delta); + } else { + mp_number tmp; + mp_value_node r = (mp_value_node) mp_get_dep_list((mp_value_node) p); + while (mp_get_dep_info(r) != NULL) { + r = (mp_value_node) mp_link(r); + } + new_number_clone(tmp, mp_get_value_number(r)); + number_add(delta, tmp); + // number_add(delta, mp_get_value_number(r)); + if (r != (mp_value_node) mp_get_dep_list((mp_value_node) p)) { + mp_set_value_number(r, delta); + } else { + mp_recycle_value(mp, p); + mp_type(p) = mp_known_type; + mp_set_value_number(p, delta); + } + free_number(tmp); + } + if (mp->fix_needed) { + mp_fix_dependencies(mp); + } + free_number(delta); +} + +@ @<Transform a known big node@>= +mp_node r, pp, qq; /* list manipulation registers */ +mp_set_up_trans(mp, c); +if (mp->cur_exp.type == mp_known_type) { + /* Transform known by known */ + mp_make_exp_copy(mp, p); + r = mp_get_value_node(cur_exp_node); + if (mp->cur_exp.type == mp_transform_type) { + mp_bilin3(mp, mp_yy_part(r), &(mp->tyy), &(mp_get_value_number(mp_xy_part(q))), &(mp->tyx), &zero_t); + mp_bilin3(mp, mp_yx_part(r), &(mp->tyy), &(mp_get_value_number(mp_xx_part(q))), &(mp->tyx), &zero_t); + mp_bilin3(mp, mp_xy_part(r), &(mp->txx), &(mp_get_value_number(mp_yy_part(q))), &(mp->txy), &zero_t); + mp_bilin3(mp, mp_xx_part(r), &(mp->txx), &(mp_get_value_number(mp_yx_part(q))), &(mp->txy), &zero_t); + } + mp_bilin3(mp, mp_y_part(r), &(mp->tyy), &(mp_get_value_number(mp_x_part(q))), &(mp->tyx), &(mp->ty)); + mp_bilin3(mp, mp_x_part(r), &(mp->txx), &(mp_get_value_number(mp_y_part(q))), &(mp->txy), &(mp->tx)); +} else { + pp = mp_stash_cur_exp(mp); + qq = mp_get_value_node(pp); + mp_make_exp_copy(mp, p); + r = mp_get_value_node(cur_exp_node); + if (mp->cur_exp.type == mp_transform_type) { + mp_bilin2(mp, mp_yy_part(r), mp_yy_part(qq), &(mp_get_value_number(mp_xy_part(q))), mp_yx_part(qq), NULL); + mp_bilin2(mp, mp_yx_part(r), mp_yy_part(qq), &(mp_get_value_number(mp_xx_part(q))), mp_yx_part(qq), NULL); + mp_bilin2(mp, mp_xy_part(r), mp_xx_part(qq), &(mp_get_value_number(mp_yy_part(q))), mp_xy_part(qq), NULL); + mp_bilin2(mp, mp_xx_part(r), mp_xx_part(qq), &(mp_get_value_number(mp_yx_part(q))), mp_xy_part(qq), NULL); + } + mp_bilin2(mp, mp_y_part(r), mp_yy_part(qq), &(mp_get_value_number(mp_x_part(q))), mp_yx_part(qq), mp_y_part(qq)); + mp_bilin2(mp, mp_x_part(r), mp_xx_part(qq), &(mp_get_value_number(mp_y_part(q))), mp_xy_part(qq), mp_x_part(qq)); + mp_recycle_value(mp, pp); + mp_free_value_node(mp, pp); +} + +@ 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, mp_number *v, mp_node r) +{ + if (mp_type(r) == mp_known_type) { + mp_number ret; + new_number(ret); + take_scaled(ret, mp_get_value_number(r), *v); + mp_set_dep_value(mp->dep_final, mp_get_dep_value(mp->dep_final)); + number_add(mp_get_dep_value(mp->dep_final), ret); + free_number(ret); + } else { + mp_set_dep_list(p, mp_p_plus_fq(mp, (mp_value_node) mp_get_dep_list(p), v, (mp_value_node) mp_get_dep_list((mp_value_node) r), mp_proto_dependent_type, 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, mp_number *v, mp_node u, mp_node q) +{ + mp_number vv; /* temporary storage for |value(p)| */ + new_number_clone(vv, mp_get_value_number(p)); + mp_new_dep(mp, p, mp_proto_dependent_type, mp_const_dependency(mp, &zero_t)); /* this sets |dep_final| */ + if (number_nonzero(vv)) { + mp_add_mult_dep(mp, (mp_value_node) p, &vv, t); /* |dep_final| doesn't change */ + } + if (number_nonzero(*v)) { + mp_number arg1; + new_number_clone(arg1, *v); + mp_add_mult_dep(mp, (mp_value_node) p, &arg1, u); + free_number(arg1); + } + if (q != NULL) { + mp_add_mult_dep(mp, (mp_value_node) p, &unity_t, q); + } + if (mp_get_dep_list((mp_value_node) p) == (mp_node) mp->dep_final) { + number_clone(vv, mp_get_dep_value(mp->dep_final)); + mp_recycle_value(mp, p); + mp_type(p) = mp_known_type; + mp_set_value_number(p, vv); + } + free_number(vv); +} + +@ Finally, in |bilin3| everything is |known|. + +@<Declare subroutines needed by |big_trans|@>= +static void mp_bilin3 (MP mp, mp_node p, mp_number *t, mp_number *v, mp_number *u, mp_number *delta_orig) +{ + mp_number delta; + mp_number tmp; + new_number(tmp); + new_number_clone(delta, *delta_orig); + if (! number_equal(*t, unity_t)) { + take_scaled(tmp, mp_get_value_number(p), *t); + } else { + number_clone(tmp, mp_get_value_number(p)); + } + number_add(delta, tmp); + if (number_nonzero(*u)) { + mp_number ret; + new_number(ret); + take_scaled(ret, *v, *u); + mp_set_value_number(p, delta); + number_add(mp_get_value_number(p), ret); + free_number(ret); + } else { + mp_set_value_number(p, delta); + } + free_number(tmp); + free_number(delta); +} + +@ @<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; /* link variables for copies of path nodes */ + mp_number a, b; /* indices for chopping */ + mp_number l; + int reversed; /* was |a>b|? */ + new_number(l); + mp_path_length(mp, &l); + new_number_clone(a, mp_get_value_number(mp_x_part(p))); + new_number_clone(b, mp_get_value_number(mp_y_part(p))); + if (number_lessequal(a, b)) { + reversed = 0; + } else { + reversed = 1; + number_swap (a, b); + } + /* Dispense with the cases |a<0| and/or |b>l| */ + if (number_negative(a)) { + if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) { + set_number_to_zero(a); + if (number_negative(b)) { + set_number_to_zero(b); + } + } else { + do { + number_add(a, l); + number_add(b, l); + } while (number_negative(a)); /* a cycle always has length |l>0| */ + } + } + if (number_greater(b, l)) { + if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) { + number_clone(b, l); + if (number_greater(a, l)) { + number_clone(a, l); + } + } else { + while (number_greaterequal(a, l)) { + number_subtract(a, l); + number_subtract(b, l); + } + } + } + q = cur_exp_knot; + while (number_greaterequal(a, unity_t)) { + q = mp_next_knot(q); + number_subtract(a, unity_t); + number_subtract(b, unity_t); + } + if (number_equal(b, a)) { + /* Construct a path from |pp| to |qq| of length zero */ + if (number_positive(a)) { + mp_number arg1; + new_number_clone(arg1, a); + convert_scaled_to_fraction(arg1); + mp_split_cubic(mp, q, &arg1); + free_number(arg1); + q = mp_next_knot(q); + } + pp = mp_copy_knot(mp, q); + qq = pp; + } else { + /* Construct a path from |pp| to |qq| of length $\lceil b\rceil$ */ + mp_knot rr; + pp = mp_copy_knot(mp, q); + qq = pp; + do { + q = mp_next_knot(q); + rr = qq; + qq = mp_copy_knot(mp, q); + mp_prev_knot(qq) = rr; + mp_next_knot(rr) = qq; + number_subtract(b, unity_t); + } while (number_positive(b)); + if (number_positive(a)) { + mp_knot ss = pp; + mp_number arg1; + new_number_clone(arg1, a); + convert_scaled_to_fraction(arg1); + mp_split_cubic(mp, ss, &arg1); + free_number(arg1); + pp = mp_next_knot(ss); + mp_toss_knot(mp, ss); + if (rr == ss) { + mp_number arg1, arg2; + new_number(arg1); + set_number_from_subtraction(arg1, unity_t, a); + new_number_clone(arg2, b); + make_scaled(b, arg2, arg1); + free_number(arg1); + free_number(arg2); + rr = pp; + } + } + if (number_negative(b)) { + mp_number arg1; + new_number(arg1); + set_number_from_addition(arg1, b, unity_t); + convert_scaled_to_fraction(arg1); + mp_split_cubic(mp, rr, &arg1); + free_number(arg1); + mp_toss_knot(mp, qq); + qq = mp_next_knot(rr); + } + } + mp_left_type(pp) = mp_endpoint_knot; + mp_right_type(qq) = mp_endpoint_knot; + mp_prev_knot(pp) = qq; + mp_next_knot(qq) = pp; + mp_toss_knot_list(mp, cur_exp_knot); + if (reversed) { + mp_set_cur_exp_knot(mp, mp_next_knot(mp_htap_ypoc(mp, pp))); + mp_toss_knot_list(mp, pp); + } else { + mp_set_cur_exp_knot(mp, pp); + } + free_number(l); + free_number(a); + free_number(b); +} + +@ @<Declare binary action...@>= +static void mp_set_up_offset (MP mp, mp_node p) +{ + mp_find_offset(mp, &(mp_get_value_number(mp_x_part(p))), &(mp_get_value_number(mp_y_part(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_number(new_expr.data.n); + mp_find_direction_time(mp, &new_expr.data.n, &(mp_get_value_number(mp_x_part(p))), &(mp_get_value_number(mp_y_part(p))), cur_exp_knot); + mp_flush_cur_exp(mp, new_expr); +} + +static void mp_set_up_envelope (MP mp, mp_node p) +{ + mp_knot q = mp_copy_path(mp, cur_exp_knot); /* the original path */ + /* TODO: accept elliptical pens for straight paths */ + /* TODO: quite some duplicate code here: maybe make some helpers */ + if (mp_pen_is_elliptical(mp_get_value_knot(p))) { + mp_bad_envelope_pen(mp); + mp_set_cur_exp_knot(mp, q); + mp->cur_exp.type = mp_path_type; + } else { + int linejoin = mp_mitered_linejoin_code; + int linecap = mp_butt_linecap_code; + mp_number miterlimit; + new_number(miterlimit); + if (number_greater(internal_value(mp_linejoin_internal), unity_t)) { + linejoin = mp_beveled_linejoin_code; + } else if (number_positive(internal_value(mp_linejoin_internal))) { + linejoin = mp_rounded_linejoin_code; + } + if (number_greater(internal_value(mp_linecap_internal), unity_t)) { + linecap = mp_squared_linecap_code; + } else if (number_positive(internal_value(mp_linecap_internal))) { + linecap = mp_rounded_linecap_code; + } + if (number_less(internal_value(mp_miterlimit_internal), unity_t)) { + set_number_to_unity(miterlimit); + } else { + number_clone(miterlimit, internal_value(mp_miterlimit_internal)); + } + mp_set_cur_exp_knot(mp, mp_make_envelope(mp, q, mp_get_value_knot(p), linejoin, linecap, &miterlimit)); + mp->cur_exp.type = mp_path_type; + } +} + +static void mp_set_up_boundingpath (MP mp, mp_node p) +{ + mp_number miterlimit; + mp_knot q = mp_copy_path(mp, cur_exp_knot); /* the original path */ + mp_knot qq; + int linejoin = mp_mitered_linejoin_code; + int linecap = mp_butt_linecap_code; + mp_knot pen = mp_get_value_knot(p); + new_number(miterlimit); + /* + Accept elliptical pens for s paths using |mp_make_path| to convert an + elliptical pen to a polygonal one. The approximation of 8 knots should be + good enough. + */ + if (mp_pen_is_elliptical(mp_get_value_knot(p))) { + mp_knot kp, kq; + pen = mp_copy_pen(mp, mp_get_value_knot(p)); + mp_make_path(mp, pen); + kq = pen; + do { + kp = kq; + kq = mp_next_knot(kq); + mp_prev_knot(kq) = kp; + } while (kq != pen); + mp_close_path_cycle(mp, kp, pen); + } + if (number_greater(internal_value(mp_linejoin_internal), unity_t)) { + linejoin = mp_beveled_linejoin_code; + } else if (number_positive(internal_value(mp_linejoin_internal))) { + linejoin = mp_rounded_linejoin_code; + } + if (number_greater(internal_value(mp_linecap_internal), unity_t)) { + linecap = mp_squared_linecap_code; + } else if (number_positive(internal_value(mp_linecap_internal))) { + linecap = mp_rounded_linecap_code; + } + if (number_less(internal_value(mp_miterlimit_internal), unity_t)) { + set_number_to_unity(miterlimit); + } else { + number_clone(miterlimit, internal_value(mp_miterlimit_internal)); + } + qq = mp_make_envelope(mp, q, pen, linejoin, linecap, &miterlimit); + mp_set_cur_exp_knot(mp, qq); + mp->cur_exp.type = mp_path_type; + if (! mp_get_cur_bbox(mp)) { + mp_bad_binary(mp, p, mp_boundingpath_operation); + mp_set_cur_exp_knot(mp, q); + mp->cur_exp.type = mp_path_type; + return; + } else { + mp_knot ll = mp_new_knot(mp); + mp_knot lr = mp_new_knot(mp); + mp_knot ur = mp_new_knot(mp); + mp_knot ul = mp_new_knot(mp); + if (ll == NULL || lr == NULL || ur == NULL || ul == NULL){ + mp_bad_binary(mp, p, mp_boundingpath_operation); + mp_set_cur_exp_knot(mp, q); + mp->cur_exp.type = mp_path_type; + return; + } else { + mp_left_type(ll) = mp_endpoint_knot; + mp_right_type(ll) = mp_endpoint_knot; + mp_originator(ll) = mp_program_code; + mp_knotstate(ll) = mp_regular_knot; + number_clone(ll->x_coord, mp_minx); + number_clone(ll->y_coord, mp_miny); + mp_originator(lr) = mp_program_code; + mp_knotstate(lr) = mp_regular_knot; + number_clone(lr->x_coord, mp_maxx); + number_clone(lr->y_coord, mp_miny); + mp_originator(ur) = mp_program_code; + mp_knotstate(ur) = mp_regular_knot; + number_clone(ur->x_coord, mp_maxx); + number_clone(ur->y_coord, mp_maxy); + mp_originator(ul) = mp_program_code; + mp_knotstate(ul) = mp_regular_knot; + number_clone(ul->x_coord, mp_minx); + number_clone(ul->y_coord, mp_maxy); + mp_prev_knot(lr) = ll; + mp_next_knot(ll) = lr; + mp_prev_knot(ur) = lr; + mp_next_knot(lr) = ur; + mp_prev_knot(ul) = ur; + mp_next_knot(ur) = ul; + mp_close_path_cycle(mp, ul, ll); + mp_make_path(mp,ll); + mp->cur_exp.type = mp_path_type; + mp_set_cur_exp_knot(mp, ll); + mp_free_path(mp,qq); + } + } +} + +@ @<Declare binary action...@>= +static void mp_find_point (MP mp, mp_number *v_orig, int c) +{ + mp_knot p; /* the path */ + mp_number n; /* its length */ + mp_number v; + new_number(n); + new_number_clone(v, *v_orig); + p = cur_exp_knot; + if (mp_left_type(p) == mp_endpoint_knot) { + set_number_to_unity(n); + number_negate(n); + } + do { + p = mp_next_knot(p); + number_add(n, unity_t); + } while (p != cur_exp_knot); + if (number_zero(n)) { + set_number_to_zero(v); + } else if (number_negative(v)) { + if (mp_left_type(p) == mp_endpoint_knot) { + set_number_to_zero(v); + } else { + /* |v = n - 1 - ((-v - 1) % n) == - ((-v - 1) % n) - 1 + n| */ + number_negate(v); + number_add_scaled(v, -1); + number_modulo(v, n); + number_negate(v); + number_add_scaled(v, -1); + number_add(v, n); + } + } else if (number_greater(v, n)) { + if (mp_left_type(p) == mp_endpoint_knot) { + number_clone(v, n); + } else { + number_modulo(v, n); + } + } + p = cur_exp_knot; + while (number_greaterequal(v, unity_t)) { + p = mp_next_knot(p); + number_subtract(v, unity_t); + } + if (number_nonzero(v)) { + /* Insert a fractional node by splitting the cubic */ + convert_scaled_to_fraction(v); + mp_split_cubic(mp, p, &v); + p = mp_next_knot(p); + } + /* Set the current expression to the desired path coordinates */ + push_of_path_result(mp, c - mp_point_operation, p); + free_number(v); + free_number(n); +} + +@* 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 \quote {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 +static void worry_about_bad_statement (MP mp); + +static void flush_unparsable_junk_after_statement (MP mp); + +void mp_do_statement (MP mp) +{ + /* governs \MP's activities */ + mp->cur_exp.type = mp_vacuous_type; + mp_get_x_next(mp); + if (cur_cmd > mp_max_primary_command) { + worry_about_bad_statement(mp); + } else if (cur_cmd > mp_max_statement_command) { + /* + Do an equation, assignment, title, or + `$\langle\,$expression$\,\rangle\,$|endgroup|'; The most important + statements begin with expressions + */ + mp_value new_expr; + mp->var_flag = mp_assignment_command; + mp_scan_expression(mp); + if (cur_cmd < mp_end_group_command) { + if (cur_cmd == mp_equals_command) { + mp_do_equation(mp); + } else if (cur_cmd == mp_assignment_command) { + mp_do_assignment(mp); + } else if (mp->cur_exp.type == mp_string_type) { + /* Do a title */ + if (number_positive(internal_value(mp_tracing_titles_internal))) { + mp_print_nl(mp, ""); + mp_print_mp_str(mp, cur_exp_str); + update_terminal(); + } + } else if (mp->cur_exp.type != mp_vacuous_type) { + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Isolated expression", + "I couldn't find an '=' or ':=' after the expression that is shown above this\n" + "error message, so I guess I'll just ignore it and carry on." + ); + mp_get_x_next(mp); + } + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_flush_cur_exp(mp, new_expr); + mp->cur_exp.type = mp_vacuous_type; + } + } else { + /* + Do a statement that doesn't begin with an expression. 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. + */ + if (number_positive(internal_value(mp_tracing_commands_internal))) { + mp_show_cmd_mod(mp, cur_cmd, cur_mod); + } + switch (cur_cmd) { + case mp_type_name_command: + mp_do_type_declaration(mp); + break; + case mp_macro_def_command: + switch (cur_mod) { + case mp_def_code: + case mp_var_def_code: + mp_scan_def(mp, cur_mod); + break; + case mp_primary_def_code: + case mp_secondary_def_code: + case mp_tertiary_def_code: + mp_make_op_def(mp, cur_mod); + break; + } + break; + case mp_only_set_command: + switch (cur_mod) { + case mp_random_seed_code: + mp_do_random_seed(mp); + break; + case mp_max_knot_pool_code: + mp_do_max_knot_pool(mp); + break; + } + break; + case mp_mode_command: + mp_print_ln(mp); + mp->interaction = cur_mod; + mp->selector = mp->interaction == mp_batch_mode ? mp_log_only_selector : mp_term_and_log_selector; + mp_get_x_next(mp); + break; + case mp_protection_command: + mp_do_protection(mp); + break; + case mp_property_command: + mp_do_property(mp); + break; + case mp_delimiters_command: + mp_def_delims(mp); + break; + case mp_save_command: + do { + mp_get_symbol(mp); + mp_save_variable(mp, cur_sym); + mp_get_x_next(mp); + } while (cur_cmd == mp_comma_command); + break; + case mp_interim_command: + mp_do_interim(mp); + break; + case mp_let_command: + mp_do_let(mp); + break; + case mp_new_internal_command: + mp_do_new_internal(mp); + break; + case mp_show_command: + mp_do_show_whatever(mp); + break; + case mp_add_to_command: + mp_do_add_to(mp); + break; + case mp_bounds_command: + mp_do_bounds(mp); + break; + case mp_ship_out_command: + mp_do_ship_out(mp); + break; + case mp_every_job_command: + mp_get_symbol(mp); + mp->every_job_sym = cur_sym; + mp_get_x_next(mp); + break; + case mp_message_command: + mp_do_message(mp); + break; + case mp_write_command: + mp_do_write(mp); + break; + default: + break; + } + mp->cur_exp.type = mp_vacuous_type; + } + if (cur_cmd < mp_semicolon_command) { + flush_unparsable_junk_after_statement(mp); + } + 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. + +@c +static void worry_about_bad_statement (MP mp) +{ + if (cur_cmd < mp_semicolon_command) { + char msg[256]; + mp_string sname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_print_cmd_mod(mp, cur_cmd, cur_mod); + sname = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "A statement can't begin with '%s'", mp_str(mp, sname)); + delete_str_ref(sname); + mp_back_error( + mp, + msg, + "I was looking for the beginning of a new statement. If you just proceed without\n" + "changing anything, I'll ignore everything up to the next ';'." + ); + 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. + +@c +static void flush_unparsable_junk_after_statement (MP mp) +{ + mp_back_error( + mp, + "Extra tokens will be flushed", + "I've just read as much of that statement as I could fathom, so a semicolon should\n" + "have been next. It's very puzzling ... but I'll try to get myself back together,\n" + "by ignoring everything up to the next ';'." + ); + mp->scanner_status = mp_flushing_state; + do { + get_t_next(mp); + if (cur_cmd == mp_string_command) { + delete_str_ref(cur_mod_str); + } + } while (! mp_end_of_statement); /* |cur_cmd=semicolon|, |end_group|, or |stop| */ + mp->scanner_status = mp_normal_state; +} + +@ 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 +static void trace_equation (MP mp, mp_node lhs) +{ + mp_begin_diagnostic(mp); + mp_print_nl(mp, "{("); + mp_print_exp(mp, lhs, 0); + mp_print_str(mp, ")=("); + mp_print_exp(mp, NULL, 0); + mp_print_str(mp, ")}"); + mp_end_diagnostic(mp, 0); +} + +void mp_do_equation (MP mp) +{ + mp_node lhs = mp_stash_cur_exp(mp); /* capsule for the left-hand side */ + mp_get_x_next(mp); + mp->var_flag = mp_assignment_command; + mp_scan_expression(mp); + if (cur_cmd == mp_equals_command) { + mp_do_equation(mp); + } else if (cur_cmd == mp_assignment_command) { + mp_do_assignment(mp); + } + if (number_greater(internal_value(mp_tracing_commands_internal), two_t)) { + trace_equation(mp, lhs); + } + if (mp->cur_exp.type == mp_unknown_path_type) { + if (mp_type(lhs) == mp_pair_type) { + mp_node p; /* temporary register */ + 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 +static void bad_lhs (MP mp) +{ + mp_disp_err(mp, NULL); + mp_error( + mp, + "Improper ':=' will be changed to '='", + "I didn't find a variable name at the left of the ':=', so I'm going to pretend\n" + "that you said '=' instead." + ); + mp_do_equation(mp); +} + +static void bad_internal_assignment (MP mp, mp_node lhs) +{ + char msg[256]; + mp_disp_err(mp, NULL); + if (internal_type(mp_get_sym_info(lhs)) == mp_known_type) { + mp_snprintf(msg, 256, + "Internal quantity '%s' must receive a known numeric value", + internal_name(mp_get_sym_info(lhs)) + ); + mp_back_error( + mp, + msg, + "I can't set this internal quantity to anything but a known numeric value, so I'll\n" + "have to ignore this assignment." + ); + } else if (internal_type(mp_get_sym_info(lhs)) == mp_boolean_type) { + mp_snprintf(msg, 256, + "Internal quantity '%s' must receive a known boolean value", + internal_name(mp_get_sym_info(lhs)) + ); + mp_back_error( + mp, + msg, + "I can't set this internal quantity to anything but a known boolean value, so I'll\n" + "have to ignore this assignment." + ); + } else { + mp_snprintf(msg, 256, + "Internal quantity '%s' must receive a known string", + internal_name(mp_get_sym_info(lhs)) + ); + mp_back_error( + mp, + msg, + "I can't set this internal quantity to anything but a known string value, so I'll\n" + "have to ignore this assignment." + ); + } + mp_get_x_next(mp); +} + +static void forbidden_internal_assignment (MP mp, mp_node lhs) +{ + char msg[256]; + mp_snprintf(msg, 256,"Internal quantity '%s' is read-only", internal_name(mp_get_sym_info(lhs))); + mp_back_error( + mp, + msg, + "I can't set this internal quantity to anything just yet (it is read-only), so\n" + "I'll have to ignore this assignment." + ); + mp_get_x_next(mp); +} + +static void bad_internal_assignment_precision (MP mp, mp_node lhs, mp_number *min, mp_number *max) +{ + char msg[256]; + char hlp[256]; + mp_snprintf(msg, 256, + "Bad '%s' has been ignored", + internal_name(mp_get_sym_info(lhs))); + mp_snprintf(hlp, 256, + "Precision values are limited by the current numbersystem.\n" + "Currently I am using '%s'; the allowed precision range is [%s,%s].", + mp_str(mp, internal_string(mp_number_system_internal)), number_tostring(*min), number_tostring(*max)); + mp_back_error(mp, msg, hlp); + mp_get_x_next(mp); +} + +static void bad_expression_assignment (MP mp, mp_node lhs) +{ + char *msg = mp_obliterated(mp, lhs); + mp_back_error( + mp, + msg, + "It seems you did a nasty thing --- probably by accident, but nevertheless you\n" + "nearly hornswoggled me ... While I was evaluating the right-hand side of this\n" + "command, something happened, and the left-hand side is no longer a variable! So I\n" + "won't change anything." + ); + mp_memory_free(msg); + mp_get_x_next(mp); +} + +static void trace_assignment (MP mp, mp_node lhs) +{ + mp_begin_diagnostic(mp); + mp_print_nl(mp, "{"); + if (mp_name_type(lhs) == mp_internal_operation) { + mp_print_str(mp, internal_name(mp_get_sym_info(lhs))); + } else { + mp_show_token_list(mp, lhs, NULL); + } + mp_print_str(mp, ":="); + mp_print_exp(mp, NULL, 0); + mp_print_chr(mp, '}'); + mp_end_diagnostic(mp, 0); +} + +void mp_do_assignment (MP mp) +{ + if (mp->cur_exp.type != mp_token_list_type) { + bad_lhs(mp); + } else { + mp_node lhs = cur_exp_node; /* token list for the left-hand side */ + mp->cur_exp.type = mp_vacuous_type; + mp_get_x_next(mp); + mp->var_flag = mp_assignment_command; + mp_scan_expression(mp); + if (cur_cmd == mp_equals_command) { + mp_do_equation(mp); + } else if (cur_cmd == mp_assignment_command) { + mp_do_assignment(mp); + } + if (number_greater(internal_value(mp_tracing_commands_internal), two_t)) { + trace_assignment (mp, lhs); + } + if (mp_name_type(lhs) == mp_internal_operation) { + /* Assign the current expression to an internal variable */ + switch (mp->cur_exp.type) { + case mp_known_type: + case mp_string_type: + case mp_boolean_type: + if (internal_type(mp_get_sym_info(lhs)) == mp->cur_exp.type) { + switch (mp_get_sym_info(lhs)) { + case mp_number_system_internal: + forbidden_internal_assignment(mp, lhs); + break; + // case mp_tracing_online_internal: + // number_clone(internal_value(mp_get_sym_info(lhs)), cur_exp_value_number); + // mp->run_internal(mp, 3, mp->int_ptr, number_to_int(internal_value(mp_get_sym_info(lhs))), internal_name(mp_get_sym_info(lhs))); + // break; + case mp_number_precision_internal: + if (mp->cur_exp.type == mp_known_type + && (! number_less (cur_exp_value_number, precision_min)) + && (! number_greater(cur_exp_value_number, precision_max)) + ) { + if (internal_type(mp_get_sym_info(lhs)) == mp_string_type) { + add_str_ref(cur_exp_str); + set_internal_string(mp_get_sym_info(lhs), cur_exp_str); + } else { + number_clone(internal_value(mp_get_sym_info(lhs)), cur_exp_value_number); + } + set_precision(); + } else { + bad_internal_assignment_precision(mp, lhs, &precision_min, &precision_max); + } + default: + if (internal_type(mp_get_sym_info(lhs)) == mp_string_type) { + add_str_ref(cur_exp_str); + set_internal_string(mp_get_sym_info(lhs), cur_exp_str); + } else { + number_clone(internal_value(mp_get_sym_info(lhs)), cur_exp_value_number); + } + break; + } + } else { + bad_internal_assignment(mp, lhs); + } + break; + default: + bad_internal_assignment(mp, lhs); + } + } else { + /* Assign the current expression to the variable |lhs| */ + mp_node p = mp_find_variable(mp, lhs); /* where the left-hand value is stored */ + if (p != NULL) { + mp_node q = mp_stash_cur_exp(mp); /* temporary capsule for the right-hand value */ + mp->cur_exp.type = mp_und_type(mp, p); + mp_recycle_value(mp, p); + mp_type(p) = mp->cur_exp.type; + mp_set_value_number(p, zero_t); + mp_make_exp_copy(mp, p); + p = mp_stash_cur_exp(mp); + mp_unstash_cur_exp(mp, q); + mp_make_eq(mp, p); + } else { + bad_expression_assignment(mp, lhs); + } + } + mp_flush_node_list(mp, lhs); + } +} + +@ 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 +static void announce_bad_equation (MP mp, mp_node lhs) +{ + char msg[256]; + mp_snprintf(msg, 256, + "Equation cannot be performed (%s=%s)", + (mp_type(lhs) <= mp_pair_type ? mp_type_string(mp_type(lhs)) : "numeric"), + (mp->cur_exp.type <= mp_pair_type ? mp_type_string(mp->cur_exp.type) : "numeric")); + mp_disp_err(mp, lhs); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + msg, + "I'm sorry, but I don't know how to make such things equal. (See the two\n" + "expressions just above the error message.)" + ); + mp_get_x_next(mp); +} + +static void mp_exclaim_inconsistent_equation (MP mp) +{ + mp_back_error( + mp, + "Inconsistent equation", + "The equation I just read contradicts what was said before. But don't worry;\n" + "continue and I'll just ignore it." + ); + mp_get_x_next(mp); +} + +static void mp_exclaim_redundant_or_inconsistent_equation (MP mp) +{ + mp_back_error( + mp, + "Redundant or inconsistent equation", + "An equation between already-known quantities can't help. But don't worry;\n" + "continue and I'll just ignore it." + ); + mp_get_x_next(mp); +} + +static void report_redundant_or_inconsistent_equation (MP mp, mp_node lhs, mp_number *v) +{ + if (mp->cur_exp.type <= mp_string_type) { + if (mp->cur_exp.type == mp_string_type) { + if (mp_str_vs_str(mp, mp_get_value_str(lhs), cur_exp_str) != 0) { + mp_exclaim_inconsistent_equation(mp); + } else { + mp_exclaim_redundant_equation(mp); + } + } else if (number_equal(*v, cur_exp_value_number)) { + mp_exclaim_redundant_equation(mp); + } else { + mp_exclaim_inconsistent_equation(mp); + } + } else { + mp_exclaim_redundant_or_inconsistent_equation(mp); + } +} + +void mp_make_eq (MP mp, mp_node lhs) +{ + mp_value new_expr; + mp_variable_type t; /* type of the left-hand side */ + mp_number v; /* value of the left-hand side */ + memset(&new_expr, 0, sizeof(mp_value)); + new_number(v); + RESTART: + t = mp_type(lhs); + if (t <= mp_pair_type) { + number_clone(v, mp_get_value_number(lhs)); + } + /* + For each type |t|, make an equation or complain if |cur_type| is + incompatible with~|t| + */ + switch (t) { + case mp_boolean_type: + case mp_string_type: + case mp_pen_type: + case mp_nep_type: + case mp_path_type: + case mp_picture_type: + if (mp->cur_exp.type == t + unknown_tag) { + new_number(new_expr.data.n); + switch (t) { + case mp_boolean_type: + number_clone(new_expr.data.n, v); + break; + case mp_string_type: + new_expr.data.str = mp_get_value_str(lhs); + break; + case mp_picture_type: + new_expr.data.node = mp_get_value_node(lhs); + break; + default: + /* pen or path */ + new_expr.data.p = mp_get_value_knot(lhs); + break; + } + mp_nonlinear_eq(mp, new_expr, cur_exp_node, 0); + mp_unstash_cur_exp(mp, cur_exp_node); + } else if (mp->cur_exp.type == t) { + report_redundant_or_inconsistent_equation(mp, lhs, &v); + } else { + announce_bad_equation(mp, lhs); + } + break; + case mp_unknown_boolean_type: + case mp_unknown_string_type: + case mp_unknown_pen_type: + case mp_unknown_nep_type: + case mp_unknown_path_type: + case mp_unknown_picture_type: + if (mp->cur_exp.type == t - unknown_tag) { + mp_nonlinear_eq(mp, mp->cur_exp, lhs, 1); + } else if (mp->cur_exp.type == t) { + mp_ring_merge (mp, lhs, cur_exp_node); + } else if (mp->cur_exp.type == mp_pair_type) { + if (t == mp_unknown_path_type) { + mp_pair_to_path(mp); + goto RESTART; + } + } else { + announce_bad_equation(mp, lhs); + } + 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 */ + mp_node q = mp_get_value_node(cur_exp_node); + mp_node p = mp_get_value_node(lhs); + switch (t) { + case mp_transform_type: + mp_try_eq(mp, mp_yy_part(p), mp_yy_part(q)); + mp_try_eq(mp, mp_yx_part(p), mp_yx_part(q)); + mp_try_eq(mp, mp_xy_part(p), mp_xy_part(q)); + mp_try_eq(mp, mp_xx_part(p), mp_xx_part(q)); + mp_try_eq(mp, mp_ty_part(p), mp_ty_part(q)); + mp_try_eq(mp, mp_tx_part(p), mp_tx_part(q)); + break; + case mp_color_type: + mp_try_eq(mp, mp_blue_part(p), mp_blue_part(q)); + mp_try_eq(mp, mp_green_part(p), mp_green_part(q)); + mp_try_eq(mp, mp_red_part(p), mp_red_part(q)); + break; + case mp_cmykcolor_type: + mp_try_eq(mp, mp_black_part(p), mp_black_part(q)); + mp_try_eq(mp, mp_yellow_part(p), mp_yellow_part(q)); + mp_try_eq(mp, mp_magenta_part(p), mp_magenta_part(q)); + mp_try_eq(mp, mp_cyan_part(p), mp_cyan_part(q)); + break; + case mp_pair_type: + mp_try_eq(mp, mp_y_part(p), mp_y_part(q)); + mp_try_eq(mp, mp_x_part(p), mp_x_part(q)); + break; + default: + break; + } + } else { + announce_bad_equation(mp, lhs); + } + break; + case mp_known_type: + case mp_dependent_type: + case mp_proto_dependent_type: + case mp_independent_type: + if (mp->cur_exp.type >= mp_known_type) { + mp_try_eq(mp, lhs, NULL); + } else { + announce_bad_equation(mp, lhs); + } + break; + case mp_vacuous_type: + announce_bad_equation(mp, lhs); + break; + default: + announce_bad_equation(mp, lhs); + break; + } + check_arith(); + mp_recycle_value(mp, lhs); + free_number(v); + mp_free_value_node(mp, lhs); +} + +@ 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); + +@d equation_threshold_k mp->math->md_equation_threshold_t + +@ @c +static void deal_with_redundant_or_inconsistent_equation (MP mp, mp_value_node p, mp_node r) +{ + mp_number absp; + new_number_abs(absp, mp_get_value_number(p)); + if (number_greater(absp, equation_threshold_k)) { /* off by .001 or more */ + char msg[256]; + mp_snprintf(msg, 256, "Inconsistent equation (off by %s)", number_tostring (mp_get_value_number(p))); + mp_back_error( + mp, + msg, + "The equation I just read contradicts what was said before. But don't worry;\n" + "continue and I'll just ignore it." + ); + mp_get_x_next(mp); + } else if (r == NULL) { + mp_exclaim_redundant_equation(mp); + } + free_number(absp); + mp_free_dep_node(mp, p); +} + +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_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| */ + int 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| + */ + mp_variable_type t = mp_type(l); /* the type of list |p| */ + switch (t) { + case mp_known_type: + { + mp_number arg1; + new_number(arg1); + number_negated_clone(arg1, mp_get_value_number(l)); + t = mp_dependent_type; + p = mp_const_dependency(mp, &arg1); + q = p; + free_number(arg1); + } + break; + case mp_independent_type: + { + t = mp_dependent_type; + p = mp_single_dependency(mp, l); + number_negate(mp_get_dep_value(p)); + q = mp->dep_final; + } + break; + default: + { + mp_value_node ll = (mp_value_node) l; + p = (mp_value_node) mp_get_dep_list(ll); + q = p; + while (1) { + number_negate(mp_get_dep_value(q)); + if (mp_get_dep_info(q) == NULL) { + break; + } else { + q = (mp_value_node) mp_link(q); + } + } + mp_link(mp_get_prev_dep(ll)) = mp_link(q); + mp_set_prev_dep((mp_value_node) mp_link(q), mp_get_prev_dep(ll)); + mp_type(ll) = mp_known_type; + } + break; + } + /* Add the right operand to list |p| */ + if (r == NULL) { + if (mp->cur_exp.type == mp_known_type) { + number_add(mp_get_value_number(q), cur_exp_value_number); + goto DONE1; + } else { + tt = mp->cur_exp.type; + if (tt == mp_independent_type) { + pp = mp_single_dependency(mp, cur_exp_node); + } else { + pp = (mp_value_node) mp_get_dep_list((mp_value_node) cur_exp_node); + } + } + } else if (mp_type(r) == mp_known_type) { + number_add(mp_get_dep_value(q), mp_get_value_number(r)); + goto DONE1; + } else { + tt = mp_type(r); + if (tt == mp_independent_type) { + pp = mp_single_dependency(mp, r); + } else { + pp = (mp_value_node) mp_get_dep_list((mp_value_node) r); + } + } + if (tt != mp_independent_type) { + copied = 0; + } else { + copied = 1; + tt = mp_dependent_type; + } + /* Add dependency list |pp| of type |tt| to dependency list~|p| of type~|t| */ + mp->watch_coefs = 0; + if (t == tt) { + p = mp_p_plus_q(mp, p, pp, (int) t); + } else if (t == mp_proto_dependent_type) { + p = mp_p_plus_fq(mp, p, &unity_t, pp, mp_proto_dependent_type, mp_dependent_type); + } else { + mp_number x; + new_number(x); + q = p; + while (mp_get_dep_info(q) != NULL) { + number_clone(x, mp_get_dep_value(q)); + fraction_to_round_scaled(x); + mp_set_dep_value(q, x); + q = (mp_value_node) mp_link(q); + } + free_number(x); + t = mp_proto_dependent_type; + p = mp_p_plus_q(mp, p, pp, (int) t); + } + mp->watch_coefs = 1; + if (copied) { + mp_flush_node_list(mp, (mp_node) pp); + } + DONE1: + if (mp_get_dep_info(p) == NULL) { + deal_with_redundant_or_inconsistent_equation(mp, p, r); + } else { + mp_linear_eq(mp, p, (int) t); + if (r == NULL && mp->cur_exp.type != mp_known_type && mp_type(cur_exp_node) == mp_known_type) { + mp_node pp = cur_exp_node; + mp_set_cur_exp_value_number(mp, &(mp_get_value_number(pp))); + mp->cur_exp.type = mp_known_type; + mp_free_value_node(mp, pp); + } + } +} + +@ 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 = cur_sym; + if (cur_cmd != mp_tag_command) { + mp_clear_symbol(mp, x, 0); + } + h = mp_new_symbolic_node(mp); + mp_set_sym_sym(h, x); + t = h; + while (1) { + mp_get_x_next(mp); + if (cur_sym == NULL) { + break; + } else if (cur_cmd != mp_tag_command) { + /* could be smarter: */ + if (cur_cmd != mp_internal_command) { + if (cur_cmd == mp_left_bracket_command) { + /* + Descend past a collective subscript If the subscript + isn't collective, we don't accept it as part of the + declared variable. + */ + mp_sym ll = cur_sym; /* hash address of left bracket */ + mp_get_x_next(mp); + if (cur_cmd == mp_right_bracket_command) { + set_cur_sym(mp_collective_subscript); + } else { + mp_back_input(mp); + set_cur_sym(ll); + set_cur_cmd(mp_left_bracket_command); + break; + } + } else { + break; + } + } + } + mp_link(t) = mp_new_symbolic_node(mp); + t = mp_link(t); + mp_set_sym_sym(t, cur_sym); + mp_name_type(t) = cur_sym_mod; + } + if (eq_property(x) != 0) { + mp_check_overload(mp, x); + } + // if ((eq_type(x) % mp_outer_tag_command) != mp_tag_command) { + if (eq_type(x) != mp_tag_command) { + mp_clear_symbol(mp, x, 0); + } + if (equiv_node(x) == NULL) { + mp_new_root (mp, x); + } + return h; +} + +@ Type declarations are introduced by the following primitive operations. + +@ @<Put each...@>= +mp_primitive(mp, "numeric", mp_type_name_command, mp_numeric_type_operation); +@:numeric_}{|numeric| primitive@> +mp_primitive(mp, "string", mp_type_name_command, mp_string_type_operation); +@:string_}{|string| primitive@> +mp_primitive(mp, "boolean", mp_type_name_command, mp_boolean_type_operation); +@:boolean_}{|boolean| primitive@> +mp_primitive(mp, "path", mp_type_name_command, mp_path_type_operation); +@:path_}{|path| primitive@> +mp_primitive(mp, "pen", mp_type_name_command, mp_pen_type_operation); +@:pen_}{|pen| primitive@> +mp_primitive(mp, "nep", mp_type_name_command, mp_nep_type_operation); +@:nep_}{|nep| primitive@> +mp_primitive(mp, "picture", mp_type_name_command, mp_picture_type_operation); +@:picture_}{|picture| primitive@> +mp_primitive(mp, "transform", mp_type_name_command, mp_transform_type_operation); +@:transform_}{|transform| primitive@> +mp_primitive(mp, "color", mp_type_name_command, mp_color_type_operation); +@:color_}{|color| primitive@> +mp_primitive(mp, "rgbcolor", mp_type_name_command, mp_color_type_operation); +@:color_}{|rgbcolor| primitive@> +mp_primitive(mp, "cmykcolor", mp_type_name_command, mp_cmykcolor_type_operation); +@:color_}{|cmykcolor| primitive@> +mp_primitive(mp, "pair", mp_type_name_command, mp_pair_type_operation); +@:pair_}{|pair| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_type_name_command: + /* return mp_type_string(mp, m): */ + return ""; + +@ Now we are ready to handle type declarations, assuming that a |type_name| has +just been scanned. We don't use the type to operation mix here, we just have +am extra set of operations and a switch that maps on type. + +@<Declare action procedures for use by |do_statement|@>= +static void mp_do_type_declaration (MP mp); + +@ @c +static void flush_spurious_symbols_after_declared_variable (MP mp); + +void mp_do_type_declaration (MP mp) +{ + int t = mp_numeric_type; /* cur_mod >= mp_transform_type ? cur_mod : cur_mod + unknown_tag; */ /* the type being declared */ + switch (cur_mod) { + case mp_string_type_operation: t = mp_unknown_string_type; break; + case mp_boolean_type_operation: t = mp_unknown_boolean_type; break; + case mp_path_type_operation: t = mp_unknown_path_type; break; + case mp_pen_type_operation: t = mp_unknown_pen_type; break; + case mp_nep_type_operation: t = mp_unknown_nep_type; break; + case mp_picture_type_operation: t = mp_unknown_picture_type; break; + case mp_transform_type_operation: t = mp_transform_type; break; + case mp_color_type_operation: t = mp_color_type; break; + case mp_cmykcolor_type_operation: t = mp_cmykcolor_type; break; + case mp_pair_type_operation: t = mp_pair_type; break; + case mp_numeric_type_operation: t = mp_numeric_type; break; + } + + do { + mp_node p = mp_scan_declared_variable(mp); /* token list for a declared variable */ + mp_node q; /* value node for the variable */ + mp_flush_variable(mp, equiv_node(mp_get_sym_sym(p)), mp_link(p), 0); + q = mp_find_variable(mp, p); + if (q != NULL) { + mp_type(q) = t; + mp_set_value_number(q, zero_t); /* todo: this was |null| */ + } else { + mp_back_error( + mp, + "Declared variable conflicts with previous vardef", + "You can't use, e.g., 'numeric foo[]' after 'vardef foo'. Proceed, and I'll ignore\n" + "the illegal redeclaration." + ); + mp_get_x_next(mp); + } + mp_flush_node_list(mp, p); + if (cur_cmd < mp_comma_command) { + flush_spurious_symbols_after_declared_variable(mp); + } + } while (! mp_end_of_statement); +} + +@ +@c +static void flush_spurious_symbols_after_declared_variable (MP mp) +{ + const char *hlp = NULL; + if (cur_cmd == mp_numeric_command) { + hlp = + "Variables in declarations must consist entirely of names and explicit subscripts\n" + "like 'x15a' aren't permitted. I'm going to discard the junk I found here, up to the\n" + "next comma or the end of the declaration."; + } else { + hlp = + "Variables in declarations must consist entirely of names and collective\n" + "subscripts, e.g., 'x[]a'. Are you trying to use a reserved word in a variable\n" + "name? I'm going to discard the junk I found here, up to the next comma or the end\n" + "of the declaration."; + } + mp_back_error( + mp, + "Illegal suffix of declared variable will be flushed", + hlp + ); + mp_get_x_next(mp); + mp->scanner_status = mp_flushing_state; + do { + get_t_next(mp); + @<Decrease the string reference count...@> + } while (cur_cmd < mp_comma_command); /* break on either |end_of_statement| or |comma| */ + mp->scanner_status = mp_normal_state; +} + +@ \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) { + do { + mp_do_statement(mp); + if (cur_cmd == mp_end_group_command) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_error( + mp, + "Extra 'endgroup'", + "I'm not currently working on a 'begingroup', so I had better not try to end\n" + "anything." + ); + mp_flush_cur_exp(mp, new_expr); + } + } while (cur_cmd != mp_stop_command); +} + +int mp_run (MP mp) +{ + if (mp->history < mp_fatal_error_stop) { + mp_memory_free(mp->jump_buf); + mp->jump_buf = mp_memory_allocate(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, 0); + if (p == NULL) { + errid = "variable does not exist"; + } else if (eq_type(p) != mp_internal_command) { + errid = "variable is not an internal"; + } else if ((internal_type(equiv(p)) == mp_string_type) && (isstring)) { + set_internal_string(equiv(p), mp_rts(mp, v)); + } else if ((internal_type(equiv(p)) == mp_known_type) && (! isstring)) { + int test = atoi(v); + if (test > 16383 && mp->math_mode == mp_math_scaled_mode) { + errid = "value is too large"; + } else if (test < -16383 && mp->math_mode == mp_math_scaled_mode) { + errid = "value is too small"; + } else { + number_clone(internal_value(equiv(p)), unity_t); + number_multiply_int(internal_value(equiv(p)), test); + } + } else { + errid = "value has the wrong type"; + } + } + 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@>= +# undef term_in +# undef term_out + +typedef struct mp_run_data +{ + void *term_in; /* dummy pointer */ + 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. + +@ 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|. + +@<Exported function headers@>= +void mplib_shipout_backend (MP mp, void *h); + +@ This is where we fill them all in. + +@<Set default function pointers@>= +mp->find_file = mp_find_file; +mp->open_file = mp_open_file; +mp->close_file = mp_close_file; +mp->write_file = mp_write_file; +mp->read_file = mp_read_file; +mp->run_script = mp_run_script; +mp->run_internal = mp_run_internal; +mp->run_logger = mp_run_logger; +mp->run_overload = mp_run_overload; +mp->run_error = mp_run_error; +mp->run_warning = mp_run_warning; +mp->make_text = mp_make_text; +mp->shipout_backend = mp_shipout_backend; + +mp->find_file_id = 0; +mp->run_script_id = 0; +mp->run_logger_id = 0; +mp->run_error_id = 0; +mp->run_warning_id = 0; +mp->run_overload_id = 0; +mp->make_text_id = 0; +mp->open_file_id = 0; + +@ This might change too. + +@c +void mplib_shipout_backend (MP mp, void *voidh) +{ + mp_edge_header_node h = (mp_edge_header_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; + } + } +} + +@ 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); +} + +@ @<Finish non-interactive use@>= +mp_memory_free(mp->term_in); +mp->term_in = NULL; + +@ @<Start non-interactive work@>= +@<Initialize the output routines@> +mp->input_ptr = 0; +mp->max_in_stack = mp_file_bottom_text; +mp->in_open = mp_file_bottom_text; +mp->open_parens = 0; +mp->max_buf_stack = 0; +mp->param_ptr = 0; +mp->max_param_stack = 0; +start = loc = 0; +iindex = mp_file_bottom_text; +nloc = nstart = NULL; +mp->first = 0; +line = 0; +name = is_term; +mp->force_eof = 0; +if (mp->term_in == NULL) { + mp->term_in = (mp->open_file)(mp,"terminal", "r", mp_filetype_terminal); +} +mp->last = 0; +mp->scanner_status = mp_normal_state; +mp_fix_date_and_time(mp); +if (mp->random_seed == 0) { + mp->random_seed = (number_to_scaled(internal_value(mp_time_internal))/number_to_scaled(unity_t)) + + number_to_scaled(internal_value(mp_day_internal)); +} +init_randoms(mp->random_seed); +mp->selector = mp->interaction == mp_batch_mode ? mp_no_print_selector : mp_term_only_selector; +mp->history = mp_spotless; +if (mp->every_job_sym != NULL) { + set_cur_sym(mp->every_job_sym); + mp_back_input(mp); +} + +@ @c +int mp_execute (MP mp, const char *s, size_t l) +{ + (void) l; + if (mp->finished) { + return mp->history; + } else if (mp->history < mp_fatal_error_stop) { + mp_memory_free(mp->jump_buf); + mp->jump_buf = mp_memory_allocate(sizeof(jmp_buf)); + if (mp->jump_buf == NULL || setjmp (*(mp->jump_buf)) != 0) { + return mp->history; + } else { + mp->term_offset = 0; + mp->file_offset = 0; + if (mp->term_in == NULL) { + mp->term_in = (mp->open_file)(mp,"terminal", "r", mp_filetype_terminal); + mp->last = 0; + } + if (s && l > 0) { + (mp->write_file)(mp, mp->term_in, s); + } else { + /* we already filled the terminal buffer, so no longer: */ + /* + mp_final_cleanup(mp); + mp_close_files_and_terminate(mp); + return mp->history; + */ + } + if (mp->run_state == 0) { + /* mp->selector = mp_term_only_selector; */ + @<Start non-interactive work@> + } + mp->run_state = 1; + /* we grab one line */ + mp_input_ln(mp, mp->term_in); + mp_firm_up_the_line(mp); + mp->buffer[limit] = '%'; + mp->first = (size_t) (limit + 1); + loc = start; + do { + mp_do_statement(mp); + } while (cur_cmd != mp_stop_command); + 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); + } else { + mp_memory_free(mp->jump_buf); + mp->jump_buf = mp_memory_allocate(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, const char *s, size_t l); +int mp_finish (MP mp); +char *mp_metapost_version (void); + +@ @<Put each...@>= +mp_primitive(mp, "end", mp_stop_command, 0); +@:end_}{|end| primitive@> +mp_primitive(mp, "dump", mp_stop_command, 1); +mp->frozen_dump = mp_frozen_primitive (mp, "dump", mp_stop_command, 1); +@:dump_}{|dump| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_stop_command: + return cur_mod == 0 ? "end" : "dump"; + +@* Commands. + +Let's turn now to statements that are classified as \quote {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 (when we have more seters thsi will change into one +function and a genericmessage). + +@ @<Declare action procedures for use by |do_statement|@>= +static void mp_do_max_knot_pool (MP mp); + +@ @c +void mp_do_max_knot_pool (MP mp) +{ + /* similar to the random seed setter */ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_get_x_next(mp); + if (cur_cmd != mp_assignment_command) { + mp_back_error( + mp, + "Missing ':=' has been inserted", + "Always say 'maxknotpool := <numeric expression>'." + ); + @.Missing `:='@> + }; + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type != mp_known_type) { + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Unknown value will be ignored", + "Your expression was too random for me to handle, so I won't change the maximum\n" + "seed just now." + ); + @.Unknown value...ignored@> + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else { + /* the action */ + int p = (int) number_to_scaled (cur_exp_value_number) / 65536; + if (p > mp->max_knot_nodes) { + mp->max_knot_nodes = p; + } else if (p > max_num_knot_nodes) { + /* not now: flush excess nodes */ + } else { + /* we always keep the minimum */ + } + } +} + +@ @<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)); + new_number(new_expr.data.n); + mp_get_x_next(mp); + if (cur_cmd != mp_assignment_command) { + mp_back_error( + mp, + "Missing ':=' has been inserted", + "Always say 'randomseed := <numeric expression>'." + ); + @.Missing `:='@> + }; + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type != mp_known_type) { + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Unknown value will be ignored", + "Your expression was too random for me to handle, so I won't change the random\n" + "seed just now." + ); + @.Unknown value...ignored@> + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else { + @<Initialize the random seed to |cur_exp|@> + } +} + +@ @<Initialize the random seed to |cur_exp|@>= +init_randoms(number_to_scaled(cur_exp_value_number)); +if (mp->interaction < mp_silent_mode && (mp->selector == mp_log_only_selector || mp->selector == mp_term_and_log_selector)) { + int selector = mp->selector; + mp->selector = mp_log_only_selector; + mp_print_nl(mp, "{randomseed:="); + print_number(cur_exp_value_number); + mp_print_chr(mp, '}'); + mp_print_nl(mp, ""); + mp->selector = selector; +} + +@ And here's another simple one (somewhat different in flavor): + +@ @<Put each...@>= +mp_primitive(mp, "batchmode", mp_mode_command, mp_batch_mode); +@:mp_batch_mode_}{|batchmode| primitive@> +mp_primitive(mp, "nonstopmode", mp_mode_command, mp_nonstop_mode); +@:mp_nonstop_mode_}{|nonstopmode| primitive@> +mp_primitive(mp, "scrollmode", mp_mode_command, mp_scroll_mode); +@:mp_scroll_mode_}{|scrollmode| primitive@> +mp_primitive(mp, "errorstopmode", mp_mode_command, mp_error_stop_mode); +@:mp_error_stop_mode_}{|errorstopmode| primitive@> +mp_primitive(mp, "silentmode", mp_mode_command, mp_silent_mode); +@:mp_silent_mode_}{|silentmode| primitive@> + +@ @<Cases of |print_cmd_mod|...@>= +case mp_mode_command: + switch (m) { + case mp_batch_mode : return "batchmode"; + case mp_nonstop_mode : return "nonstopmode"; + case mp_scroll_mode : return "scrollmode"; + case mp_error_stop_mode: return "errorstopmode"; + default : return "silentmode"; + } +break; + +@ The |inner| and |outer| commands are only slightly harder. + +@ @<Put each...@>= +mp_primitive(mp, "inner", mp_protection_command, 0); +@:inner_}{|inner| primitive@> +mp_primitive(mp, "outer", mp_protection_command, 1); +@:outer_}{|outer| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_protection_command: + switch (m) { + case 0: return "inner"; + case 1: return "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 = cur_mod; /* 0 to unprotect, 1 to protect */ + do { +// int t; /* the |eq_type| before we change it */ + mp_get_symbol(mp); +// t = eq_type(cur_sym); +// switch(m) { +// case 0: +// if (t >= mp_outer_tag_command) { +// set_eq_type(cur_sym, (t - mp_outer_tag_command)); +// } +// break; +// case 1: +// if (t < mp_outer_tag_command) { +// set_eq_type(cur_sym, (t + mp_outer_tag_command)); +// } +// break; +// } + mp_get_x_next(mp); + } while (cur_cmd == mp_comma_command); +} + +@ The |setproperty| command expects a numeric, followed by a color and then +a list of symbols (names) that get that numeric value as property value. We use +a plural because one can use bitsets. This property, when larger than zero, can +trigger a callback when |overloadmode| is other than zero. This mechanism is +quite experimental and used in \CONTEXT\ for protecting definitions. + +@ @<Put each...@>= +mp_primitive(mp, "setproperty", mp_property_command, 1); +@:setproperty_}{|setproperty| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_property_command: + return "setproperty"; + +@ @<Declare action procedures for use by |do_statement|@>= +static void mp_do_property (MP mp); + +@ @c +// mp_scan_numeric_value(mp, 0, &p); + +void mp_do_property (MP mp) +{ + int p = 0; + mp_get_x_next(mp); + mp_scan_primary(mp); + switch (mp->cur_exp.type) { + case mp_numeric_type: + case mp_known_type: + { + mp_back_input(mp); + p = (int) number_to_scaled (cur_exp_value_number) / 65536; // hm + } + break; + default: + mp_back_error(mp, "Bad property value", NULL); + break; + } + mp_get_x_next(mp); + if (cur_cmd == mp_colon_command) { + do { + mp_get_symbol(mp); + set_eq_property(cur_sym, p); + mp_get_x_next(mp); + } while (cur_cmd == mp_comma_command); + } else { + mp_back_error(mp, "Bad property specification, colon expected", NULL); + } +} + +@ \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. + +@ @<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 = cur_sym; + mp_get_clear_symbol(mp); + r_delim = cur_sym; + set_eq_type(l_delim, mp_left_delimiter_command); + set_equiv_sym(l_delim, r_delim); + set_eq_type(r_delim, mp_right_delimiter_command); + set_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 (cur_cmd == mp_right_delimiter_command && equiv_sym(cur_sym) == l_delim) { + return; + } else if (cur_sym != r_delim) { + char msg[256]; + mp_snprintf(msg, 256, "Missing '%s' has been inserted", mp_str(mp, text(r_delim))); + @.Missing `)'@> + mp_back_error( + mp, + msg, + "I found no right delimiter to match a left one. So I've put one in, behind the\n" + "scenes; this may fix the problem." + ); + } else { + char msg[256]; + mp_snprintf(msg, 256, "The token '%s' is no longer a right delimiter", mp_str(mp, text(r_delim))); + @.The token...delimiter@> + mp_error( + mp, + msg, + "Strange: This token has lost its former meaning! I'll read it as a right\n" + "delimiter this time; but watch out, I'll probably miss it later." + ); + } +} + +@ The next four commands save or change the values associated with tokens. + +@ @<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 (cur_cmd != mp_internal_command) { + char msg[256]; + mp_snprintf(msg, 256, + "The token '%s' isn't an internal quantity", + (cur_sym == NULL ? "(%CAPSULE)" : mp_str(mp, text(cur_sym))) + ); + @.The token...quantity@> + mp_back_error(mp, msg, "Something like 'tracingonline' should follow 'interim'."); + } else { + mp_save_internal(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 = cur_sym; + mp_get_x_next(mp); + if (cur_cmd != mp_equals_command && cur_cmd != mp_assignment_command) { + mp_back_error( + mp, + "Missing '=' has been inserted", + "You should have said 'let symbol = something'. But don't worry; I'll pretend that\n" + "an equals sign was present. The next token I read will be 'something'." + ); + @.Missing `='@> + } + mp_get_symbol(mp); + switch (cur_cmd) { + case mp_defined_macro_command: + case mp_primary_def_command: + case mp_secondary_def_command: + case mp_tertiary_def_command: + mp_add_mac_ref(cur_mod_node); + break; + default: + break; + } + mp_clear_symbol(mp, l, 0); + set_eq_type(l, cur_cmd); + switch (cur_cmd) { + case mp_tag_command: + set_equiv(l, 0); /* todo: this was |null| */ + break; + case mp_defined_macro_command: + case mp_primary_def_command: + case mp_secondary_def_command: + case mp_tertiary_def_command: + set_equiv_node(l, cur_mod_node); + break; + case mp_left_delimiter_command: + case mp_right_delimiter_command: + set_equiv_sym(l, equiv_sym(cur_sym)); + break; + default: + set_equiv(l, cur_mod); + break; + } + 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) +{ + if (l > max_halfword) { + mp_confusion(mp, "out of memory"); /* can't be reached */ + } else { + mp_internal *internal = mp_memory_allocate((size_t) (l + 1) * sizeof(mp_internal)); + for (int 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)); + new_number(((mp_internal *)(internal + k))->v.data.n); + } + } + mp_memory_free(mp->internal); + mp->internal = internal; + mp->max_internal = l; + } +} + + +/* newinternal [numeric|string|boolean] [runscript] | [runscript] */ + +/* 0:allocate 1:push 2:pop 3:pushlogging 4:poplogging */ + +void mp_do_new_internal (MP mp) +{ + int the_type = mp_known_type; + int run_script = 0; + mp_get_next(mp); /* not mp_get_next(mp) because we don't want to expand runscript */ + if (cur_cmd == mp_type_name_command && cur_mod == mp_string_type_operation) { + the_type = mp_string_type; + } else if (cur_cmd == mp_type_name_command && cur_mod == mp_boolean_type_operation) { + the_type = mp_boolean_type; + } else if (cur_cmd == mp_type_name_command && cur_mod == mp_numeric_type_operation) { + the_type = mp_numeric_type; + } else if (! (cur_cmd == mp_type_name_command && cur_mod == mp_numeric_type_operation)) { + mp_back_input(mp); + } + if (mp_numeric_type == mp_known_type) { + /* We do as traditional MP does. */ + } else { + /* We have an explicit type and check for run_internal. */ + if (the_type == mp_numeric_type) { + the_type = mp_known_type; + } + mp_get_next(mp); /* not mp_get_next(mp) because we don't want to expand runscript */ + if (cur_cmd == mp_runscript_command) { + run_script = 1; /* run_internal */ + } else { + 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); + ++mp->int_ptr; + set_eq_type(cur_sym, mp_internal_command); + set_equiv(cur_sym, mp->int_ptr); + mp_memory_free(internal_name(mp->int_ptr)); + set_internal_name(mp->int_ptr, mp_strdup(mp_str(mp, text(cur_sym)))); + if (the_type == mp_string_type) { + set_internal_string(mp->int_ptr, mp_rts(mp,"")); + } else { + set_number_to_zero(internal_value(mp->int_ptr)); + } + set_internal_type(mp->int_ptr, the_type); + set_internal_run(mp->int_ptr, run_script); + if (run_script) { + mp->run_internal(mp, 0, mp->int_ptr, the_type, internal_name(mp->int_ptr)); + } + mp_get_x_next(mp); + } while (cur_cmd == mp_comma_command); +} + +@ @<Dealloc variables@>= +for (int k = 0; k <= mp->max_internal; k++) { + free_number(mp->internal[k].v.data.n); + mp_memory_free(internal_name(k)); +} +mp_memory_free(mp->internal); + +@ The various |show| commands are distinguished by modifier fields in the +usual way. + +@<Enumeration types@>= +typedef enum mp_show_codes { + mp_show_token_code, /* show the meaning of a single token */ + mp_show_stats_code, /* show current memory and string usage */ + mp_show_code, /* show a list of expressions */ + mp_show_var_code, /* show a variable and its descendents */ + mp_show_dependencies_code, /* show dependent variables in terms of independents */ +} mp_show_codes; + +@ @<Put each...@>= +mp_primitive(mp, "showtoken", mp_show_command, mp_show_token_code); +@:show_token_}{|showtoken| primitive@> +mp_primitive(mp, "showstats", mp_show_command, mp_show_stats_code); +@:show_stats_}{|showstats| primitive@> +mp_primitive(mp, "show", mp_show_command, mp_show_code); +@:show_}{|show| primitive@> +mp_primitive(mp, "showvariable", mp_show_command, mp_show_var_code); +@:show_var_}{|showvariable| primitive@> +mp_primitive(mp, "showdependencies", mp_show_command, mp_show_dependencies_code); +@:show_dependencies_}{|showdependencies| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_show_command: + switch (m) { + case mp_show_token_code : return "showtoken"; + case mp_show_stats_code : return "showstats"; + case mp_show_code : return "show"; + case mp_show_var_code : return "showvariable"; + case mp_show_dependencies_code: return "showdependencies"; + } + 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) +{ + do { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + 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 (cur_cmd == mp_comma_command); +} + +@ @<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 (cur_sym == NULL) { + /* Show a numeric or string or capsule token */ + switch (cur_cmd) { + case mp_numeric_command: + print_number(cur_mod_number); + break; + case mp_capsule_command: + mp_print_capsule(mp, cur_mod_node); + break; + default: + mp_print_chr(mp, '"'); + mp_print_mp_str(mp, cur_mod_str); + mp_print_chr(mp, '"'); + delete_str_ref(cur_mod_str); + break; + } + } else { + mp_print_mp_str(mp,text(cur_sym)); + mp_print_chr(mp, '='); + // if (eq_type(cur_sym) >= mp_outer_tag_command) { + // mp_print_str(mp, "(outer) "); + // } + mp_print_cmd_mod(mp, cur_cmd, cur_mod); + if (cur_cmd == mp_defined_macro_command) { + mp_print_ln(mp); + mp_show_macro (mp, cur_mod_node, NULL); + } + /* this avoids recursion between |show_macro| and |print_cmd_mod| */ + @^recursion@> + } +} + +@ 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 mp_left_delimiter_command: +case mp_right_delimiter_command: + return c == mp_left_delimiter_command ? "left delimiter" : "right delimiter"; +case mp_tag_command: + return m == 0 ? "tag" : "variable"; +case mp_defined_macro_command: + return "macro:"; +case mp_primary_def_command: + return "primarydef"; +case mp_secondary_def_command: + return "secondarydef"; +case mp_tertiary_def_command: + return "tertiarydef"; +case mp_repeat_loop_command: + return "[repeat the loop]"; +case mp_internal_command: + return internal_name(m); + +@ @<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 (cur_cmd == mp_comma_command); +} + +@ @<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, (int) mp->var_used); + mp_print_ln(mp); + mp_print_nl(mp, "String usage "); + mp_print_int(mp, (int) mp->strs_in_use); + mp_print_chr(mp, '&'); + 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) +{ + if (mp_type(p) == mp_structured_type) { + /* Descend the structure */ + mp_node q = mp_get_attribute_head(p); + do { + mp_disp_var(mp, q); + q = mp_link(q); + } while (q != mp->end_attr); + q = mp_get_subscr_head(p); + while (mp_name_type(q) == mp_subscript_operation) { + mp_disp_var(mp, q); + q = mp_link(q); + } + } else if (mp_type(p) >= mp_unsuffixed_macro_type) { + /* Display a variable macro */ + mp_print_nl(mp, ""); + mp_print_variable_name(mp, p); + if (mp_type(p) > mp_unsuffixed_macro_type) { + mp_print_str(mp, "@@#"); /* |suffixed_macro| */ + } + mp_print_str(mp, "=macro:"); + mp_show_macro(mp, mp_get_value_node(p), NULL); + } else if (mp_type(p) != mp_undefined_type) { + mp_print_nl(mp, ""); + mp_print_variable_name(mp, p); + mp_print_chr(mp, '='); + mp_print_exp(mp, p, 0); + } +} + +@ @<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 (cur_sym != NULL && cur_sym_mod == 0 && cur_cmd == mp_tag_command) { + if (cur_mod != 0 || cur_mod_node != NULL) { + mp_disp_var(mp, cur_mod_node); + goto DONE; + } + } + mp_disp_token(mp); + DONE: + mp_get_x_next(mp); + } while (cur_cmd == mp_comma_command); +} + +@ @<Declare action procedures for use by |do_statement|@>= +static void mp_do_show_dependencies (MP mp); + +@ @c +void mp_do_show_dependencies (MP mp) +{ + /* link that runs through all dependencies */ + mp_value_node 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_type) { + mp_print_chr(mp, '='); + } else { + mp_print_str(mp, " = "); /* extra spaces imply proto-dependency */ + } + mp_print_dependency(mp, (mp_value_node) mp_get_dep_list(p), mp_type(p)); + } + p = (mp_value_node) mp_get_dep_list(p); + while (mp_get_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 (cur_mod) { + case mp_show_token_code: + mp_do_show_token(mp); + break; + case mp_show_stats_code: + mp_do_show_stats(mp); + break; + case mp_show_code: + mp_do_show(mp); + break; + case mp_show_var_code: + mp_do_show_var(mp); + break; + case mp_show_dependencies_code: + mp_do_show_dependencies(mp); + break; + } + if (number_positive(internal_value(mp_showstopping_internal))) { + const char *hlp = NULL; + if (mp->interaction < mp_error_stop_mode) { + --mp->error_count; + } else { + hlp = "This isn't an error message; I'm just showing something."; + } + if (cur_cmd == mp_semicolon_command) { + mp_error(mp, "OK", hlp); + } else { + mp_back_error(mp, "OK", hlp); + mp_get_x_next(mp); + } + @.OK@> + } +} + +@ We have all kind of with variants. + +@<Enumeration types@>= +typedef enum mp_with_codes { + mp_with_pen_code, + mp_with_dashed_code, + mp_with_pre_script_code, + mp_with_post_script_code, + mp_with_stacking_code, + mp_with_no_model_code, + mp_with_grey_model_code, + mp_with_uninitialized_model_code, + mp_with_rgb_model_code, + mp_with_cmyk_model_code, + mp_with_linecap_code, + mp_with_linejoin_code, + mp_with_miterlimit_code, +} mp_with_codes; + +@ We use enums so that it looks better in the editor: + +@<Enumeration types@>= +typedef enum mp_add_codes { + mp_add_double_path_code, /* command modifier for |doublepath| */ + mp_add_contour_code, /* command modifier for |contour| */ + mp_add_also_code, /* command modifier for |also| */ +} mp_add_codes ; + +@ The |addto| command needs the following additional primitives: + +@<Put each...@>= +mp_primitive(mp, "doublepath", mp_thing_to_add_command, mp_add_double_path_code); +@:double_path_}{|doublepath| primitive@> +mp_primitive(mp, "contour", mp_thing_to_add_command, mp_add_contour_code); +@:contour_}{|contour| primitive@> +mp_primitive(mp, "also", mp_thing_to_add_command, mp_add_also_code); +@:also_}{|also| primitive@> +mp_primitive(mp, "withpen", mp_with_option_command, mp_with_pen_code); +@:with_pen_}{|withpen| primitive@> +mp_primitive(mp, "dashed", mp_with_option_command, mp_with_dashed_code); +@:dashed_}{|dashed| primitive@> +mp_primitive(mp, "withprescript", mp_with_option_command, mp_with_pre_script_code); +@:with_mp_pre_script_}{|withprescript| primitive@> +mp_primitive(mp, "withpostscript", mp_with_option_command, mp_with_post_script_code); +@:with_mp_post_script_}{|withpostscript| primitive@> +mp_primitive(mp, "withstacking", mp_with_option_command, mp_with_stacking_code); +@:with_mp_stacking_}{|withstacking| primitive@> +mp_primitive(mp, "withlinecap", mp_with_option_command, mp_with_linecap_code); +@:with_mp_linecap_}{|withlinecap| primitive@> +mp_primitive(mp, "withlinejoin", mp_with_option_command, mp_with_linejoin_code); +@:with_mp_linejoin_}{|withlinejoin| primitive@> +mp_primitive(mp, "withmiterlimit", mp_with_option_command, mp_with_miterlimit_code); +@:with_mp_miterlimit_}{|withmiterlimit| primitive@> +mp_primitive(mp, "withoutcolor", mp_with_option_command, mp_with_no_model_code); +@:with_color_}{|withoutcolor| primitive@> +mp_primitive(mp, "withgreyscale", mp_with_option_command, mp_with_grey_model_code); +@:with_color_}{|withgreyscale| primitive@> +mp_primitive(mp, "withcolor", mp_with_option_command, mp_with_uninitialized_model_code); +@:with_color_}{|withcolor| primitive@> +mp_primitive(mp, "withrgbcolor", mp_with_option_command, mp_with_rgb_model_code); +@:with_color_}{|withrgbcolor| primitive@> +mp_primitive(mp, "withcmykcolor", mp_with_option_command, mp_with_cmyk_model_code); +@:with_color_}{|withcmykcolor| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_thing_to_add_command: + switch (m) { + case mp_add_contour_code : return "contour"; + case mp_add_double_path_code: return "doublepath"; + case mp_add_also_code : return "also"; + } + break; + +case mp_with_option_command: + switch (m) { + case mp_with_pen_code : return "withpen"; + case mp_with_pre_script_code : return "withprescript"; + case mp_with_post_script_code : return "withpostscript"; + case mp_with_stacking_code : return "withstacking"; + case mp_with_no_model_code : return "withoutcolor"; + case mp_with_rgb_model_code : return "withrgbcolor"; + case mp_with_uninitialized_model_code: return "withcolor"; + case mp_with_cmyk_model_code : return "withcmykcolor"; + case mp_with_grey_model_code : return "withgreyscale"; + case mp_with_linecap_code : return "withlinecap"; + case mp_with_linejoin_code : return "withlinejoin"; + case mp_with_miterlimit_code : return "withmiterlimit"; + default : return "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, mp_node pp); + +@ 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. + +@d make_cp_a_colored_object(cp,p) do { + cp = p; + while (cp != NULL) { + if (mp_has_color(cp)) { + break; + } else { + cp = mp_link(cp); + } + } +} while (0) + +@d set_color_val(A,B) do { + if (number_negative(A)) { + set_number_to_zero(A); + } else if (number_greater(A,unity_t)) { + set_number_to_unity(A); + } else { + number_clone(A, (B)); + } +} while (0) + +@ @<Declarations@>= +/* void mp_clear_color (MP mp, void *n); */ + +@ @c +/* void mp_clear_color (MP mp, void *n) +{ + set_number_to_zero(((mp_shape_node) n)->cyan); + set_number_to_zero(((mp_shape_node) n)->magenta); + set_number_to_zero(((mp_shape_node) n)->yellow); + set_number_to_zero(((mp_shape_node) n)->black); + mp_color_model(n) = mp_uninitialized_model; +} */ + +@ @c +static void complain_invalid_with_list (MP mp, mp_variable_type t) +{ + /* Complain about improper type */ + mp_value new_expr; + const char *hlp = NULL; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + switch (t) { + case mp_with_pre_script_code: + hlp = + "Next time say 'withprescript <known string expression>'; I'll ignore the bad\n" + "'with' clause and look for another."; + break; + case mp_with_post_script_code: + hlp = + "Next time say 'withpostscript <known string expression>'; I'll ignore the bad\n" + "'with' clause and look for another."; + break; + case mp_with_stacking_code: + hlp = + "Next time say 'withstacking <known numeric expression>'; I'll ignore the bad\n" + "'with' clause and look for another."; + break; + case mp_with_dashed_code: + hlp = + "Next time say 'dashed <known picture expression>'; I'll ignore the bad 'with'\n" + "clause and look for another."; + break; + case mp_with_uninitialized_model_code: + hlp = + "Next time say 'withcolor <known color expression>'; I'll ignore the bad 'with'\n" + "clause and look for another."; + break; + case mp_with_rgb_model_code: + hlp = + "Next time say 'withrgbcolor <known color expression>'; I'll ignore the bad 'with'\n" + "clause and look for another."; + break; + case mp_with_cmyk_model_code: + hlp = + "Next time say 'withcmykcolor <known cmykcolor expression>'; I'll ignore the bad\n" + "'with' clause and look for another."; + break; + case mp_with_grey_model_code: + hlp = + "Next time say 'withgreyscale <known numeric expression>'; I'll ignore the bad\n" + " with' clause and look for another."; + break; + case mp_with_linecap_code: + hlp = + "Next time say 'withlinecap <known numeric expression>'; I'll ignore the bad\n" + "'with' clause and look for another."; + break; + case mp_with_linejoin_code: + hlp = + "Next time say 'withlinejoin <known numeric expression>'; I'll ignore the bad\n" + "'with' clause and look for another."; + break; + case mp_with_miterlimit_code: + hlp = + "Next time say 'miterlimit <known numeric expression>'; I'll ignore the bad\n" + "'with' clause and look for another."; + break; + default: + hlp = + "Next time say 'withpen <known pen expression>'; I'll ignore the bad 'with' clause\n" + "and look for another."; + break; + } + mp_back_error(mp, "Improper type", hlp); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); +} + +void mp_scan_with_list (MP mp, mp_node p, mp_node pstop) +{ + mp_node cp = MP_VOID; /* can't we reuse some? */ + mp_node pp = MP_VOID; + mp_node dp = MP_VOID; + mp_node ap = MP_VOID; + mp_node bp = MP_VOID; + mp_node sp = MP_VOID; + mp_node spstop = MP_VOID; + mp_number ml; + int miterlimit = 0; + int linecap = -1; + int linejoin = -1; + while (cur_cmd == mp_with_option_command) { + /* |cur_mod| of the |with_option| (should match |cur_type|) */ + int t; + CONTINUE: + t = cur_mod; + mp_get_x_next(mp); + if (t != mp_with_no_model_code) { + mp_scan_expression(mp); + } + switch (t) { + case mp_with_uninitialized_model_code : + switch (mp->cur_exp.type) { + case mp_cmykcolor_type: + case mp_color_type: + case mp_known_type: + case mp_boolean_type: + { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + if (cp == MP_VOID) { + make_cp_a_colored_object(cp, p); + } + if (cp != NULL) { + /* Transfer a color from the current expression to object~|cp| */ + switch (mp->cur_exp.type) { + case mp_color_type: + { + /* Transfer a rgbcolor from the current expression to object~|cp| */ + mp_node q = mp_get_value_node(cur_exp_node); + mp_color_model(cp) = mp_rgb_model; + set_color_val(mp_red_color(cp), mp_get_value_number(mp_red_part(q))); + set_color_val(mp_green_color(cp), mp_get_value_number(mp_green_part(q))); + set_color_val(mp_blue_color(cp), mp_get_value_number(mp_blue_part(q))); + set_number_to_zero(mp_black_color(cp)); + } + break; + case mp_cmykcolor_type: + { + /* Transfer a cmykcolor from the current expression to object~|cp| */ + mp_node q = mp_get_value_node(cur_exp_node); + mp_color_model(cp) = mp_cmyk_model; + set_color_val(mp_cyan_color(cp), mp_get_value_number(mp_cyan_part(q))); + set_color_val(mp_magenta_color(cp), mp_get_value_number(mp_magenta_part(q))); + set_color_val(mp_yellow_color(cp), mp_get_value_number(mp_yellow_part(q))); + set_color_val(mp_black_color(cp), mp_get_value_number(mp_black_part(q))); + } + break; + case mp_known_type: + { + /* Transfer a greyscale from the current expression to object~|cp| */ + // mp_number qq; + // new_number_clone(qq, cur_exp_value_number); + mp_color_model(cp) = mp_grey_model; + set_number_to_zero(mp_cyan_color(cp)); + set_number_to_zero(mp_magenta_color(cp)); + set_number_to_zero(mp_yellow_color(cp)); + set_color_val(mp_grey_color(cp), cur_exp_value_number); + // set_color_val(mp_grey_color(cp), qq); + // free_number(qq); + } + break; + default: + switch (cur_exp_value_boolean) { + case mp_false_operation: + /* Transfer a noncolor from the current expression to object~|cp| */ + mp_color_model(cp) = mp_no_model; + break; + case mp_true_operation: + /* Transfer no color from the current expression to object~|cp| */ + mp_color_model(cp) = mp_uninitialized_model; + break; + default: + break; + } + set_number_to_zero(mp_cyan_color(cp)); + set_number_to_zero(mp_magenta_color(cp)); + set_number_to_zero(mp_yellow_color(cp)); + set_number_to_zero(mp_black_color(cp)); + break; + } + } + mp_flush_cur_exp(mp, new_expr); + } + break; + default: + complain_invalid_with_list(mp, t); + goto CONTINUE; + } + break; + case mp_with_rgb_model_code: + if (mp->cur_exp.type != mp_color_type) { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } else { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + if (cp == MP_VOID) { + make_cp_a_colored_object(cp, p); + } + if (cp != NULL) { + /* Transfer a rgbcolor from the current expression to ob ject~|cp| */ + mp_node q = mp_get_value_node(cur_exp_node); + mp_color_model(cp) = mp_rgb_model; + set_color_val(mp_red_color(cp), mp_get_value_number(mp_red_part(q))); + set_color_val(mp_green_color(cp), mp_get_value_number(mp_green_part(q))); + set_color_val(mp_blue_color(cp), mp_get_value_number(mp_blue_part(q))); + set_number_to_zero(mp_black_color(cp)); + } + mp_flush_cur_exp(mp, new_expr); + } + break; + case mp_with_cmyk_model_code: + if (mp->cur_exp.type != mp_cmykcolor_type) { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } else { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + if (cp == MP_VOID) { + make_cp_a_colored_object(cp, p); + } + if (cp != NULL) { + /* Transfer a cmykcolor from the current expression to object~|cp| */ + mp_node q = mp_get_value_node(cur_exp_node); + mp_color_model(cp) = mp_cmyk_model; + set_color_val(mp_cyan_color(cp), mp_get_value_number(mp_cyan_part(q))); + set_color_val(mp_magenta_color(cp), mp_get_value_number(mp_magenta_part(q))); + set_color_val(mp_yellow_color(cp), mp_get_value_number(mp_yellow_part(q))); + set_color_val(mp_black_color(cp), mp_get_value_number(mp_black_part(q))); + } + mp_flush_cur_exp(mp, new_expr); + } + break; + case mp_with_grey_model_code: + if (mp->cur_exp.type != mp_known_type) { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } else { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + if (cp == MP_VOID) { + make_cp_a_colored_object(cp, p); + } + if (cp != NULL) { + /* Transfer a greyscale from the current expression to object~|cp| */ + // mp_number qq; + // new_number_clone(qq, cur_exp_value_number); + mp_color_model(cp) = mp_grey_model; + set_number_to_zero(mp_cyan_color(cp)); + set_number_to_zero(mp_magenta_color(cp)); + set_number_to_zero(mp_yellow_color(cp)); + set_color_val(mp_grey_color(cp), cur_exp_value_number); + // set_color_val(mp_grey_color(cp), qq); + // free_number(qq); + } + mp_flush_cur_exp(mp, new_expr); + } + break; + case mp_with_no_model_code: + if (cp == MP_VOID) { + make_cp_a_colored_object(cp, p); + } + if (cp != NULL) { + /* Transfer a noncolor from the current expression to object~|cp| */ + mp_color_model(cp) = mp_no_model; + set_number_to_zero(mp_cyan_color(cp)); + set_number_to_zero(mp_magenta_color(cp)); + set_number_to_zero(mp_yellow_color(cp)); + set_number_to_zero(mp_grey_color(cp)); + } + break; + case mp_with_pen_code: + if (mp->cur_exp.type != mp_pen_type && mp->cur_exp.type != mp_nep_type) { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } else { + if (pp == MP_VOID) { + /* Make |pp| an object in list~|p| that needs a pen */ + pp = p; + while (pp != NULL) { + if (mp_has_pen(pp)) { + break; + } else { + pp = mp_link(pp); + } + } + } + if (pp != NULL) { + switch (mp_type(pp)) { + case mp_fill_node_type: + case mp_stroked_node_type: + if (mp_pen_ptr((mp_shape_node) pp) != NULL) { + mp_toss_knot_list(mp, mp_pen_ptr((mp_shape_node) pp)); + } + mp_pen_ptr((mp_shape_node) pp) = cur_exp_knot; + mp_pen_type((mp_shape_node) pp) = mp->cur_exp.type == mp_nep_type; + break; + default: + break; + } + mp->cur_exp.type = mp_vacuous_type; + } + } + break; + case mp_with_pre_script_code: + if (mp->cur_exp.type != mp_string_type) { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } else if (cur_exp_str->len) { + if (ap == MP_VOID) { + ap = p; + } + while ((ap != NULL) && (! mp_has_script(ap))) { + ap = mp_link(ap); + } + if (ap != NULL) { + if (mp_pre_script(ap) != NULL) { + int selector = mp->selector; + mp_string s = mp_pre_script(ap); /* for string cleanup after combining */ + mp->selector = mp_new_string_selector; + mp_str_room(mp, (int) (mp_pre_script(ap)->len + cur_exp_str->len + 2)); + mp_print_mp_str(mp, cur_exp_str); + mp_str_room(mp, 1); + mp_append_char(mp, 13); + mp_print_mp_str(mp, mp_pre_script(ap)); + mp_pre_script(ap) = mp_make_string(mp); + delete_str_ref(s); + mp->selector = selector; + } else { + mp_pre_script(ap) = cur_exp_str; + } + add_str_ref(mp_pre_script(ap)); + mp->cur_exp.type = mp_vacuous_type; + } + } + break; + case mp_with_post_script_code: + if (mp->cur_exp.type != mp_string_type) { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } else if (cur_exp_str->len) { + if (bp == MP_VOID) { + bp = p; + } + while ((bp != NULL) && (! mp_has_script(bp))) { + bp = mp_link(bp); + } + if (bp != NULL) { + if (mp_post_script(bp) != NULL) { + int selector = mp->selector; + mp_string s = mp_post_script(bp); /* for string cleanup after combining */ + mp->selector = mp_new_string_selector; + mp_str_room(mp, (int) (mp_post_script(bp)->len + cur_exp_str->len + 2)); + mp_print_mp_str(mp, mp_post_script(bp)); + mp_str_room(mp, 1); + mp_append_char(mp, 13); + mp_print_mp_str(mp, cur_exp_str); + mp_post_script(bp) = mp_make_string(mp); + delete_str_ref(s); + mp->selector = selector; + } else { + mp_post_script(bp) = cur_exp_str; + } + add_str_ref(mp_post_script(bp)); + mp->cur_exp.type = mp_vacuous_type; + } + } + break; + case mp_with_stacking_code: + switch (mp->cur_exp.type) { + case mp_known_type: + { + if (sp == MP_VOID) { + sp = p; + } + if (pp && spstop == MP_VOID) { + spstop = pstop; + } + if (sp != NULL) { + mp_stacking(sp) = round_unscaled(cur_exp_value_number); + } + if (pp && spstop != NULL) { + mp_stacking(spstop) = round_unscaled(cur_exp_value_number); + } + /* free ? */ + mp->cur_exp.type = mp_vacuous_type; + } + break; + case mp_pair_type: + { + if (pp && mp_nice_pair(mp, cur_exp_node, mp->cur_exp.type)) { + if (sp == MP_VOID) { + sp = p; + } + if (spstop == MP_VOID) { + spstop = pstop; + } + if (sp != NULL) { + mp_stacking(sp) = round_unscaled(mp_get_value_number(mp_x_part(mp_get_value_node(cur_exp_node)))); + } + if (spstop != NULL) { + mp_stacking(spstop) = round_unscaled(mp_get_value_number(mp_y_part(mp_get_value_node(cur_exp_node)))); + } + /* free ? */ + mp->cur_exp.type = mp_vacuous_type; + } else { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } + } + break; + default: + { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } + } + break; + case mp_with_linecap_code: + switch (mp->cur_exp.type) { + case mp_known_type: + { + linecap = round_unscaled(cur_exp_value_number); + mp->cur_exp.type = mp_vacuous_type; + break; + } + default: + { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } + } + break; + case mp_with_linejoin_code: + switch (mp->cur_exp.type) { + case mp_known_type: + { + linejoin = round_unscaled(cur_exp_value_number); + mp->cur_exp.type = mp_vacuous_type; + break; + } + default: + { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } + } + break; + case mp_with_miterlimit_code: + switch (mp->cur_exp.type) { + case mp_known_type: + { + miterlimit = 1; + new_number_clone(ml, cur_exp_value_number); + mp->cur_exp.type = mp_vacuous_type; + break; + } + default: + { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } + } + break; + case mp_with_dashed_code: + if (mp->cur_exp.type != mp_picture_type) { + complain_invalid_with_list(mp, t); + goto CONTINUE; + } + // fall through + default: + if (dp == MP_VOID) { + /* Make |dp| a stroked node in list~|p| */ + dp = p; + while (dp != NULL) { + if (mp_type(dp) == mp_stroked_node_type) { + break; + } else { + dp = mp_link(dp); + } + } + } + if (dp != NULL) { + if (mp_dash_ptr(dp) != NULL) { + mp_delete_edge_ref(mp, mp_dash_ptr(dp)); + } + mp_dash_ptr(dp) = (mp_node) mp_make_dashes(mp, (mp_edge_header_node) cur_exp_node); + set_number_to_unity(((mp_shape_node) dp)->dashscale); + mp->cur_exp.type = mp_vacuous_type; + } + break; + } + } + /* + Copy the information from objects |cp|, |pp|, and |dp| into the rest of + the list. These were > MP_VOID tests but can we rely on that one being + |1| which is hopefully not some used address. + */ + if (cp > MP_VOID) { + /* Copy |cp|'s color into the colored objects linked to~|cp| */ + mp_node q = mp_link(cp); + while (q != NULL) { + if (mp_has_color(q)) { + mp_shape_node q0 = (mp_shape_node) q; + mp_shape_node cp0 = (mp_shape_node) cp; + number_clone(q0->red, cp0->red); + number_clone(q0->green, cp0->green); + number_clone(q0->blue, cp0->blue); + number_clone(q0->black, cp0->black); + mp_color_model(q) = mp_color_model(cp); + } + q = mp_link(q); + } + } + if (pp > MP_VOID) { + /* Copy |mp_pen_ptr(pp)| into stroked and filled nodes linked to |pp| */ + mp_node q = mp_link(pp); + while (q != NULL) { + if (mp_has_pen(q)) { + switch (mp_type(q)) { + case mp_fill_node_type: + case mp_stroked_node_type: + if (mp_pen_ptr((mp_shape_node) q) != NULL) { + mp_toss_knot_list(mp, mp_pen_ptr((mp_shape_node) q)); + } + mp_pen_ptr((mp_shape_node) q) = mp_copy_pen(mp, mp_pen_ptr((mp_shape_node) pp)); + break; + default: + break; + } + } + q = mp_link(q); + } + } + if (dp > MP_VOID) { + /* Make stroked nodes linked to |dp| refer to |mp_dash_ptr(dp)| */ + mp_node q = mp_link(dp); + while (q != NULL) { + if (mp_type(q) == mp_stroked_node_type) { + if (mp_dash_ptr(q) != NULL) { + mp_delete_edge_ref(mp, mp_dash_ptr(q)); + } + mp_dash_ptr(q) = mp_dash_ptr(dp); + set_number_to_unity(((mp_shape_node) q)->dashscale); + if (mp_dash_ptr(q) != NULL) { + mp_add_edge_ref(mp, mp_dash_ptr(q)); + } + } + q = mp_link(q); + } + } + if (linecap >= 0 && linecap < mp_weird_linecap_code) { + mp_node q = p; + while (q != NULL) { + switch (mp_type(q)) { + case mp_fill_node_type: + case mp_stroked_node_type: + mp_set_linecap(q, linecap); + break; + default: + break; + } + q = mp_link(q); + } + } + if (linejoin >= 0 && linejoin < mp_weird_linejoin_code) { + mp_node q = p; + while (q != NULL) { + switch (mp_type(q)) { + case mp_fill_node_type: + case mp_stroked_node_type: + mp_set_linejoin(q, linejoin); + break; + default: + break; + } + q = mp_link(q); + } + } + if (miterlimit) { + mp_node q = p; + while (q != NULL) { + switch (mp_type(q)) { + case mp_fill_node_type: + case mp_stroked_node_type: + number_clone(mp_miterlimit(q), ml); + break; + default: + break; + } + q = mp_link(q); + } + free_number(ml); + } + if (! pp && sp > MP_VOID) { + mp_node q = mp_link(sp); + while (q != NULL) { + mp_stacking(q) = mp_stacking(sp); + 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_edge_header_node mp_find_edges_var (MP mp, mp_node t); + +@ @c +mp_edge_header_node mp_find_edges_var (MP mp, mp_node t) +{ + mp_edge_header_node cur_edges = NULL; + mp_node p = mp_find_variable(mp, t); + if (p == NULL) { + char *msg = mp_obliterated(mp, t); + mp_back_error( + mp, + msg, + "It seems you did a nasty thing --- probably by accident, but nevertheless you\n" + "nearly hornswoggled me ... While I was evaluating the right-hand side of thisn" + "command, something happened, and the left-hand side is no longer a variable! So In" + "won't change anything." + ); + mp_memory_free(msg); + mp_get_x_next(mp); + } else if (mp_type(p) != mp_picture_type) { + char msg[256]; + mp_string sname; + int selector = mp->selector; + mp->selector = mp_new_string_selector; + mp_show_token_list(mp, t, NULL); + sname = mp_make_string(mp); + mp->selector = selector; + mp_snprintf(msg, 256, "Variable %s is the wrong type(%s)", mp_str(mp, sname), mp_type_string(mp_type(p))); + @.Variable x is the wrong type@> + delete_str_ref(sname); + mp_back_error( + mp, + msg, + "I was looking for a 'known' picture variable. So I'll not change anything just\n" + "now." + ); + mp_get_x_next(mp); + } else { + mp_set_value_node(p, (mp_node) mp_private_edges(mp, (mp_edge_header_node) mp_get_value_node(p))); + cur_edges = (mp_edge_header_node) mp_get_value_node(p); + } + mp_flush_node_list(mp, t); + return cur_edges; +} + +@ @<Put each...@>= +mp_primitive(mp, "clip", mp_bounds_command, mp_start_clip_node_type); +@:clip_}{|clip| primitive@> +mp_primitive(mp, "setgroup", mp_bounds_command, mp_start_group_node_type); +@:group_}{|group| primitive@> +mp_primitive(mp, "setbounds", mp_bounds_command, mp_start_bounds_node_type); +@:set_bounds_}{|setbounds| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_bounds_command: + switch (m) { + case mp_start_clip_node_type : return "clip"; + case mp_start_group_node_type : return "setgroup"; + case mp_start_bounds_node_type: return "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...@>= +int 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, int sep); + +@ @c +mp_node mp_start_draw_cmd (MP mp, int sep) +{ + mp_node lhv = NULL; /* variable to add to left */ + int add_type = 0; /* value to be returned in |last_add_type| */ + mp_get_x_next(mp); + mp->var_flag = sep; + mp_scan_primary(mp); + if (mp->cur_exp.type != mp_token_list_type) { + /* Abandon edges command because there's no variable */ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Not a suitable variable", + "At this point I needed to see the name of a picture variable. (Or perhaps you\n" + "have indeed presented me with one; I might have missed it, if it wasn't followed\n" + "by the proper token.) So I'll not change anything just now.\n" + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else { + lhv = cur_exp_node; + add_type = (int) cur_mod; + mp->cur_exp.type = mp_vacuous_type; + mp_get_x_next(mp); + mp_scan_expression(mp); + } + mp->last_add_type = add_type; + return lhv; +} + +@ 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_edge_header_node lhe; + /* initial value of |cur_mod| */ + int c = cur_cmd; + int m = cur_mod; + /* variable on left, the corresponding edge structure */ + mp_node lhv = mp_start_draw_cmd(mp, mp_to_command); + if (lhv != NULL) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + lhe = mp_find_edges_var(mp, lhv); + if (lhe == NULL) { + new_number(new_expr.data.n); + mp_flush_cur_exp(mp, new_expr); + } else if (mp->cur_exp.type != mp_path_type) { + char msg[256]; + mp_disp_err(mp, NULL); + new_number(new_expr.data.n); + mp_snprintf(msg, 256, "Improper '%s'", mp_cmd_mod_string(mp, c, m)); + mp_back_error( + mp, + msg, + "This expression should have specified a known path. So I'll not change anything\n" + "just now." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) { + /* Complain about a non-cycle */ + mp_back_error( + mp, + "Not a cycle", + "That contour should have ended with '..cycle' or '&cycle'. So I'll not change\n" + "anything just now." + ); + mp_get_x_next(mp); + } else { + /* Make |cur_exp| into a |setbounds| or clipping path and add it to |lhe| */ + mp_node p = mp_new_bounds_node(mp, cur_exp_knot, (int) m); + mp_node pp; + int mm = 0; + switch (m) { + case mp_start_clip_node_type : mm = mp_stop_clip_node_type; break; + case mp_start_group_node_type : mm = mp_stop_group_node_type; break; + case mp_start_bounds_node_type: mm = mp_stop_bounds_node_type; break; + } + pp = mp_new_bounds_node(mp, NULL, mm); + mp_scan_with_list(mp, p, pp); + mp_link(p) = mp_link(mp_edge_list(lhe)); + mp_link(mp_edge_list(lhe)) = p; + if (mp_obj_tail(lhe) == mp_edge_list(lhe)) { + mp_obj_tail(lhe) = p; + } + mp_link(mp_obj_tail(lhe)) = pp; + mp_obj_tail(lhe) = pp; + 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 = mp_start_draw_cmd(mp, mp_thing_to_add_command); + if (lhv != NULL) { + mp_edge_header_node lhe; /* variable on left, the corresponding edge structure */ + mp_node p; /* the graphical object or list for |scan_with_list| to update */ + mp_edge_header_node e; /* an edge structure to be merged */ + int add_type = mp->last_add_type; /* |also_code|, |contour_code|, or |double_path_code| */ + if (add_type == mp_add_also_code) { + /* + Make sure the current expression is a suitable picture and set + |e| and |p| appropriately. Setting |p:=NULL| causes the + $\langle$with list$\rangle$ to be ignored; setting |e:=NULL| + prevents anything from being added to |lhe|. + */ + p = NULL; + e = NULL; + if (mp->cur_exp.type != mp_picture_type) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Improper 'addto'", + "This expression should have specified a known picture. So I'll not change\n" + "anything just now." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else { + e = mp_private_edges(mp, (mp_edge_header_node) cur_exp_node); + mp->cur_exp.type = mp_vacuous_type; + p = mp_link(mp_edge_list(e)); + } + } else { + /* + Create a graphical object |p| based on |add_type| and the current + expression. In this case |add_type<>also_code| so setting + |p:=NULL| suppresses future attempts to add to the edge + structure. + */ + 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) { + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_disp_err(mp, NULL); + mp_back_error( + mp, + "Improper 'addto'", + "This expression should have specified a known path. So I'll not change anything\n" + "just now." + ); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else if (add_type != mp_add_contour_code) { + p = mp_new_shape_node(mp, cur_exp_knot, mp_stroked_node_type); + mp->cur_exp.type = mp_vacuous_type; + } else if (mp_left_type(cur_exp_knot) == mp_endpoint_knot) { + /* Complain about a non-cycle */ + mp_back_error( + mp, + "Not a cycle", + "That contour should have ended with '.. cycle' or '& cycle'. So I'll not change\n" + "anything just now." + ); + mp_get_x_next(mp); + } else { + p = mp_new_shape_node(mp, cur_exp_knot, mp_fill_node_type); + mp->cur_exp.type = mp_vacuous_type; + } + } + mp_scan_with_list(mp, p, NULL); + /* 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) { + mp_delete_edge_ref(mp, e); + } + } else if (add_type == mp_add_also_code) { + if (e != NULL) { + /* Merge |e| into |lhe| and delete |e| */ + if (mp_link(mp_edge_list(e)) != NULL) { + mp_link(mp_obj_tail(lhe)) = mp_link(mp_edge_list(e)); + mp_obj_tail(lhe) = mp_obj_tail(e); + mp_obj_tail(e) = mp_edge_list(e); + mp_link(mp_edge_list(e)) = NULL; + mp_flush_dash_list(mp, lhe); + } + mp_toss_edges(mp, e); + } + } else if (p != NULL) { + mp_link(mp_obj_tail(lhe)) = p; + mp_obj_tail(lhe) = p; + if (add_type == mp_add_double_path_code) { + if (mp_pen_ptr((mp_shape_node) p) == NULL) { + mp_pen_ptr((mp_shape_node) p) = mp_get_pen_circle(mp, &zero_t); + } + } + } + } +} + +@ @<Declare action procedures for use by |do_statement|@>= +@<Declare the output procedures@> +static void mp_do_ship_out (MP mp); + +@ @c +void mp_do_ship_out (MP mp) +{ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + mp_get_x_next(mp); + mp_scan_expression(mp); + if (mp->cur_exp.type != mp_picture_type) { + mp_disp_err(mp, NULL); + set_number_to_zero(new_expr.data.n); + mp_back_error(mp, "Not a known picture", "I can only output known pictures."); + mp_get_x_next(mp); + mp_flush_cur_exp(mp, new_expr); + } else { + mp_ship_out(mp, cur_exp_node); + set_number_to_zero(new_expr.data.n); + mp_flush_cur_exp(mp, new_expr); + } +} + +@ The |everyjob| command simply assigns a nonzero value to the global variable +|every_job_sym|. + +@ @<Glob...@>= +mp_sym every_job_sym; + +@ @<Set init...@>= +mp->every_job_sym = NULL; + +@ Finally, we have only the \quote {message} commands remaining. + +@d message_code 0 +@d err_message_code 1 +@d err_help_code 2 + +@<Put each...@>= +mp_primitive(mp, "message", mp_message_command, message_code); +@:message_}{|message| primitive@> +mp_primitive(mp, "errmessage", mp_message_command, err_message_code); +@:err_message_}{|errmessage| primitive@> +mp_primitive(mp, "errhelp", mp_message_command, err_help_code); +@:err_help_}{|errhelp| primitive@> + +@ @<Cases of |print_cmd...@>= +case mp_message_command: + if (m < err_message_code) { + return "message"; + } else if (m == err_message_code) { + return "errmessage"; + } else { + return "errhelp"; + } + +@ @<Declare action procedures for use by |do_statement|@>= +static void mp_do_message (MP mp); +static void mp_no_string_err (MP mp, const char *s); + +@ @c +void mp_do_message (MP mp) +{ + mp_value new_expr; + int m = cur_mod; /* the type of message */ + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + 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_mp_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; + } + } + set_number_to_zero(new_expr.data.n); + mp_flush_cur_exp(mp, new_expr); +} + +static void mp_no_string_err (MP mp, const char *s) +{ + mp_disp_err(mp, NULL); + mp_back_error(mp, "Not a string", s); + @.Not a string@> + mp_get_x_next(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 (cur_exp_str->len == 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...@>= +int long_help_seen; /* has the long |\\errmessage| help been used? */ + +@ @<Set init...@>= +mp->long_help_seen = 0; + +@ @<Print string |cur_exp| as an error message@>= +{ + char msg[256]; + mp_snprintf(msg, 256, "%s", mp_str(mp, cur_exp_str)); + if (mp->err_help != NULL) { + mp->use_err_help = 1; + mp_back_error(mp, msg, NULL); + } else if (mp->long_help_seen) { + mp_back_error(mp, msg, "(That was another 'errmessage'.)"); + } else { + @^Marple, Jane@> + if (mp->interaction < mp_error_stop_mode) { + mp->long_help_seen = 1; + } + mp_back_error( + mp, + msg, + "This error message was generated by an 'errmessage' command, so I can't give any\n" + "explicit help. Pretend that you're Miss Marple: Examine all clues, and deduce the\n" + "truth by inspired guesses." + ); + } + mp_get_x_next(mp); + mp->use_err_help = 0; +} + +@ @<Declare action procedures for use by |do_statement|@>= +static void mp_do_write (MP mp); +static void mp_do_write_string (MP mp, mp_string t); + +@ @c +void mp_do_write (MP mp) +{ + mp_value new_expr; + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + 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 (cur_cmd != mp_to_command) { + mp_back_error(mp, "Missing 'to' clause", "A write command should end with 'to <filename>'"); + mp_get_x_next(mp); + } else { + mp_string t = cur_exp_str; /* the line of text to be written */ + mp->cur_exp.type = mp_vacuous_type; + 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 { + mp_do_write_string(mp, t); + } + } + set_number_to_zero(new_expr.data.n); + mp_flush_cur_exp(mp, new_expr); +} + +static void mp_do_write_string (MP mp, mp_string t) +{ + char *fn = mp_str(mp, cur_exp_str); + int n = mp->write_files; + int n0 = mp->write_files; + while (mp_strcmp(fn, mp->wr_fname[n]) != 0) { + if (n == 0) { + /* bottom reached */ + if (n0 == mp->write_files) { + if (mp->write_files < mp->max_write_files) { + ++mp->write_files; + } else { + int l = mp->max_write_files + (mp->max_write_files / 4); + void **wr_file = mp_memory_allocate((size_t) (l + 1) * sizeof(void *)); + char **wr_fname = mp_memory_allocate((size_t) (l + 1) * sizeof(char *)); + for (int 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; + } + } + mp_memory_free(mp->wr_file); + mp_memory_free(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 { + --n; + if (mp->wr_fname[n] == NULL) { + n0 = n; + } + } + } + if (mp_str_vs_str(mp, t, mp->eof_line) == 0) { + (mp->close_file)(mp, mp->wr_file[n]); + mp_memory_free(mp->wr_fname[n]); + mp->wr_fname[n] = NULL; + if (n == mp->write_files - 1) { + mp->write_files = n; + } + } else { + int selector = mp->selector; + mp->selector = n + mp_first_file_selector; + mp_print_mp_str(mp, t); + mp_print_ln(mp); + mp->selector = selector; + } +} + +@ @<Initialize table entries@>= +mp->inf_val = mp_new_value_node(mp); +mp_set_value_number(mp->inf_val, fraction_four_t); + +@ @<Free table entries@>= +mp_free_value_node(mp, mp->inf_val); + +@ 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. + +@ 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 list + +@ @<Initialize table entries@>= +mp->zero_val = mp_new_value_node(mp); +mp_set_value_number(mp->zero_val, zero_t); + +@ @<Free table entries@>= +mp_free_value_node(mp, mp->zero_val); + +@ To print |scaled| value to PDF output we need some subroutines to ensure +accurary. + +@d max_integer 0x7FFFFFFF /* $2^{31}-1$ */ + +@<Glob...@>= +int ten_pow[10]; /* $10^0..10^9$ */ +int scaled_out; /* amount of |scaled| that was taken out in |divide_scaled| */ + +@ @<Set init...@>= +mp->ten_pow[0] = 1; +for (int 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. Originally teh output was targeted at \POSTSCRIPT\ but the library +has no backend. It privides the result as a structure that reflects the original +\POSTSCRIPT\ backend. We could use more direct methods but for now we follow the +route with an intermediate. Actually, it's that intermediate that is kind of +the standard output \API. We no longer report the shipped outfigure because the +backend can do that, but we keep the number. + +@<Declare the output procedures@>= +static void mp_ship_out (MP mp, mp_node h); + +@ Some of these types are already used earlier. + +@<Exported types@>= +typedef struct mp_color { + double a_val; /* r or c */ + double b_val; /* g or m */ + double c_val; /* b or y */ + double d_val; /* k */ +} mp_color; + +typedef struct mp_dash_object { + double offset; + double *array; +} mp_dash_object; + +/* + This mp_graphic_object gets cast onto the fill and stroke. For some reason + we don't distinguish between start and stop here. +*/ + +typedef struct mp_graphic_object { + int type; + int stacking; + struct mp_graphic_object *next; +} mp_graphic_object; + +typedef struct mp_shape_object { + int type; + int stacking; + struct mp_graphic_object *next; + char *pre_script; + char *post_script; + size_t pre_length; + size_t post_length; + mp_color color; + mp_gr_knot path; + mp_gr_knot htap; + mp_gr_knot pen; + double miterlimit; + mp_dash_object *dash; + unsigned char color_model; + unsigned char linejoin; + unsigned char linecap; + unsigned char padding; /* pen_type */ +} mp_shape_object; + +typedef struct mp_start_object { + int type; + int stacking; + struct mp_graphic_object *next; + char *pre_script; + char *post_script; + size_t pre_length; + size_t post_length; + mp_gr_knot path; +} mp_start_object; + +typedef struct mp_stop_object { + int type; + int stacking; + struct mp_graphic_object *next; + char *pre_script; + char *post_script; + size_t pre_length; + size_t post_length; + mp_gr_knot path; +} mp_stop_object; + +typedef struct mp_edge_object { + struct mp_graphic_object *body; + struct mp_edge_object *next; + MP parent; + double minx; + double miny; + double maxx; + double maxy; + double width; + double height; + double depth; + double italic; + int charcode; + int padding; +} mp_edge_object; + +@d gr_next_knot(A) (A)->next +@d gr_originator(A) (A)->originator +@d mp_knotstate(A) (A)->state +@d gr_type(A) (A)->type +@d gr_link(A) (A)->next +@d gr_color_model(A) (A)->color_model +@d gr_red_val(A) (A)->color.a_val +@d gr_green_val(A) (A)->color.b_val +@d gr_blue_val(A) (A)->color.c_val +@d gr_cyan_val(A) (A)->color.a_val +@d gr_magenta_val(A) (A)->color.b_val +@d gr_yellow_val(A) (A)->color.c_val +@d gr_black_val(A) (A)->color.d_val +@d gr_grey_val(A) (A)->color.d_val +@d gr_path_ptr(A) (A)->path +@d gr_htap_ptr(A) (A)->htap +@d gr_pen_ptr(A) (A)->pen +@d gr_linejoin_val(A) (A)->linejoin +@d gr_linecap_val(A) (A)->linecap +@d gr_stacking_val(A) (A)->stacking +@d gr_miterlimit_val(A) (A)->miterlimit +@d gr_pre_script(A) (A)->pre_script +@d gr_post_script(A) (A)->post_script +@d gr_pre_length(A) (A)->pre_length +@d gr_post_length(A) (A)->post_length +@d gr_dash_ptr(A) (A)->dash + +@d mp_gr_export_color(q,p) +if (mp_color_model(p) == mp_uninitialized_model) { + gr_color_model(q) = number_to_scaled(internal_value(mp_default_color_model_internal))/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 ? (number_to_scaled(unity_t)/65536.0) : 0; +} else { + gr_color_model(q) = mp_color_model(p); + gr_cyan_val(q) = number_to_double(p->cyan); + gr_magenta_val(q) = number_to_double(p->magenta); + gr_yellow_val(q) = number_to_double(p->yellow); + gr_black_val(q) = number_to_double(p->black); +} + +@d mp_gr_export_scripts(q,p) +if (mp_pre_script (p)) { + gr_pre_script(q) = mp_strndup((const char *) mp_pre_script(p)->str, mp_pre_script(p)->len); + gr_pre_length(q) = mp_pre_script(p)->len; +} +if (mp_post_script(p)) { + gr_post_script(q) = mp_strndup((const char *) mp_post_script(p)->str, mp_post_script(p)->len); + gr_post_length(q) = mp_post_script(p)->len; +} + +@ @c +struct mp_edge_object *mp_gr_export (MP mp, mp_edge_header_node h) +{ + mp_node p; /* the current graphical object */ + mp_edge_object *hh = mp_memory_allocate(sizeof(mp_edge_object)); /* the first graphical object */ + mp_graphic_object *hp = NULL; /* the current graphical object */ + mp_set_bbox(mp, h, 1); + hh->parent = mp; + hh->body = NULL; + hh->next = NULL; + hh->minx = number_to_double(h->minx); + hh->minx = fabs(hh->minx) < 0.00001 ? 0 : hh->minx; + hh->miny = number_to_double(h->miny); + hh->miny = fabs(hh->miny) < 0.00001 ? 0 : hh->miny; + hh->maxx = number_to_double(h->maxx); + hh->maxx = fabs(hh->maxx) < 0.00001 ? 0 : hh->maxx; + hh->maxy = number_to_double(h->maxy); + hh->maxy = fabs(hh->maxy) < 0.00001 ? 0 : hh->maxy; + hh->charcode = round_unscaled(internal_value(mp_char_code_internal)); + hh->width = number_to_double(internal_value(mp_char_wd_internal)); + hh->height = number_to_double(internal_value(mp_char_ht_internal)); + hh->depth = number_to_double(internal_value(mp_char_dp_internal)); + hh->italic = number_to_double(internal_value(mp_char_ic_internal)); + p = mp_link(mp_edge_list(h)); + while (p != NULL) { + mp_graphic_object *hq = mp_new_graphic_object(mp, (int) ((mp_type(p) - mp_fill_node_type) + 1)); + switch (mp_type(p)) { + /* todo: share code between fill and stroked */ + case mp_fill_node_type: + { + mp_number d_width; /* the current pen width */ + mp_shape_node p0 = (mp_shape_node) p; + mp_shape_object *tf = (mp_shape_object *) hq; + gr_pen_ptr(tf) = mp_export_knot_list(mp, mp_pen_ptr(p0)); + new_number(d_width); + mp_get_pen_scale(mp, &d_width, mp_pen_ptr(p0)); /* whats the point ? */ + free_number(d_width); + if ((mp_pen_ptr(p0) == NULL) || mp_pen_is_elliptical(mp_pen_ptr(p0))) { + gr_path_ptr(tf) = mp_export_knot_list(mp, mp_path_ptr(p0)); + } else { + mp_knot pc = mp_copy_path(mp, mp_path_ptr(p0)); + mp_knot pp = mp_make_envelope(mp, pc, mp_pen_ptr(p0), p0->linejoin, 0, &(p0->miterlimit)); + gr_path_ptr(tf) = mp_export_knot_list(mp, pp); + mp_toss_knot_list(mp, pp); + pc = mp_htap_ypoc(mp, mp_path_ptr(p0)); + pp = mp_make_envelope(mp, pc, mp_pen_ptr((mp_shape_node) p), p0->linejoin, 0, &(p0->miterlimit)); + gr_htap_ptr(tf) = mp_export_knot_list(mp, pp); + mp_toss_knot_list(mp, pp); + } + mp_gr_export_color(tf, p0); + mp_gr_export_scripts(tf, p); + gr_linejoin_val(tf) = p0->linejoin; + gr_stacking_val(tf) = p0->stacking; + gr_miterlimit_val(tf) = number_to_double(p0->miterlimit); + } + break; + case mp_stroked_node_type: + { + mp_number d_width; /* the current pen width */ + mp_shape_node p0 = (mp_shape_node) p; + mp_shape_object *ts = (mp_shape_object *) hq; + gr_pen_ptr(ts) = mp_export_knot_list(mp, mp_pen_ptr(p0)); + new_number(d_width); + mp_get_pen_scale(mp, &d_width, mp_pen_ptr(p0)); + if (mp_pen_is_elliptical(mp_pen_ptr(p0))) { + gr_path_ptr(ts) = mp_export_knot_list(mp, mp_path_ptr(p0)); + } else { + mp_knot pc = mp_copy_path(mp, mp_path_ptr(p0)); + int t = p0->linecap; + if (mp_left_type(pc) != mp_endpoint_knot) { + mp_left_type(mp_insert_knot(mp, pc, &(pc->x_coord), &(pc->y_coord))) = mp_endpoint_knot; + mp_right_type(pc) = mp_endpoint_knot; + pc = mp_next_knot(pc); + t = 1; + } + pc = mp_make_envelope(mp, pc, mp_pen_ptr(p0), p0->linejoin, (int) t, &(p0->miterlimit)); + gr_path_ptr(ts) = mp_export_knot_list(mp, pc); + mp_toss_knot_list(mp, pc); + } + mp_gr_export_color(ts, p0); + mp_gr_export_scripts(ts, p); + gr_linejoin_val(ts) = p0->linejoin; + gr_miterlimit_val(ts) = number_to_double(p0->miterlimit); + gr_linecap_val(ts) = p0->linecap; + gr_stacking_val(ts) = p0->stacking; + gr_dash_ptr(ts) = mp_export_dashes(mp, p0, &d_width); + free_number(d_width); + } + break; + case mp_start_clip_node_type: + case mp_start_group_node_type: + case mp_start_bounds_node_type: + { + mp_start_node p0 = (mp_start_node) p; + mp_start_object *tb = (mp_start_object *) hq; + gr_path_ptr(tb) = mp_export_knot_list(mp, mp_path_ptr((mp_start_node) p)); + gr_stacking_val(tb) = p0->stacking; + mp_gr_export_scripts(tb, p); + } + break; + case mp_stop_clip_node_type: + case mp_stop_group_node_type: + case mp_stop_bounds_node_type: + { + mp_stop_node p0 = (mp_stop_node) p; + mp_stop_object *tb = (mp_stop_object *) hq; + gr_stacking_val(tb) = p0->stacking; + } + break; + default: + break; + } + if (hh->body == NULL) { + hh->body = hq; + } else { + gr_link(hp) = hq; + } + hp = hq; + p = mp_link(p); + } + return hh; +} + +@ The code here comes from the psout.w file and is part of the stipped down +library for \LUAMETATEX. There is no backend code in this subset. For that you +need the official \METAPOST\ distribution. One way of making a stand alone image +is to wrap the code in a small \CONTEXT\ file and process it to \PDF, which then +can be converted to another image format. You can blame me for errors. + +@<MPlib export header stuff@>= +struct mp_edge_object *mp_gr_export (MP mp, mp_edge_header_node h); +mp_graphic_object *mp_new_graphic_object (MP mp, int type); +void mp_gr_toss_objects (mp_edge_object *hh); +void mp_gr_toss_object (mp_graphic_object *p); + +@ @c +static void mp_do_gr_toss_dashes (mp_dash_object *dl) { + if (dl) { + mp_memory_free(dl->array); + mp_memory_free(dl); + } +} + +@ @c +static void mp_do_gr_toss_knot_list (mp_gr_knot p) +{ + if (p) { + mp_gr_knot q = p; + do { + mp_gr_knot r = gr_next_knot(q); + mp_memory_free(q); + q = r; + } while (q != p); + } +} + +@ @c +mp_graphic_object *mp_new_graphic_object (MP mp, int type) +{ + mp_graphic_object *p; + size_t size; + (void) mp; + switch (type) { + case mp_fill_code: + case mp_stroked_code: + size = sizeof(mp_shape_object); + break; + case mp_start_clip_code: + case mp_start_group_code: + case mp_start_bounds_code: + size = sizeof(mp_start_object); + break; + default: + size = sizeof(mp_graphic_object); + break; + } + p = (mp_graphic_object *) mp_memory_allocate(size); + memset(p, 0, size); + gr_type(p) = type; + return p; +} + +@ @c +void mp_gr_toss_object (mp_graphic_object *p) +{ + switch (gr_type(p)) { + case mp_fill_code: + case mp_stroked_code: + { + mp_shape_object *o = (mp_shape_object *) p; + mp_memory_free(gr_pre_script(o)); + mp_memory_free(gr_post_script(o)); + mp_do_gr_toss_knot_list(gr_pen_ptr(o)); + mp_do_gr_toss_knot_list(gr_path_ptr(o)); + if (gr_htap_ptr(o)) { + mp_do_gr_toss_knot_list(gr_htap_ptr(o)); + } + if (gr_dash_ptr(o)) { + mp_do_gr_toss_dashes(gr_dash_ptr(o)); + } + } + break; + case mp_start_clip_code: + case mp_start_group_code: + case mp_start_bounds_code: + { + mp_start_object *o = (mp_start_object *) p; + mp_memory_free(gr_pre_script(o)); + mp_memory_free(gr_post_script(o)); + mp_do_gr_toss_knot_list(gr_path_ptr(o)); + } + break; + case mp_stop_clip_code: + case mp_stop_group_code: + case mp_stop_bounds_code: + break; + } + mp_memory_free(p); +} + +@ @c +void mp_gr_toss_objects (mp_edge_object *hh) +{ + mp_graphic_object *p = hh->body; + while (p) { + mp_graphic_object *q = gr_link(p); + mp_gr_toss_object(p); + p = q; + } + mp_memory_free(hh); +} + +@ This function is now nearly trivial. + +@c +void mp_ship_out (MP mp, mp_node h) { + (mp->shipout_backend)(mp, h); +} + +@ @<Declarations@>= +static void mp_shipout_backend (MP mp, void *h); + +@ We keep the template as comment: + +@c +static void mp_shipout_backend (MP mp, void *voidh) +{ + (void) mp; + (void) voidh; +} + +@ @<Exported types@>= +typedef void (*mp_backend_writer) (MP, void *); + +@ @<Option variables@>= +mp_backend_writer shipout_backend; + +@* Some extensions. + +Get a numeric value from \MP\ is not easy. We have to consider the macro and the +loops, as also the internal type (this is a first attempt, and more work is +needed). If we are inside a |for| loop, then the global |loop_ptr| is not null +and the other loops eventually nested are available by mean of |loop_ptr->link|. +The current numeric value is stored in |old_value|. + +@ @<Exported function headers@>= +void mp_scan_next_value (MP mp, int keep, int *token, int *mode, int *kind); +void mp_scan_expr_value (MP mp, int keep, int *kind); +void mp_scan_token_value (MP mp, int keep, int *token, int *mode, int *kind); +void mp_scan_symbol_value (MP mp, int keep, char **s, int expand); +void mp_scan_property_value (MP mp, int keep, int *kind, char **s, int *property, int *detail); + +int mp_skip_token_value (MP mp, int token); + +void mp_scan_numeric_value (MP mp, int primary, double *d); +void mp_scan_boolean_value (MP mp, int primary, int *b); +void mp_scan_string_value (MP mp, int primary, char **s, size_t *l); +void mp_scan_pair_value (MP mp, int primary, double *x, double *y); +void mp_scan_color_value (MP mp, int primary, double *r, double *g, double *b); +void mp_scan_cmykcolor_value (MP mp, int primary, double *c, double *m, double *y, double *k); +void mp_scan_transform_value (MP mp, int primary, double *x, double *y, double *xx, double *xy, double *yx, double *yy); +void mp_scan_path_value (MP mp, int primary, mp_knot *k); + +void mp_push_numeric_value (MP mp, double n); +void mp_push_integer_value (MP mp, int i); +void mp_push_boolean_value (MP mp, int b); +void mp_push_string_value (MP mp, const char *s, int l); +void mp_push_pair_value (MP mp, double x, double y); +void mp_push_color_value (MP mp, double r, double g, double b); +void mp_push_cmykcolor_value (MP mp, double c, double m, double y, double k); +void mp_push_transform_value (MP mp, double x, double y, double xx, double xy, double yx, double yy); +void mp_push_path_value (MP mp, mp_knot k); + +@ This is a lightweight version, one that also omits the quotes around strings. +When we scan we check the type anyway. We don't really have a list either. So we +only serialize symbolic names, strings and single tokens. + +@c +// void mp_scan_symbol_value (MP mp, int keep, char **s, int expand) +// { +// if (mp->extensions) { +// mp_node p; +// unsigned char *r = NULL; +// if (expand) { +// mp_get_x_next(mp); +// } else { +// mp_get_next(mp); +// } +// if (keep) { +// mp_back_input(mp); +// } +// *s = NULL; +// p = mp_cur_tok(mp); +// if (p) { +// /* simplified mp_show_token_list */ +// if (mp_type(p) == mp_symbol_node_type) { +// // if (mp_name_type(p) != mp_expr_operation && mp_name_type(p) != mp_suffix_operation && mp_name_type(p) != mp_text_operation) { +// mp_sym sr = mp_get_sym_sym(p); +// // if (sr != mp_collective_subscript) { +// mp_string rr = text(sr); +// if (rr && rr->str) { +// r = rr->str; +// } +// // } +// // } +// } else if (mp_name_type(p) == mp_token_operation) { +// if (mp_type(p) == mp_string_type) { +// r = mp_get_value_str(p)->str; +// } +// } +// } +// if (r) { +// *s = (char *) mp_strdup((char *) r); +// } else { +// *s = NULL; +// } +// } +// } + +void mp_scan_symbol_value (MP mp, int keep, char **s, int expand) +{ + if (mp->extensions) { + if (expand) { + mp_get_x_next(mp); + } else { + mp_get_next(mp); + } + if (keep) { + mp_back_input(mp); + } + if (cur_sym == NULL && (cur_sym_mod == 0 || cur_sym_mod == mp_normal_operation)) { + *s = NULL; + } else { + unsigned char *r = NULL; + mp_node p = mp_new_symbolic_node(mp); + mp_set_sym_sym(p, cur_sym); + mp_name_type(p) = cur_sym_mod; + if (mp_type(p) == mp_symbol_node_type) { + mp_sym sr = mp_get_sym_sym(p); + mp_string rr = text(sr); + if (rr && rr->str) { + r = rr->str; + } + } else if (mp_name_type(p) == mp_token_operation) { + if (mp_type(p) == mp_string_type) { + r = mp_get_value_str(p)->str; + } + } + mp_free_symbolic_node(mp, p); + if (r) { + *s = (char *) mp_strdup((char *) r); + } else { + *s = NULL; + } + } + } +} + +void mp_scan_property_value (MP mp, int keep, int *kind, char **str, int *property, int *detail) +{ + if (mp->extensions) { + mp_symbol_entry *entry; + mp_get_symbol(mp); + entry = cur_sym; + if (entry) { + mp_node node = entry->type == mp_tag_command ? entry->v.data.node : NULL; + *kind = entry->type; + *str = (char *) mp_strdup((char *) entry->text->str); + *property = entry->property; + if (node) { + *detail = node->type; + } + if (keep) { + mp_back_input(mp); + } + } + } +} + +void mp_scan_next_value (MP mp, int keep, int *token, int *mode, int *kind) +{ + if (mp->extensions) { + mp_get_next(mp); + if (keep) { + mp_back_input(mp); + } + *token = cur_cmd; + *mode = cur_mod; + *kind = mp->cur_exp.type; + } +} + +void mp_scan_expr_value (MP mp, int keep, int *kind) +{ + if (mp->extensions) { + mp_get_next(mp); + mp_scan_primary(mp); + *kind = mp->cur_exp.type; + if (keep) { + mp_back_input(mp); + mp_back_expr(mp); + } + } +} + +void mp_scan_token_value (MP mp, int keep, int *token, int *mode, int *kind) +{ + if (mp->extensions) { + mp_get_x_next(mp); + if (keep) { + mp_back_input(mp); + } + *token = cur_cmd; + *mode = cur_mod; + *kind = mp->cur_exp.type; + } +} + +int mp_skip_token_value (MP mp, int token) +{ + if (mp->extensions) { + mp_get_x_next(mp); + if (token == cur_cmd) { + return 1; + } else { + mp_back_input(mp); + } + } + return 0; +} + +static void mp_scan_something (MP mp, int primary) +{ + mp_get_x_next(mp); + switch (primary) { + case 0: mp_scan_expression(mp); break; + case 1: mp_scan_primary(mp); break; + case 2: mp_scan_secondary(mp); break; + case 3: mp_scan_tertiary(mp); break; + default: mp_scan_expression(mp); break; + } +} + +void mp_scan_numeric_value (MP mp, int primary, double *d) +{ + if (mp->extensions) { + mp_scan_something(mp, primary); + if (mp->cur_exp.type != mp_known_type) { + mp_back_input(mp); /* hm */ + } else { + mp_back_input(mp); /* hm */ + *d = number_to_double(cur_exp_value_number); + } + } +} + +# define mp_set_double_value(mp,target,what) \ +if (mp_type(what) == mp_known_type) { \ + *target = number_to_double(mp_get_value_number(what)); \ +} + +void mp_scan_pair_value (MP mp, int primary, double *x, double *y) +{ + if (mp->extensions) { + mp_scan_something(mp, primary); + if (mp->cur_exp.type != mp_pair_type) { + mp_back_input(mp); /* hm */ + } else { + mp_node p ; + mp_back_input(mp); /* hm */ + p = mp_get_value_node(cur_exp_node); + mp_set_double_value(mp, x, mp_x_part(p)); + mp_set_double_value(mp, y, mp_y_part(p)); + } + } +} + +void mp_scan_color_value (MP mp, int primary, double *r, double *g, double *b) +{ + if (mp->extensions) { + mp_scan_something(mp, primary); + if (mp->cur_exp.type != mp_color_type) { + mp_back_input(mp); /* hm */ + } else { + mp_node p ; + mp_back_input(mp); /* hm */ + p = mp_get_value_node(cur_exp_node); + mp_set_double_value(mp, r, mp_red_part(p)); + mp_set_double_value(mp, g, mp_green_part(p)); + mp_set_double_value(mp, b, mp_blue_part(p)); + } + } +} + +void mp_scan_cmykcolor_value (MP mp, int primary, double *c, double *m, double *y, double *k) +{ + if (mp->extensions) { + mp_scan_something(mp, primary); + if (mp->cur_exp.type != mp_cmykcolor_type) { + mp_back_input(mp); /* hm */ + } else { + mp_node p ; + mp_back_input(mp); /* hm */ + p = mp_get_value_node(cur_exp_node); + mp_set_double_value(mp, c, mp_cyan_part(p)); + mp_set_double_value(mp, m, mp_magenta_part(p)); + mp_set_double_value(mp, y, mp_yellow_part(p)); + mp_set_double_value(mp, k, mp_black_part(p)); + } + } +} + +void mp_scan_transform_value (MP mp, int primary, double *x, double *y, double *xx, double *xy, double *yx, double *yy) +{ + if (mp->extensions) { + mp_scan_something(mp, primary); + if (mp->cur_exp.type != mp_transform_type) { + mp_back_input(mp); /* hm */ + } else { + mp_node p ; + mp_back_input(mp); /* hm */ + p = mp_get_value_node(cur_exp_node); + mp_set_double_value(mp, x, mp_x_part(p)); + mp_set_double_value(mp, y, mp_y_part(p)); + mp_set_double_value(mp, xx, mp_xx_part(p)); + mp_set_double_value(mp, xy, mp_xy_part(p)); + mp_set_double_value(mp, yx, mp_yx_part(p)); + mp_set_double_value(mp, yy, mp_yy_part(p)); + } + } +} + +void mp_scan_path_value (MP mp, int primary, mp_knot *k) +{ + if (mp->extensions) { + mp_scan_something(mp, primary); + if (mp->cur_exp.type != mp_path_type && mp->cur_exp.type != mp_pen_type) { + mp_back_input(mp); /* hm */ + } else { + mp_back_input(mp); /* hm */ + *k = cur_exp_knot; + } + } +} + +void mp_scan_boolean_value (MP mp, int primary, int *b) +{ + if (mp->extensions) { + mp_scan_something(mp, primary); + if (mp->cur_exp.type != mp_boolean_type) { + mp_back_input(mp); /* hm */ + } else { + mp_back_input(mp); /* hm */ + *b = cur_exp_value_boolean == mp_true_operation ? 1 : 0 ; + } + } +} + +void mp_scan_string_value (MP mp, int primary, char **s, size_t *l) +{ + if (mp->extensions) { + mp_scan_something(mp, primary); + if (mp->cur_exp.type != mp_string_type) { + mp_back_input(mp); /* hm */ + *s = NULL ; + *l = 0; + } else { + mp_back_input(mp); /* hm */ + *s = (char *) cur_exp_str->str ; + *l = cur_exp_str->len; + } + } +} + +void mp_push_numeric_value (MP mp, double n) +{ + mp_number m; + new_number_from_double(mp, m, n); + mp->cur_exp.type = mp_known_type; + mp_set_cur_exp_value_number(mp, &m); + mp_back_expr(mp); +} + +void mp_push_integer_value (MP mp, int i) +{ + mp_number m; + new_number(m); + set_number_from_int(m, i); + mp->cur_exp.type = mp_known_type; + mp_set_cur_exp_value_number(mp, &m); + mp_back_expr(mp); +} + +void mp_push_boolean_value (MP mp, int b) +{ + mp->cur_exp.type = mp_boolean_type; + mp_set_cur_exp_value_boolean(mp, b ? mp_true_operation : mp_false_operation); + mp_back_expr(mp); +} + +void mp_push_string_value (MP mp, const char *s, int l) +{ + mp->cur_exp.type = mp_string_type; + mp_set_cur_exp_str(mp, mp_rtsl(mp, (char *) s, l)); + mp_back_expr(mp); +} + +void mp_push_pair_value (MP mp, double x, double y) +{ + /* + mp_value new_expr; + */ + mp_number px, py; + mp_node p = mp_new_value_node(mp); + mp_node v; + mp_init_pair_node(mp, p); + v = mp_get_value_node(p); + new_number_from_double(mp, px, x); + new_number_from_double(mp, py, y); + mp_type(mp_x_part(v)) = mp_known_type; + mp_type(mp_y_part(v)) = mp_known_type; + mp_set_value_number(mp_x_part(v), px); + mp_set_value_number(mp_y_part(v), py); + free_number(px); + free_number(py); + /* + memset(&new_expr, 0, sizeof(mp_value)); + new_number(new_expr.data.n); + 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_operation; + */ + mp_name_type(p) = mp_capsule_operation; + mp->cur_exp.type = mp_pair_type; + mp_set_cur_exp_node(mp, p); + mp_back_expr(mp); +} + +void mp_push_color_value (MP mp, double r, double g, double b) +{ + mp_number pr, pg, pb; + mp_node p = mp_new_value_node(mp); + mp_node v; + mp_init_color_node(mp, p, mp_color_type); + v = mp_get_value_node(p); + new_number_from_double(mp, pr, r); + new_number_from_double(mp, pg, g); + new_number_from_double(mp, pb, b); + mp_type(mp_red_part (v)) = mp_known_type; + mp_type(mp_green_part(v)) = mp_known_type; + mp_type(mp_blue_part (v)) = mp_known_type; + mp_set_value_number(mp_red_part (v), pr); + mp_set_value_number(mp_green_part(v), pg); + mp_set_value_number(mp_blue_part (v), pb); + free_number(pr); + free_number(pg); + free_number(pb); + mp_name_type(p) = mp_capsule_operation; + mp->cur_exp.type = mp_color_type; + mp_set_cur_exp_node(mp, p); + mp_back_expr(mp); +} + +void mp_push_cmykcolor_value (MP mp, double c, double m, double y, double k) +{ + mp_number pc, pm, py, pk; + mp_node p = mp_new_value_node(mp); + mp_node v; + mp_init_color_node(mp, p, mp_cmykcolor_type); + v = mp_get_value_node(p); + new_number_from_double(mp, pc, c); + new_number_from_double(mp, pm, m); + new_number_from_double(mp, py, y); + new_number_from_double(mp, pk, k); + mp_type(mp_cyan_part (v)) = mp_known_type; + mp_type(mp_magenta_part(v)) = mp_known_type; + mp_type(mp_yellow_part (v)) = mp_known_type; + mp_type(mp_black_part (v)) = mp_known_type; + mp_set_value_number(mp_cyan_part (v), pc); + mp_set_value_number(mp_magenta_part(v), pm); + mp_set_value_number(mp_yellow_part (v), py); + mp_set_value_number(mp_black_part (v), pk); + free_number(pc); + free_number(pm); + free_number(py); + free_number(pk); + mp_name_type(p) = mp_capsule_operation; + mp->cur_exp.type = mp_cmykcolor_type; + mp_set_cur_exp_node(mp, p); + mp_back_expr(mp); +} + +void mp_push_transform_value (MP mp, double x, double y, double xx, double xy, double yx, double yy) +{ + mp_number px, py, pxx, pxy, pyx, pyy ; + mp_node p = mp_new_value_node(mp); + mp_node v; + mp_init_transform_node(mp, p); + v = mp_get_value_node(p); + new_number_from_double(mp, px, x); + new_number_from_double(mp, py, y); + new_number_from_double(mp, pxx, xx); + new_number_from_double(mp, pxy, xy); + new_number_from_double(mp, pyx, yx); + new_number_from_double(mp, pyy, yy); + mp_type(mp_x_part (v)) = mp_known_type; + mp_type(mp_y_part (v)) = mp_known_type; + mp_type(mp_xx_part(v)) = mp_known_type; + mp_type(mp_xy_part(v)) = mp_known_type; + mp_type(mp_yx_part(v)) = mp_known_type; + mp_type(mp_yy_part(v)) = mp_known_type; + mp_set_value_number(mp_x_part (v), px); + mp_set_value_number(mp_y_part (v), py); + mp_set_value_number(mp_xx_part(v), pxx); + mp_set_value_number(mp_xy_part(v), pxy); + mp_set_value_number(mp_yx_part(v), pyx); + mp_set_value_number(mp_yy_part(v), pyy); + free_number(px); + free_number(py); + free_number(pxx); + free_number(pxy); + free_number(pyx); + free_number(pyy); + mp_name_type(p) = mp_capsule_operation; + mp->cur_exp.type = mp_transform_type; + mp_set_cur_exp_node(mp, p); + mp_back_expr(mp); +} + +void mp_push_path_value (MP mp, mp_knot k) +{ + mp->cur_exp.type = mp_path_type; + mp_set_cur_exp_knot(mp, k); + mp_back_expr(mp); +} + +@* 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 \quote {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@> + +Watch out: we also close all files when we do a subrun (execute) so that's why +we have this static closer. + +@ @<Declarations@>= +static void mp_close_files (MP mp); +static void mp_close_files_and_terminate (MP mp); + +@ @c +static void mp_close_files (MP mp) +{ + if (mp->rd_fname != NULL) { + for (int k = 0; k < (int) mp->read_files; k++) { + if (mp->rd_fname[k] != NULL) { + (mp->close_file)(mp, mp->rd_file[k]); + mp_memory_free(mp->rd_fname[k]); + mp->rd_fname[k] = NULL; + } + } + } + if (mp->wr_fname != NULL) { + for (int k = 0; k < (int) mp->write_files; k++) { + if (mp->wr_fname[k] != NULL) { + (mp->close_file)(mp, mp->wr_file[k]); + mp_memory_free(mp->wr_fname[k]); + mp->wr_fname[k] = NULL; + } + } + } +} + +void mp_close_files_and_terminate (MP mp) +{ + if (mp->finished) { + return; + } else { + mp_close_files(mp); + wake_up_terminal(); + mp_print_ln(mp); + mp->finished = 1; + } +} + +@ @<Dealloc ...@>= +mp_close_files(mp); +if (mp->rd_fname != NULL) { + mp_memory_free(mp->rd_file); + mp_memory_free(mp->rd_fname); + mp->rd_file = NULL; + mp->rd_fname = NULL; +} +if (mp->wr_fname != NULL) { + mp_memory_free(mp->wr_file); + mp_memory_free(mp->wr_fname); + mp->wr_file = NULL; + mp->wr_fname = NULL; +} + +@ We get to the |final_cleanup| routine when |end| or |dump| has been +scanned. + +@c +void mp_final_cleanup (MP 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); + } + if (mp->interaction < mp_silent_mode) { + while (mp->open_parens > 0) { + mp_print_str(mp, " )"); + --mp->open_parens; + } + } + while (mp->cond_ptr != NULL) { + mp_print_nl(mp, "(end occurred when "); + @.end occurred...@> + mp_print_cmd_mod(mp, mp_fi_or_else_command, mp->cur_if); + /* |if| or |elseif| or |else| */ + if (mp->if_line != 0) { + mp_print_str(mp, " on line "); + mp_print_int(mp, mp->if_line); + } + mp_print_str(mp, " was incomplete)"); + mp->if_line = mp_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 == mp_term_and_log_selector) { + mp->selector = mp_term_only_selector; + mp_print_nl(mp, "(see the transcript file for additional information)"); + @.see the transcript file...@> + mp->selector = mp_term_and_log_selector; + } + } + } +} + +@ @<Declarations@>= +static void mp_final_cleanup (MP mp); +static void mp_init_prim (MP mp); +static void mp_init_tab (MP mp); + +@ Initialize all the primitives. + +@c void mp_init_prim (MP mp) +{ + @<Put each...@> +} + +@ Initialize other tables: + +@c void mp_init_tab (MP mp) +{ + @<Initialize table entries@> +} + +@* 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 \quote {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 \quote {inner loop}; these account for more than 60\pct! of +\MP's running time, exclusive of input and output. |